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