Panda3D
gtkStatsPianoRoll.cxx
1 // Filename: gtkStatsPianoRoll.cxx
2 // Created by: drose (16Jan06)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "gtkStatsPianoRoll.h"
16 #include "gtkStatsMonitor.h"
17 #include "numeric_types.h"
18 #include "gtkStatsLabelStack.h"
19 
20 static const int default_piano_roll_width = 400;
21 static const int default_piano_roll_height = 200;
22 
23 ////////////////////////////////////////////////////////////////////
24 // Function: GtkStatsPianoRoll::Constructor
25 // Access: Public
26 // Description:
27 ////////////////////////////////////////////////////////////////////
28 GtkStatsPianoRoll::
29 GtkStatsPianoRoll(GtkStatsMonitor *monitor, int thread_index) :
30  PStatPianoRoll(monitor, thread_index,
31  default_piano_roll_width,
32  default_piano_roll_height),
33  GtkStatsGraph(monitor)
34 {
35  // Let's show the units on the guide bar labels. There's room.
36  set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
37 
38  // Add a DrawingArea widget on top of the graph, to display all of
39  // the scale units.
40  _scale_area = gtk_drawing_area_new();
41  g_signal_connect(G_OBJECT(_scale_area), "expose_event",
42  G_CALLBACK(expose_event_callback), this);
43  gtk_box_pack_start(GTK_BOX(_graph_vbox), _scale_area,
44  FALSE, FALSE, 0);
45  gtk_widget_set_size_request(_scale_area, 0, 20);
46 
47 
48  gtk_widget_set_size_request(_graph_window, default_piano_roll_width,
49  default_piano_roll_height);
50 
51  const PStatClientData *client_data =
52  GtkStatsGraph::_monitor->get_client_data();
53  string thread_name = client_data->get_thread_name(_thread_index);
54  string window_title = thread_name + " thread piano roll";
55  gtk_window_set_title(GTK_WINDOW(_window), window_title.c_str());
56 
57  gtk_widget_show_all(_window);
58  gtk_widget_show(_window);
59 
60  // Allow the window to be resized as small as the user likes. We
61  // have to do this after the window has been shown; otherwise, it
62  // will affect the window's initial size.
63  gtk_widget_set_size_request(_window, 0, 0);
64 
65  clear_region();
66 }
67 
68 ////////////////////////////////////////////////////////////////////
69 // Function: GtkStatsPianoRoll::Destructor
70 // Access: Public, Virtual
71 // Description:
72 ////////////////////////////////////////////////////////////////////
73 GtkStatsPianoRoll::
74 ~GtkStatsPianoRoll() {
75 }
76 
77 ////////////////////////////////////////////////////////////////////
78 // Function: GtkStatsPianoRoll::idle
79 // Access: Public, Virtual
80 // Description: Called as each frame's data is made available. There
81 // is no gurantee the frames will arrive in order, or
82 // that all of them will arrive at all. The monitor
83 // should be prepared to accept frames received
84 // out-of-order or missing.
85 ////////////////////////////////////////////////////////////////////
87 new_data(int thread_index, int frame_number) {
88  if (!_pause) {
89  update();
90  }
91 }
92 
93 ////////////////////////////////////////////////////////////////////
94 // Function: GtkStatsPianoRoll::force_redraw
95 // Access: Public, Virtual
96 // Description: Called when it is necessary to redraw the entire graph.
97 ////////////////////////////////////////////////////////////////////
100  PStatPianoRoll::force_redraw();
101 }
102 
103 ////////////////////////////////////////////////////////////////////
104 // Function: GtkStatsPianoRoll::changed_graph_size
105 // Access: Public, Virtual
106 // Description: Called when the user has resized the window, forcing
107 // a resize of the graph.
108 ////////////////////////////////////////////////////////////////////
110 changed_graph_size(int graph_xsize, int graph_ysize) {
111  PStatPianoRoll::changed_size(graph_xsize, graph_ysize);
112 }
113 
114 ////////////////////////////////////////////////////////////////////
115 // Function: GtkStatsPianoRoll::set_time_units
116 // Access: Public, Virtual
117 // Description: Called when the user selects a new time units from
118 // the monitor pulldown menu, this should adjust the
119 // units for the graph to the indicated mask if it is a
120 // time-based graph.
121 ////////////////////////////////////////////////////////////////////
123 set_time_units(int unit_mask) {
124  int old_unit_mask = get_guide_bar_units();
125  if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) {
126  unit_mask = unit_mask & (GBU_hz | GBU_ms);
127  unit_mask |= (old_unit_mask & GBU_show_units);
128  set_guide_bar_units(unit_mask);
129 
130  gtk_widget_queue_draw(_scale_area);
131  }
132 }
133 
134 ////////////////////////////////////////////////////////////////////
135 // Function: GtkStatsPianoRoll::clicked_label
136 // Access: Public, Virtual
137 // Description: Called when the user single-clicks on a label.
138 ////////////////////////////////////////////////////////////////////
140 clicked_label(int collector_index) {
141  if (collector_index >= 0) {
142  GtkStatsGraph::_monitor->open_strip_chart(_thread_index, collector_index, false);
143  }
144 }
145 
146 ////////////////////////////////////////////////////////////////////
147 // Function: GtkStatsPianoRoll::set_horizontal_scale
148 // Access: Public
149 // Description: Changes the amount of time the width of the
150 // horizontal axis represents. This may force a redraw.
151 ////////////////////////////////////////////////////////////////////
153 set_horizontal_scale(double time_width) {
155 
156  gtk_widget_queue_draw(_graph_window);
157  gtk_widget_queue_draw(_scale_area);
158 }
159 
160 ////////////////////////////////////////////////////////////////////
161 // Function: GtkStatsPianoRoll::clear_region
162 // Access: Protected
163 // Description: Erases the chart area.
164 ////////////////////////////////////////////////////////////////////
165 void GtkStatsPianoRoll::
166 clear_region() {
167  gdk_gc_set_rgb_fg_color(_pixmap_gc, &rgb_white);
168  gdk_draw_rectangle(_pixmap, _pixmap_gc, TRUE, 0, 0,
169  get_xsize(), get_ysize());
170 }
171 
172 ////////////////////////////////////////////////////////////////////
173 // Function: GtkStatsPianoRoll::begin_draw
174 // Access: Protected, Virtual
175 // Description: Erases the chart area in preparation for drawing a
176 // bunch of bars.
177 ////////////////////////////////////////////////////////////////////
178 void GtkStatsPianoRoll::
179 begin_draw() {
180  clear_region();
181 
182  // Draw in the guide bars.
183  int num_guide_bars = get_num_guide_bars();
184  for (int i = 0; i < num_guide_bars; i++) {
185  draw_guide_bar(_pixmap, get_guide_bar(i));
186  }
187 }
188 
189 ////////////////////////////////////////////////////////////////////
190 // Function: GtkStatsPianoRoll::draw_bar
191 // Access: Protected, Virtual
192 // Description: Draws a single bar on the chart.
193 ////////////////////////////////////////////////////////////////////
194 void GtkStatsPianoRoll::
195 draw_bar(int row, int from_x, int to_x) {
196  if (row >= 0 && row < _label_stack.get_num_labels()) {
197  int y = _label_stack.get_label_y(row, _graph_window);
198  int height = _label_stack.get_label_height(row);
199 
200  int collector_index = get_label_collector(row);
201  GdkGC *gc = get_collector_gc(collector_index);
202 
203  gdk_draw_rectangle(_pixmap, gc, TRUE,
204  from_x, y - height + 2,
205  to_x - from_x, height - 4);
206  }
207 }
208 
209 ////////////////////////////////////////////////////////////////////
210 // Function: GtkStatsPianoRoll::end_draw
211 // Access: Protected, Virtual
212 // Description: Called after all the bars have been drawn, this
213 // triggers a refresh event to draw it to the window.
214 ////////////////////////////////////////////////////////////////////
215 void GtkStatsPianoRoll::
216 end_draw() {
217  gtk_widget_queue_draw(_graph_window);
218 }
219 
220 ////////////////////////////////////////////////////////////////////
221 // Function: GtkStatsPianoRoll::idle
222 // Access: Protected, Virtual
223 // Description: Called at the end of the draw cycle.
224 ////////////////////////////////////////////////////////////////////
225 void GtkStatsPianoRoll::
226 idle() {
227  if (_labels_changed) {
228  update_labels();
229  }
230 }
231 
232 ////////////////////////////////////////////////////////////////////
233 // Function: GtkStatsPianoRoll::additional_graph_window_paint
234 // Access: Protected, Virtual
235 // Description: This is called during the servicing of expose_event;
236 // it gives a derived class opportunity to do some
237 // further painting into the graph window.
238 ////////////////////////////////////////////////////////////////////
239 void GtkStatsPianoRoll::
240 additional_graph_window_paint() {
241  int num_user_guide_bars = get_num_user_guide_bars();
242  for (int i = 0; i < num_user_guide_bars; i++) {
243  draw_guide_bar(_graph_window->window, get_user_guide_bar(i));
244  }
245 }
246 
247 ////////////////////////////////////////////////////////////////////
248 // Function: GtkStatsPianoRoll::consider_drag_start
249 // Access: Protected, Virtual
250 // Description: Based on the mouse position within the graph window,
251 // look for draggable things the mouse might be hovering
252 // over and return the appropriate DragMode enum or
253 // DM_none if nothing is indicated.
254 ////////////////////////////////////////////////////////////////////
255 GtkStatsGraph::DragMode GtkStatsPianoRoll::
256 consider_drag_start(int graph_x, int graph_y) {
257  if (graph_y >= 0 && graph_y < get_ysize()) {
258  if (graph_x >= 0 && graph_x < get_xsize()) {
259  // See if the mouse is over a user-defined guide bar.
260  int x = graph_x;
261  double from_height = pixel_to_height(x - 2);
262  double to_height = pixel_to_height(x + 2);
263  _drag_guide_bar = find_user_guide_bar(from_height, to_height);
264  if (_drag_guide_bar >= 0) {
265  return DM_guide_bar;
266  }
267 
268  } else {
269  // The mouse is left or right of the graph; maybe create a new
270  // guide bar.
271  return DM_new_guide_bar;
272  }
273  }
274 
275  return GtkStatsGraph::consider_drag_start(graph_x, graph_y);
276 }
277 
278 ////////////////////////////////////////////////////////////////////
279 // Function: GtkStatsPianoRoll::handle_button_press
280 // Access: Protected, Virtual
281 // Description: Called when the mouse button is depressed within the
282 // graph window.
283 ////////////////////////////////////////////////////////////////////
284 gboolean GtkStatsPianoRoll::
285 handle_button_press(GtkWidget *widget, int graph_x, int graph_y,
286  bool double_click) {
287  if (double_click) {
288  // Double-clicking on a color bar in the graph is the same as
289  // double-clicking on the corresponding label.
290  clicked_label(get_collector_under_pixel(graph_x, graph_y));
291  return TRUE;
292  }
293 
294  if (_potential_drag_mode == DM_none) {
295  set_drag_mode(DM_scale);
296  _drag_scale_start = pixel_to_height(graph_x);
297  //SetCapture(_graph_window);
298  return TRUE;
299 
300  } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) {
301  set_drag_mode(DM_guide_bar);
302  _drag_start_x = graph_x;
303  //SetCapture(_graph_window);
304  return TRUE;
305  }
306 
307  return GtkStatsGraph::handle_button_press(widget, graph_x, graph_y,
308  double_click);
309 }
310 
311 ////////////////////////////////////////////////////////////////////
312 // Function: GtkStatsPianoRoll::handle_button_release
313 // Access: Protected, Virtual
314 // Description: Called when the mouse button is released within the
315 // graph window.
316 ////////////////////////////////////////////////////////////////////
317 gboolean GtkStatsPianoRoll::
318 handle_button_release(GtkWidget *widget, int graph_x, int graph_y) {
319  if (_drag_mode == DM_scale) {
320  set_drag_mode(DM_none);
321  //ReleaseCapture();
322  return handle_motion(widget, graph_x, graph_y);
323 
324  } else if (_drag_mode == DM_guide_bar) {
325  if (graph_x < 0 || graph_x >= get_xsize()) {
326  remove_user_guide_bar(_drag_guide_bar);
327  } else {
328  move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
329  }
330  set_drag_mode(DM_none);
331  //ReleaseCapture();
332  return handle_motion(widget, graph_x, graph_y);
333  }
334 
335  return GtkStatsGraph::handle_button_release(widget, graph_x, graph_y);
336 }
337 
338 ////////////////////////////////////////////////////////////////////
339 // Function: GtkStatsPianoRoll::ns_motion_notify_event_callback
340 // Access: Protected, Virtual
341 // Description: Called when the mouse is moved within the
342 // graph window.
343 ////////////////////////////////////////////////////////////////////
344 gboolean GtkStatsPianoRoll::
345 handle_motion(GtkWidget *widget, int graph_x, int graph_y) {
346  if (_drag_mode == DM_none && _potential_drag_mode == DM_none) {
347  // When the mouse is over a color bar, highlight it.
348  _label_stack.highlight_label(get_collector_under_pixel(graph_x, graph_y));
349 
350  /*
351  // Now we want to get a WM_MOUSELEAVE when the mouse leaves the
352  // graph window.
353  TRACKMOUSEEVENT tme = {
354  sizeof(TRACKMOUSEEVENT),
355  TME_LEAVE,
356  _graph_window,
357  0
358  };
359  TrackMouseEvent(&tme);
360  */
361 
362  } else {
363  // If the mouse is in some drag mode, stop highlighting.
364  _label_stack.highlight_label(-1);
365  }
366 
367  if (_drag_mode == DM_scale) {
368  double ratio = (double)graph_x / (double)get_xsize();
369  if (ratio > 0.0f) {
370  set_horizontal_scale(_drag_scale_start / ratio);
371  }
372  return TRUE;
373 
374  } else if (_drag_mode == DM_new_guide_bar) {
375  // We haven't created the new guide bar yet; we won't until the
376  // mouse comes within the graph's region.
377  if (graph_x >= 0 && graph_x < get_xsize()) {
378  set_drag_mode(DM_guide_bar);
379  _drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_x));
380  return TRUE;
381  }
382 
383  } else if (_drag_mode == DM_guide_bar) {
384  move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
385  return TRUE;
386  }
387 
388  return GtkStatsGraph::handle_motion(widget, graph_x, graph_y);
389 }
390 
391 ////////////////////////////////////////////////////////////////////
392 // Function: GtkStatsPianoRoll::get_collector_under_pixel
393 // Access: Private
394 // Description: Returns the collector index associated with the
395 // indicated vertical row, or -1.
396 ////////////////////////////////////////////////////////////////////
397 int GtkStatsPianoRoll::
398 get_collector_under_pixel(int xpoint, int ypoint) {
399  if (_label_stack.get_num_labels() == 0) {
400  return -1;
401  }
402 
403  // Assume all of the labels are the same height.
404  int height = _label_stack.get_label_height(0);
405  int row = (get_ysize() - ypoint) / height;
406  if (row >= 0 && row < _label_stack.get_num_labels()) {
407  return _label_stack.get_label_collector_index(row);
408  } else {
409  return -1;
410  }
411 }
412 
413 ////////////////////////////////////////////////////////////////////
414 // Function: GtkStatsPianoRoll::update_labels
415 // Access: Private
416 // Description: Resets the list of labels.
417 ////////////////////////////////////////////////////////////////////
418 void GtkStatsPianoRoll::
419 update_labels() {
420  _label_stack.clear_labels();
421  for (int i = 0; i < get_num_labels(); i++) {
422  _label_stack.add_label(GtkStatsGraph::_monitor, this,
423  _thread_index,
424  get_label_collector(i), true);
425  }
426  _labels_changed = false;
427 }
428 
429 ////////////////////////////////////////////////////////////////////
430 // Function: GtkStatsPianoRoll::draw_guide_bar
431 // Access: Private
432 // Description: Draws the line for the indicated guide bar on the
433 // graph.
434 ////////////////////////////////////////////////////////////////////
435 void GtkStatsPianoRoll::
436 draw_guide_bar(GdkDrawable *surface, const PStatGraph::GuideBar &bar) {
437  int x = height_to_pixel(bar._height);
438 
439  if (x > 0 && x < get_xsize() - 1) {
440  // Only draw it if it's not too close to the top.
441  switch (bar._style) {
442  case GBS_target:
443  gdk_gc_set_rgb_fg_color(_pixmap_gc, &rgb_light_gray);
444  break;
445 
446  case GBS_user:
447  gdk_gc_set_rgb_fg_color(_pixmap_gc, &rgb_user_guide_bar);
448  break;
449 
450  case GBS_normal:
451  gdk_gc_set_rgb_fg_color(_pixmap_gc, &rgb_dark_gray);
452  break;
453  }
454  gdk_draw_line(surface, _pixmap_gc, x, 0, x, get_ysize());
455  }
456 }
457 
458 ////////////////////////////////////////////////////////////////////
459 // Function: GtkStatsPianoRoll::draw_guide_labels
460 // Access: Private
461 // Description: This is called during the servicing of expose_event.
462 ////////////////////////////////////////////////////////////////////
463 void GtkStatsPianoRoll::
464 draw_guide_labels() {
465  int i;
466  int num_guide_bars = get_num_guide_bars();
467  for (i = 0; i < num_guide_bars; i++) {
468  draw_guide_label(get_guide_bar(i));
469  }
470 
471  int num_user_guide_bars = get_num_user_guide_bars();
472  for (i = 0; i < num_user_guide_bars; i++) {
473  draw_guide_label(get_user_guide_bar(i));
474  }
475 }
476 
477 ////////////////////////////////////////////////////////////////////
478 // Function: GtkStatsPianoRoll::draw_guide_label
479 // Access: Private
480 // Description: Draws the text for the indicated guide bar label at
481 // the top of the graph.
482 ////////////////////////////////////////////////////////////////////
483 void GtkStatsPianoRoll::
484 draw_guide_label(const PStatGraph::GuideBar &bar) {
485  GdkGC *gc = gdk_gc_new(_scale_area->window);
486 
487  switch (bar._style) {
488  case GBS_target:
489  gdk_gc_set_rgb_fg_color(gc, &rgb_light_gray);
490  break;
491 
492  case GBS_user:
493  gdk_gc_set_rgb_fg_color(gc, &rgb_user_guide_bar);
494  break;
495 
496  case GBS_normal:
497  gdk_gc_set_rgb_fg_color(gc, &rgb_dark_gray);
498  break;
499  }
500 
501  int x = height_to_pixel(bar._height);
502  const string &label = bar._label;
503 
504  PangoLayout *layout = gtk_widget_create_pango_layout(_window, label.c_str());
505  int width, height;
506  pango_layout_get_pixel_size(layout, &width, &height);
507 
508  if (bar._style != GBS_user) {
509  double from_height = pixel_to_height(x - width);
510  double to_height = pixel_to_height(x + width);
511  if (find_user_guide_bar(from_height, to_height) >= 0) {
512  // Omit the label: there's a user-defined guide bar in the same space.
513  g_object_unref(layout);
514  g_object_unref(gc);
515  return;
516  }
517  }
518 
519  if (x >= 0 && x < get_xsize()) {
520  // Now convert our x to a coordinate within our drawing area.
521  int junk_y;
522 
523  // The x coordinate comes from the graph_window.
524  gtk_widget_translate_coordinates(_graph_window, _scale_area,
525  x, 0,
526  &x, &junk_y);
527 
528  int this_x = x - width / 2;
529  gdk_draw_layout(_scale_area->window, gc, this_x,
530  _scale_area->allocation.height - height, layout);
531  }
532 
533  g_object_unref(layout);
534  g_object_unref(gc);
535 }
536 
537 ////////////////////////////////////////////////////////////////////
538 // Function: GtkStatsPianoRoll::expose_event_callback
539 // Access: Private, Static
540 // Description: Draws in the scale labels.
541 ////////////////////////////////////////////////////////////////////
542 gboolean GtkStatsPianoRoll::
543 expose_event_callback(GtkWidget *widget, GdkEventExpose *event, gpointer data) {
544  GtkStatsPianoRoll *self = (GtkStatsPianoRoll *)data;
545  self->draw_guide_labels();
546 
547  return TRUE;
548 }
int get_xsize() const
Returns the width of the chart in pixels.
Definition: pStatGraph.I:103
virtual void set_time_units(int unit_mask)
Called when the user selects a new time units from the monitor pulldown menu, this should adjust the ...
int get_guide_bar_units() const
Returns the units that are displayed for the guide bar labels.
Definition: pStatGraph.I:140
void set_guide_bar_units(int unit_mask)
Sets the units that are displayed for the guide bar labels.
Definition: pStatGraph.I:125
int get_ysize() const
Returns the height of the chart in pixels.
Definition: pStatGraph.I:113
int get_num_labels() const
Returns the number of labels to be drawn for this chart.
Definition: pStatGraph.I:33
This is an abstract class that presents the interface for drawing a piano-roll type chart: it shows t...
void move_user_guide_bar(int n, double height)
Adjusts the height of the nth user-defined guide bar.
Definition: pStatGraph.cxx:137
The data associated with a particular client, but not with any one particular frame or thread: the li...
int find_user_guide_bar(double from_height, double to_height) const
Returns the index number of the first user guide bar found whose height is within the indicated range...
Definition: pStatGraph.cxx:172
void update()
Updates the chart with the latest data.
A window that draws a piano-roll style chart, which shows the collectors explicitly stopping and star...
const GuideBar & get_guide_bar(int n) const
Returns the nth horizontal guide bar.
Definition: pStatGraph.cxx:101
void set_horizontal_scale(double time_width)
Changes the amount of time the width of the horizontal axis represents.
int height_to_pixel(double value) const
Converts a value (i.e.
virtual void new_data(int thread_index, int frame_number)
Called as each frame&#39;s data is made available.
virtual void force_redraw()
Called when it is necessary to redraw the entire graph.
This is just an abstract base class to provide a common pointer type for the various kinds of graphs ...
Definition: gtkStatsGraph.h:32
int get_label_collector(int n) const
Returns the collector index associated with the nth label.
Definition: pStatGraph.I:44
int get_num_guide_bars() const
Returns the number of horizontal guide bars that should be drawn, based on the indicated target frame...
Definition: pStatGraph.cxx:86
This class represents a connection to a PStatsClient and manages the data exchange with the client...
void open_strip_chart(int thread_index, int collector_index, bool show_level)
Opens a new strip chart showing the indicated data.
void set_horizontal_scale(double time_width)
Changes the amount of time the width of the horizontal axis represents.
double pixel_to_height(int y) const
Converts a horizontal pixel offset to a value (a "height" in the strip chart).
const PStatClientData * get_client_data() const
Returns the client data associated with this monitor.
Definition: pStatMonitor.I:32
int add_user_guide_bar(double height)
Creates a new user guide bar and returns its index number.
Definition: pStatGraph.cxx:148
GuideBar get_user_guide_bar(int n) const
Returns the nth user-defined guide bar.
Definition: pStatGraph.cxx:126
virtual void clicked_label(int collector_index)
Called when the user single-clicks on a label.
virtual void changed_graph_size(int graph_xsize, int graph_ysize)
Called when the user has resized the window, forcing a resize of the graph.
int get_num_user_guide_bars() const
Returns the current number of user-defined guide bars.
Definition: pStatGraph.cxx:116
string get_thread_name(int index) const
Returns the name of the indicated thread.
void remove_user_guide_bar(int n)
Removes the user guide bar with the indicated index number.
Definition: pStatGraph.cxx:160