gtkStatsPianoRoll.cxx

00001 // Filename: gtkStatsPianoRoll.cxx
00002 // Created by:  drose (16Jan06)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "gtkStatsPianoRoll.h"
00016 #include "gtkStatsMonitor.h"
00017 #include "numeric_types.h"
00018 #include "gtkStatsLabelStack.h"
00019 
00020 static const int default_piano_roll_width = 400;
00021 static const int default_piano_roll_height = 200;
00022 
00023 ////////////////////////////////////////////////////////////////////
00024 //     Function: GtkStatsPianoRoll::Constructor
00025 //       Access: Public
00026 //  Description:
00027 ////////////////////////////////////////////////////////////////////
00028 GtkStatsPianoRoll::
00029 GtkStatsPianoRoll(GtkStatsMonitor *monitor, int thread_index) :
00030   PStatPianoRoll(monitor, thread_index, 
00031                  default_piano_roll_width,
00032                  default_piano_roll_height),
00033   GtkStatsGraph(monitor)
00034 {
00035   // Let's show the units on the guide bar labels.  There's room.
00036   set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
00037 
00038   // Add a DrawingArea widget on top of the graph, to display all of
00039   // the scale units.
00040   _scale_area = gtk_drawing_area_new();
00041   g_signal_connect(G_OBJECT(_scale_area), "expose_event",  
00042        G_CALLBACK(expose_event_callback), this);
00043   gtk_box_pack_start(GTK_BOX(_graph_vbox), _scale_area,
00044          FALSE, FALSE, 0);
00045   gtk_widget_set_size_request(_scale_area, 0, 20);
00046 
00047 
00048   gtk_widget_set_size_request(_graph_window, default_piano_roll_width,
00049             default_piano_roll_height);
00050 
00051   const PStatClientData *client_data = 
00052     GtkStatsGraph::_monitor->get_client_data();
00053   string thread_name = client_data->get_thread_name(_thread_index);
00054   string window_title = thread_name + " thread piano roll";
00055   gtk_window_set_title(GTK_WINDOW(_window), window_title.c_str());
00056       
00057   gtk_widget_show_all(_window);  
00058   gtk_widget_show(_window);
00059 
00060   // Allow the window to be resized as small as the user likes.  We
00061   // have to do this after the window has been shown; otherwise, it
00062   // will affect the window's initial size.
00063   gtk_widget_set_size_request(_window, 0, 0);
00064 
00065   clear_region();
00066 }
00067 
00068 ////////////////////////////////////////////////////////////////////
00069 //     Function: GtkStatsPianoRoll::Destructor
00070 //       Access: Public, Virtual
00071 //  Description:
00072 ////////////////////////////////////////////////////////////////////
00073 GtkStatsPianoRoll::
00074 ~GtkStatsPianoRoll() {
00075 }
00076 
00077 ////////////////////////////////////////////////////////////////////
00078 //     Function: GtkStatsPianoRoll::idle
00079 //       Access: Public, Virtual
00080 //  Description: Called as each frame's data is made available.  There
00081 //               is no gurantee the frames will arrive in order, or
00082 //               that all of them will arrive at all.  The monitor
00083 //               should be prepared to accept frames received
00084 //               out-of-order or missing.
00085 ////////////////////////////////////////////////////////////////////
00086 void GtkStatsPianoRoll::
00087 new_data(int thread_index, int frame_number) {
00088   if (!_pause) {
00089     update();
00090   }
00091 }
00092 
00093 ////////////////////////////////////////////////////////////////////
00094 //     Function: GtkStatsPianoRoll::force_redraw
00095 //       Access: Public, Virtual
00096 //  Description: Called when it is necessary to redraw the entire graph.
00097 ////////////////////////////////////////////////////////////////////
00098 void GtkStatsPianoRoll::
00099 force_redraw() {
00100   PStatPianoRoll::force_redraw();
00101 }
00102 
00103 ////////////////////////////////////////////////////////////////////
00104 //     Function: GtkStatsPianoRoll::changed_graph_size
00105 //       Access: Public, Virtual
00106 //  Description: Called when the user has resized the window, forcing
00107 //               a resize of the graph.
00108 ////////////////////////////////////////////////////////////////////
00109 void GtkStatsPianoRoll::
00110 changed_graph_size(int graph_xsize, int graph_ysize) {
00111   PStatPianoRoll::changed_size(graph_xsize, graph_ysize);
00112 }
00113 
00114 ////////////////////////////////////////////////////////////////////
00115 //     Function: GtkStatsPianoRoll::set_time_units
00116 //       Access: Public, Virtual
00117 //  Description: Called when the user selects a new time units from
00118 //               the monitor pulldown menu, this should adjust the
00119 //               units for the graph to the indicated mask if it is a
00120 //               time-based graph.
00121 ////////////////////////////////////////////////////////////////////
00122 void GtkStatsPianoRoll::
00123 set_time_units(int unit_mask) {
00124   int old_unit_mask = get_guide_bar_units();
00125   if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) {
00126     unit_mask = unit_mask & (GBU_hz | GBU_ms);
00127     unit_mask |= (old_unit_mask & GBU_show_units);
00128     set_guide_bar_units(unit_mask);
00129 
00130     gtk_widget_queue_draw(_scale_area);
00131   }
00132 }
00133 
00134 ////////////////////////////////////////////////////////////////////
00135 //     Function: GtkStatsPianoRoll::clicked_label
00136 //       Access: Public, Virtual
00137 //  Description: Called when the user single-clicks on a label.
00138 ////////////////////////////////////////////////////////////////////
00139 void GtkStatsPianoRoll::
00140 clicked_label(int collector_index) {
00141   if (collector_index >= 0) {
00142     GtkStatsGraph::_monitor->open_strip_chart(_thread_index, collector_index, false);
00143   }
00144 }
00145 
00146 ////////////////////////////////////////////////////////////////////
00147 //     Function: GtkStatsPianoRoll::set_horizontal_scale
00148 //       Access: Public
00149 //  Description: Changes the amount of time the width of the
00150 //               horizontal axis represents.  This may force a redraw.
00151 ////////////////////////////////////////////////////////////////////
00152 void GtkStatsPianoRoll::
00153 set_horizontal_scale(float time_width) {
00154   PStatPianoRoll::set_horizontal_scale(time_width);
00155 
00156   gtk_widget_queue_draw(_graph_window);
00157   gtk_widget_queue_draw(_scale_area);
00158 }
00159 
00160 ////////////////////////////////////////////////////////////////////
00161 //     Function: GtkStatsPianoRoll::clear_region
00162 //       Access: Protected
00163 //  Description: Erases the chart area.
00164 ////////////////////////////////////////////////////////////////////
00165 void GtkStatsPianoRoll::
00166 clear_region() {
00167   gdk_gc_set_rgb_fg_color(_pixmap_gc, &rgb_white);
00168   gdk_draw_rectangle(_pixmap, _pixmap_gc, TRUE, 0, 0, 
00169          get_xsize(), get_ysize());
00170 }
00171 
00172 ////////////////////////////////////////////////////////////////////
00173 //     Function: GtkStatsPianoRoll::begin_draw
00174 //       Access: Protected, Virtual
00175 //  Description: Erases the chart area in preparation for drawing a
00176 //               bunch of bars.
00177 ////////////////////////////////////////////////////////////////////
00178 void GtkStatsPianoRoll::
00179 begin_draw() {
00180   clear_region();
00181 
00182   // Draw in the guide bars.
00183   int num_guide_bars = get_num_guide_bars();
00184   for (int i = 0; i < num_guide_bars; i++) {
00185     draw_guide_bar(_pixmap, get_guide_bar(i));
00186   }
00187 }
00188 
00189 ////////////////////////////////////////////////////////////////////
00190 //     Function: GtkStatsPianoRoll::draw_bar
00191 //       Access: Protected, Virtual
00192 //  Description: Draws a single bar on the chart.
00193 ////////////////////////////////////////////////////////////////////
00194 void GtkStatsPianoRoll::
00195 draw_bar(int row, int from_x, int to_x) {
00196   if (row >= 0 && row < _label_stack.get_num_labels()) {
00197     int y = _label_stack.get_label_y(row, _graph_window);
00198     int height = _label_stack.get_label_height(row);
00199 
00200     int collector_index = get_label_collector(row);
00201     GdkGC *gc = get_collector_gc(collector_index);
00202     
00203     gdk_draw_rectangle(_pixmap, gc, TRUE, 
00204            from_x, y - height + 2, 
00205            to_x - from_x, height - 4);
00206   }
00207 }
00208 
00209 ////////////////////////////////////////////////////////////////////
00210 //     Function: GtkStatsPianoRoll::end_draw
00211 //       Access: Protected, Virtual
00212 //  Description: Called after all the bars have been drawn, this
00213 //               triggers a refresh event to draw it to the window.
00214 ////////////////////////////////////////////////////////////////////
00215 void GtkStatsPianoRoll::
00216 end_draw() {
00217   gtk_widget_queue_draw(_graph_window);
00218 }
00219 
00220 ////////////////////////////////////////////////////////////////////
00221 //     Function: GtkStatsPianoRoll::idle
00222 //       Access: Protected, Virtual
00223 //  Description: Called at the end of the draw cycle.
00224 ////////////////////////////////////////////////////////////////////
00225 void GtkStatsPianoRoll::
00226 idle() {
00227   if (_labels_changed) {
00228     update_labels();
00229   }
00230 }
00231 
00232 ////////////////////////////////////////////////////////////////////
00233 //     Function: GtkStatsPianoRoll::additional_graph_window_paint
00234 //       Access: Protected, Virtual
00235 //  Description: This is called during the servicing of expose_event;
00236 //               it gives a derived class opportunity to do some
00237 //               further painting into the graph window.
00238 ////////////////////////////////////////////////////////////////////
00239 void GtkStatsPianoRoll::
00240 additional_graph_window_paint() {
00241   int num_user_guide_bars = get_num_user_guide_bars();
00242   for (int i = 0; i < num_user_guide_bars; i++) {
00243     draw_guide_bar(_graph_window->window, get_user_guide_bar(i));
00244   }
00245 }
00246 
00247 ////////////////////////////////////////////////////////////////////
00248 //     Function: GtkStatsPianoRoll::consider_drag_start
00249 //       Access: Protected, Virtual
00250 //  Description: Based on the mouse position within the graph window,
00251 //               look for draggable things the mouse might be hovering
00252 //               over and return the appropriate DragMode enum or
00253 //               DM_none if nothing is indicated.
00254 ////////////////////////////////////////////////////////////////////
00255 GtkStatsGraph::DragMode GtkStatsPianoRoll::
00256 consider_drag_start(int graph_x, int graph_y) {
00257   if (graph_y >= 0 && graph_y < get_ysize()) {
00258     if (graph_x >= 0 && graph_x < get_xsize()) {
00259       // See if the mouse is over a user-defined guide bar.
00260       int x = graph_x;
00261       float from_height = pixel_to_height(x - 2);
00262       float to_height = pixel_to_height(x + 2);
00263       _drag_guide_bar = find_user_guide_bar(from_height, to_height);
00264       if (_drag_guide_bar >= 0) {
00265         return DM_guide_bar;
00266       }
00267 
00268     } else {
00269       // The mouse is left or right of the graph; maybe create a new
00270       // guide bar.
00271       return DM_new_guide_bar;
00272     }
00273   }
00274 
00275   return GtkStatsGraph::consider_drag_start(graph_x, graph_y);
00276 }
00277 
00278 ////////////////////////////////////////////////////////////////////
00279 //     Function: GtkStatsPianoRoll::handle_button_press
00280 //       Access: Protected, Virtual
00281 //  Description: Called when the mouse button is depressed within the
00282 //               graph window.
00283 ////////////////////////////////////////////////////////////////////
00284 gboolean GtkStatsPianoRoll::
00285 handle_button_press(GtkWidget *widget, int graph_x, int graph_y,
00286         bool double_click) {
00287   if (double_click) {
00288     // Double-clicking on a color bar in the graph is the same as
00289     // double-clicking on the corresponding label.
00290     clicked_label(get_collector_under_pixel(graph_x, graph_y));
00291     return TRUE;
00292   }
00293 
00294   if (_potential_drag_mode == DM_none) {
00295     set_drag_mode(DM_scale);
00296     _drag_scale_start = pixel_to_height(graph_x);
00297     //SetCapture(_graph_window);
00298     return TRUE;
00299 
00300   } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) {
00301     set_drag_mode(DM_guide_bar);
00302     _drag_start_x = graph_x;
00303     //SetCapture(_graph_window);
00304     return TRUE;
00305   }
00306 
00307   return GtkStatsGraph::handle_button_press(widget, graph_x, graph_y, 
00308               double_click);
00309 }
00310 
00311 ////////////////////////////////////////////////////////////////////
00312 //     Function: GtkStatsPianoRoll::handle_button_release
00313 //       Access: Protected, Virtual
00314 //  Description: Called when the mouse button is released within the
00315 //               graph window.
00316 ////////////////////////////////////////////////////////////////////
00317 gboolean GtkStatsPianoRoll::
00318 handle_button_release(GtkWidget *widget, int graph_x, int graph_y) {
00319   if (_drag_mode == DM_scale) {
00320     set_drag_mode(DM_none);
00321     //ReleaseCapture();
00322     return handle_motion(widget, graph_x, graph_y);
00323     
00324   } else if (_drag_mode == DM_guide_bar) {
00325     if (graph_x < 0 || graph_x >= get_xsize()) {
00326       remove_user_guide_bar(_drag_guide_bar);
00327     } else {
00328       move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
00329     }
00330     set_drag_mode(DM_none);
00331     //ReleaseCapture();
00332     return handle_motion(widget, graph_x, graph_y);
00333   }
00334 
00335   return GtkStatsGraph::handle_button_release(widget, graph_x, graph_y);
00336 }
00337 
00338 ////////////////////////////////////////////////////////////////////
00339 //     Function: GtkStatsPianoRoll::ns_motion_notify_event_callback
00340 //       Access: Protected, Virtual
00341 //  Description: Called when the mouse is moved within the
00342 //               graph window.
00343 ////////////////////////////////////////////////////////////////////
00344 gboolean GtkStatsPianoRoll::
00345 handle_motion(GtkWidget *widget, int graph_x, int graph_y) {
00346   if (_drag_mode == DM_none && _potential_drag_mode == DM_none) {
00347     // When the mouse is over a color bar, highlight it.
00348     _label_stack.highlight_label(get_collector_under_pixel(graph_x, graph_y));
00349 
00350     /*
00351     // Now we want to get a WM_MOUSELEAVE when the mouse leaves the
00352     // graph window.
00353     TRACKMOUSEEVENT tme = {
00354       sizeof(TRACKMOUSEEVENT),
00355       TME_LEAVE,
00356       _graph_window,
00357       0
00358     };
00359     TrackMouseEvent(&tme);
00360     */
00361 
00362   } else {
00363     // If the mouse is in some drag mode, stop highlighting.
00364     _label_stack.highlight_label(-1);
00365   }
00366 
00367   if (_drag_mode == DM_scale) {
00368     float ratio = (float)graph_x / (float)get_xsize();
00369     if (ratio > 0.0f) {
00370       set_horizontal_scale(_drag_scale_start / ratio);
00371     }
00372     return TRUE;
00373 
00374   } else if (_drag_mode == DM_new_guide_bar) {
00375     // We haven't created the new guide bar yet; we won't until the
00376     // mouse comes within the graph's region.
00377     if (graph_x >= 0 && graph_x < get_xsize()) {
00378       set_drag_mode(DM_guide_bar);
00379       _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_x));
00380       return TRUE;
00381     }
00382 
00383   } else if (_drag_mode == DM_guide_bar) {
00384     move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
00385     return TRUE;
00386   }
00387 
00388   return GtkStatsGraph::handle_motion(widget, graph_x, graph_y);
00389 }
00390 
00391 ////////////////////////////////////////////////////////////////////
00392 //     Function: GtkStatsPianoRoll::get_collector_under_pixel
00393 //       Access: Private
00394 //  Description: Returns the collector index associated with the
00395 //               indicated vertical row, or -1.
00396 ////////////////////////////////////////////////////////////////////
00397 int GtkStatsPianoRoll::
00398 get_collector_under_pixel(int xpoint, int ypoint) {
00399   if (_label_stack.get_num_labels() == 0) {
00400     return -1;
00401   }
00402 
00403   // Assume all of the labels are the same height.
00404   int height = _label_stack.get_label_height(0);
00405   int row = (get_ysize() - ypoint) / height;
00406   if (row >= 0 && row < _label_stack.get_num_labels()) {
00407     return _label_stack.get_label_collector_index(row);
00408   } else  {
00409     return -1;
00410   }
00411 }
00412 
00413 ////////////////////////////////////////////////////////////////////
00414 //     Function: GtkStatsPianoRoll::update_labels
00415 //       Access: Private
00416 //  Description: Resets the list of labels.
00417 ////////////////////////////////////////////////////////////////////
00418 void GtkStatsPianoRoll::
00419 update_labels() {
00420   _label_stack.clear_labels();
00421   for (int i = 0; i < get_num_labels(); i++) {
00422     _label_stack.add_label(GtkStatsGraph::_monitor, this,
00423          _thread_index,
00424          get_label_collector(i), true);
00425   }
00426   _labels_changed = false;
00427 }
00428 
00429 ////////////////////////////////////////////////////////////////////
00430 //     Function: GtkStatsPianoRoll::draw_guide_bar
00431 //       Access: Private
00432 //  Description: Draws the line for the indicated guide bar on the
00433 //               graph.
00434 ////////////////////////////////////////////////////////////////////
00435 void GtkStatsPianoRoll::
00436 draw_guide_bar(GdkDrawable *surface, const PStatGraph::GuideBar &bar) {
00437   int x = height_to_pixel(bar._height);
00438 
00439   if (x > 0 && x < get_xsize() - 1) {
00440     // Only draw it if it's not too close to the top.
00441     switch (bar._style) {
00442     case GBS_target:
00443       gdk_gc_set_rgb_fg_color(_pixmap_gc, &rgb_light_gray);
00444       break;
00445 
00446     case GBS_user:
00447       gdk_gc_set_rgb_fg_color(_pixmap_gc, &rgb_user_guide_bar);
00448       break;
00449       
00450     case GBS_normal:
00451       gdk_gc_set_rgb_fg_color(_pixmap_gc, &rgb_dark_gray);
00452       break;
00453     }
00454     gdk_draw_line(surface, _pixmap_gc, x, 0, x, get_ysize());
00455   }
00456 }
00457 
00458 ////////////////////////////////////////////////////////////////////
00459 //     Function: GtkStatsPianoRoll::draw_guide_labels
00460 //       Access: Private
00461 //  Description: This is called during the servicing of expose_event.
00462 ////////////////////////////////////////////////////////////////////
00463 void GtkStatsPianoRoll::
00464 draw_guide_labels() {
00465   int i;
00466   int num_guide_bars = get_num_guide_bars();
00467   for (i = 0; i < num_guide_bars; i++) {
00468     draw_guide_label(get_guide_bar(i));
00469   }
00470 
00471   int num_user_guide_bars = get_num_user_guide_bars();
00472   for (i = 0; i < num_user_guide_bars; i++) {
00473     draw_guide_label(get_user_guide_bar(i));
00474   }
00475 }
00476 
00477 ////////////////////////////////////////////////////////////////////
00478 //     Function: GtkStatsPianoRoll::draw_guide_label
00479 //       Access: Private
00480 //  Description: Draws the text for the indicated guide bar label at
00481 //               the top of the graph.
00482 ////////////////////////////////////////////////////////////////////
00483 void GtkStatsPianoRoll::
00484 draw_guide_label(const PStatGraph::GuideBar &bar) {
00485   GdkGC *gc = gdk_gc_new(_scale_area->window);
00486 
00487   switch (bar._style) {
00488   case GBS_target:
00489     gdk_gc_set_rgb_fg_color(gc, &rgb_light_gray);
00490     break;
00491     
00492   case GBS_user:
00493     gdk_gc_set_rgb_fg_color(gc, &rgb_user_guide_bar);
00494     break;
00495     
00496   case GBS_normal:
00497     gdk_gc_set_rgb_fg_color(gc, &rgb_dark_gray);
00498     break;
00499   }
00500 
00501   int x = height_to_pixel(bar._height);
00502   const string &label = bar._label;
00503 
00504   PangoLayout *layout = gtk_widget_create_pango_layout(_window, label.c_str());
00505   int width, height;
00506   pango_layout_get_pixel_size(layout, &width, &height);
00507 
00508   if (bar._style != GBS_user) {
00509     float from_height = pixel_to_height(x - width);
00510     float to_height = pixel_to_height(x + width);
00511     if (find_user_guide_bar(from_height, to_height) >= 0) {
00512       // Omit the label: there's a user-defined guide bar in the same space.
00513       g_object_unref(layout);
00514       g_object_unref(gc);
00515       return;
00516     }
00517   }
00518 
00519   if (x >= 0 && x < get_xsize()) {
00520     // Now convert our x to a coordinate within our drawing area.
00521     int junk_y;
00522     
00523     // The x coordinate comes from the graph_window.
00524     gtk_widget_translate_coordinates(_graph_window, _scale_area,
00525              x, 0,
00526              &x, &junk_y);
00527     
00528     int this_x = x - width / 2;
00529     gdk_draw_layout(_scale_area->window, gc, this_x, 
00530         _scale_area->allocation.height - height, layout);
00531   }
00532     
00533   g_object_unref(layout);
00534   g_object_unref(gc);
00535 }
00536 
00537 ////////////////////////////////////////////////////////////////////
00538 //     Function: GtkStatsPianoRoll::expose_event_callback
00539 //       Access: Private, Static
00540 //  Description: Draws in the scale labels.
00541 ////////////////////////////////////////////////////////////////////
00542 gboolean GtkStatsPianoRoll::
00543 expose_event_callback(GtkWidget *widget, GdkEventExpose *event, gpointer data) {
00544   GtkStatsPianoRoll *self = (GtkStatsPianoRoll *)data;
00545   self->draw_guide_labels();
00546 
00547   return TRUE;
00548 }