Panda3D
winStatsPianoRoll.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 winStatsPianoRoll.cxx
10  * @author drose
11  * @date 2003-12-03
12  */
13 
14 #include "winStatsPianoRoll.h"
15 #include "winStatsMonitor.h"
16 #include "numeric_types.h"
17 
18 static const int default_piano_roll_width = 400;
19 static const int default_piano_roll_height = 200;
20 
21 bool WinStatsPianoRoll::_window_class_registered = false;
22 const char * const WinStatsPianoRoll::_window_class_name = "piano";
23 
24 /**
25  *
26  */
27 WinStatsPianoRoll::
28 WinStatsPianoRoll(WinStatsMonitor *monitor, int thread_index) :
29  PStatPianoRoll(monitor, thread_index,
30  default_piano_roll_width,
31  default_piano_roll_height),
32  WinStatsGraph(monitor)
33 {
34  _left_margin = 128;
35  _right_margin = 8;
36  _top_margin = 16;
37  _bottom_margin = 8;
38 
39  // Let's show the units on the guide bar labels. There's room.
40  set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
41 
42  create_window();
43  clear_region();
44 }
45 
46 /**
47  *
48  */
49 WinStatsPianoRoll::
50 ~WinStatsPianoRoll() {
51 }
52 
53 /**
54  * Called as each frame's data is made available. There is no gurantee the
55  * frames will arrive in order, or that all of them will arrive at all. The
56  * monitor should be prepared to accept frames received out-of-order or
57  * missing.
58  */
60 new_data(int thread_index, int frame_number) {
61  if (!_pause) {
62  update();
63  }
64 }
65 
66 /**
67  * Called when it is necessary to redraw the entire graph.
68  */
70 force_redraw() {
71  PStatPianoRoll::force_redraw();
72 }
73 
74 /**
75  * Called when the user has resized the window, forcing a resize of the graph.
76  */
78 changed_graph_size(int graph_xsize, int graph_ysize) {
79  PStatPianoRoll::changed_size(graph_xsize, graph_ysize);
80 }
81 
82 /**
83  * Called when the user selects a new time units from the monitor pulldown
84  * menu, this should adjust the units for the graph to the indicated mask if
85  * it is a time-based graph.
86  */
88 set_time_units(int unit_mask) {
89  int old_unit_mask = get_guide_bar_units();
90  if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) {
91  unit_mask = unit_mask & (GBU_hz | GBU_ms);
92  unit_mask |= (old_unit_mask & GBU_show_units);
93  set_guide_bar_units(unit_mask);
94 
95  RECT rect;
96  GetClientRect(_window, &rect);
97  rect.left = _right_margin;
98  InvalidateRect(_window, &rect, TRUE);
99  }
100 }
101 
102 /**
103  * Called when the user single-clicks on a label.
104  */
106 clicked_label(int collector_index) {
107  if (collector_index >= 0) {
108  WinStatsGraph::_monitor->open_strip_chart(_thread_index, collector_index, false);
109  }
110 }
111 
112 /**
113  * Changes the amount of time the width of the horizontal axis represents.
114  * This may force a redraw.
115  */
117 set_horizontal_scale(double time_width) {
119 
120  RECT rect;
121  GetClientRect(_window, &rect);
122  rect.bottom = _top_margin;
123  InvalidateRect(_window, &rect, TRUE);
124 }
125 
126 /**
127  * Erases the chart area.
128  */
129 void WinStatsPianoRoll::
130 clear_region() {
131  RECT rect = { 0, 0, get_xsize(), get_ysize() };
132  FillRect(_bitmap_dc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
133 }
134 
135 /**
136  * Erases the chart area in preparation for drawing a bunch of bars.
137  */
138 void WinStatsPianoRoll::
139 begin_draw() {
140  clear_region();
141 
142  // Draw in the guide bars.
143  int num_guide_bars = get_num_guide_bars();
144  for (int i = 0; i < num_guide_bars; i++) {
145  draw_guide_bar(_bitmap_dc, get_guide_bar(i));
146  }
147 }
148 
149 /**
150  * Draws a single bar on the chart.
151  */
152 void WinStatsPianoRoll::
153 draw_bar(int row, int from_x, int to_x) {
154  if (row >= 0 && row < _label_stack.get_num_labels()) {
155  int y = _label_stack.get_label_y(row) - _graph_top;
156  int height = _label_stack.get_label_height(row);
157 
158  RECT rect = {
159  from_x, y - height + 2,
160  to_x, y - 2,
161  };
162  int collector_index = get_label_collector(row);
163  HBRUSH brush = get_collector_brush(collector_index);
164  FillRect(_bitmap_dc, &rect, brush);
165  }
166 }
167 
168 /**
169  * Called after all the bars have been drawn, this triggers a refresh event to
170  * draw it to the window.
171  */
172 void WinStatsPianoRoll::
173 end_draw() {
174  InvalidateRect(_graph_window, nullptr, FALSE);
175 }
176 
177 /**
178  * Called at the end of the draw cycle.
179  */
180 void WinStatsPianoRoll::
181 idle() {
182  if (_labels_changed) {
183  update_labels();
184  }
185 }
186 
187 /**
188  *
189  */
190 LONG WinStatsPianoRoll::
191 window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
192  switch (msg) {
193  case WM_LBUTTONDOWN:
194  if (_potential_drag_mode == DM_new_guide_bar) {
195  set_drag_mode(DM_new_guide_bar);
196  SetCapture(_graph_window);
197  return 0;
198  }
199  break;
200 
201  default:
202  break;
203  }
204 
205  return WinStatsGraph::window_proc(hwnd, msg, wparam, lparam);
206 }
207 
208 /**
209  *
210  */
211 LONG WinStatsPianoRoll::
212 graph_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
213  switch (msg) {
214  case WM_LBUTTONDOWN:
215  if (_potential_drag_mode == DM_none) {
216  set_drag_mode(DM_scale);
217  int16_t x = LOWORD(lparam);
218  _drag_scale_start = pixel_to_height(x);
219  SetCapture(_graph_window);
220  return 0;
221 
222  } else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) {
223  set_drag_mode(DM_guide_bar);
224  int16_t x = LOWORD(lparam);
225  _drag_start_x = x;
226  SetCapture(_graph_window);
227  return 0;
228  }
229  break;
230 
231  case WM_MOUSEMOVE:
232  if (_drag_mode == DM_none && _potential_drag_mode == DM_none) {
233  // When the mouse is over a color bar, highlight it.
234  int16_t x = LOWORD(lparam);
235  int16_t y = HIWORD(lparam);
236  _label_stack.highlight_label(get_collector_under_pixel(x, y));
237 
238  // Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph
239  // window.
240  TRACKMOUSEEVENT tme = {
241  sizeof(TRACKMOUSEEVENT),
242  TME_LEAVE,
243  _graph_window,
244  0
245  };
246  TrackMouseEvent(&tme);
247 
248  } else {
249  // If the mouse is in some drag mode, stop highlighting.
250  _label_stack.highlight_label(-1);
251  }
252 
253  if (_drag_mode == DM_scale) {
254  int16_t x = LOWORD(lparam);
255  double ratio = (double)x / (double)get_xsize();
256  if (ratio > 0.0f) {
257  set_horizontal_scale(_drag_scale_start / ratio);
258  }
259  return 0;
260 
261  } else if (_drag_mode == DM_new_guide_bar) {
262  // We haven't created the new guide bar yet; we won't until the mouse
263  // comes within the graph's region.
264  int16_t x = LOWORD(lparam);
265  if (x >= 0 && x < get_xsize()) {
266  set_drag_mode(DM_guide_bar);
267  _drag_guide_bar = add_user_guide_bar(pixel_to_height(x));
268  return 0;
269  }
270 
271  } else if (_drag_mode == DM_guide_bar) {
272  int16_t x = LOWORD(lparam);
273  move_user_guide_bar(_drag_guide_bar, pixel_to_height(x));
274  return 0;
275  }
276  break;
277 
278  case WM_MOUSELEAVE:
279  // When the mouse leaves the graph, stop highlighting.
280  _label_stack.highlight_label(-1);
281  break;
282 
283  case WM_LBUTTONUP:
284  if (_drag_mode == DM_scale) {
285  set_drag_mode(DM_none);
286  ReleaseCapture();
287  return 0;
288 
289  } else if (_drag_mode == DM_guide_bar) {
290  int16_t x = LOWORD(lparam);
291  if (x < 0 || x >= get_xsize()) {
292  remove_user_guide_bar(_drag_guide_bar);
293  } else {
294  move_user_guide_bar(_drag_guide_bar, pixel_to_height(x));
295  }
296  set_drag_mode(DM_none);
297  ReleaseCapture();
298  return 0;
299  }
300  break;
301 
302  case WM_LBUTTONDBLCLK:
303  {
304  // Double-clicking on a color bar in the graph is the same as double-
305  // clicking on the corresponding label.
306  int16_t x = LOWORD(lparam);
307  int16_t y = HIWORD(lparam);
308  clicked_label(get_collector_under_pixel(x, y));
309  return 0;
310  }
311  break;
312 
313  default:
314  break;
315  }
316 
317  return WinStatsGraph::graph_window_proc(hwnd, msg, wparam, lparam);
318 }
319 
320 /**
321  * This is called during the servicing of WM_PAINT; it gives a derived class
322  * opportunity to do some further painting into the window (the outer window,
323  * not the graph window).
324  */
325 void WinStatsPianoRoll::
326 additional_window_paint(HDC hdc) {
327  // Draw in the labels for the guide bars.
328  HFONT hfnt = (HFONT)GetStockObject(ANSI_VAR_FONT);
329  SelectObject(hdc, hfnt);
330  SetTextAlign(hdc, TA_LEFT | TA_BOTTOM);
331  SetBkMode(hdc, TRANSPARENT);
332 
333  int y = _top_margin;
334 
335  int i;
336  int num_guide_bars = get_num_guide_bars();
337  for (i = 0; i < num_guide_bars; i++) {
338  draw_guide_label(hdc, y, get_guide_bar(i));
339  }
340 
341  int num_user_guide_bars = get_num_user_guide_bars();
342  for (i = 0; i < num_user_guide_bars; i++) {
343  draw_guide_label(hdc, y, get_user_guide_bar(i));
344  }
345 }
346 
347 /**
348  * This is called during the servicing of WM_PAINT; it gives a derived class
349  * opportunity to do some further painting into the window (the outer window,
350  * not the graph window).
351  */
352 void WinStatsPianoRoll::
353 additional_graph_window_paint(HDC hdc) {
354  int num_user_guide_bars = get_num_user_guide_bars();
355  for (int i = 0; i < num_user_guide_bars; i++) {
356  draw_guide_bar(hdc, get_user_guide_bar(i));
357  }
358 }
359 
360 /**
361  * Based on the mouse position within the window's client area, look for
362  * draggable things the mouse might be hovering over and return the
363  * apprioprate DragMode enum or DM_none if nothing is indicated.
364  */
365 WinStatsGraph::DragMode WinStatsPianoRoll::
366 consider_drag_start(int mouse_x, int mouse_y, int width, int height) {
367  if (mouse_y >= _graph_top && mouse_y < _graph_top + get_ysize()) {
368  if (mouse_x >= _graph_left && mouse_x < _graph_left + get_xsize()) {
369  // See if the mouse is over a user-defined guide bar.
370  int x = mouse_x - _graph_left;
371  double from_height = pixel_to_height(x - 2);
372  double to_height = pixel_to_height(x + 2);
373  _drag_guide_bar = find_user_guide_bar(from_height, to_height);
374  if (_drag_guide_bar >= 0) {
375  return DM_guide_bar;
376  }
377 
378  } else if (mouse_x < _left_margin - 2 ||
379  mouse_x > width - _right_margin + 2) {
380  // The mouse is left or right of the graph; maybe create a new guide
381  // bar.
382  return DM_new_guide_bar;
383  }
384  }
385 
386  return WinStatsGraph::consider_drag_start(mouse_x, mouse_y, width, height);
387 }
388 
389 /**
390  * Returns the collector index associated with the indicated vertical row, or
391  * -1.
392  */
393 int WinStatsPianoRoll::
394 get_collector_under_pixel(int xpoint, int ypoint) {
395  if (_label_stack.get_num_labels() == 0) {
396  return -1;
397  }
398 
399  // Assume all of the labels are the same height.
400  int height = _label_stack.get_label_height(0);
401  int row = (get_ysize() - ypoint) / height;
402  if (row >= 0 && row < _label_stack.get_num_labels()) {
403  return _label_stack.get_label_collector_index(row);
404  } else {
405  return -1;
406  }
407 }
408 
409 /**
410  * Resets the list of labels.
411  */
412 void WinStatsPianoRoll::
413 update_labels() {
414  _label_stack.clear_labels();
415  for (int i = 0; i < get_num_labels(); i++) {
416  int label_index =
417  _label_stack.add_label(WinStatsGraph::_monitor, this,
418  _thread_index,
419  get_label_collector(i), true);
420  }
421  _labels_changed = false;
422 }
423 
424 /**
425  * Draws the line for the indicated guide bar on the graph.
426  */
427 void WinStatsPianoRoll::
428 draw_guide_bar(HDC hdc, const PStatGraph::GuideBar &bar) {
429  int x = height_to_pixel(bar._height);
430 
431  if (x > 0 && x < get_xsize() - 1) {
432  // Only draw it if it's not too close to either edge.
433  switch (bar._style) {
434  case GBS_target:
435  SelectObject(hdc, _light_pen);
436  break;
437 
438  case GBS_user:
439  SelectObject(hdc, _user_guide_bar_pen);
440  break;
441 
442  case GBS_normal:
443  SelectObject(hdc, _dark_pen);
444  break;
445  }
446  MoveToEx(hdc, x, 0, nullptr);
447  LineTo(hdc, x, get_ysize());
448  }
449 }
450 
451 /**
452  * Draws the text for the indicated guide bar label at the top of the graph.
453  */
454 void WinStatsPianoRoll::
455 draw_guide_label(HDC hdc, int y, const PStatGraph::GuideBar &bar) {
456  switch (bar._style) {
457  case GBS_target:
458  SetTextColor(hdc, _light_color);
459  break;
460 
461  case GBS_user:
462  SetTextColor(hdc, _user_guide_bar_color);
463  break;
464 
465  case GBS_normal:
466  SetTextColor(hdc, _dark_color);
467  break;
468  }
469 
470  int x = height_to_pixel(bar._height);
471  const std::string &label = bar._label;
472  SIZE size;
473  GetTextExtentPoint32(hdc, label.data(), label.length(), &size);
474 
475  if (bar._style != GBS_user) {
476  double from_height = pixel_to_height(x - size.cx);
477  double to_height = pixel_to_height(x + size.cx);
478  if (find_user_guide_bar(from_height, to_height) >= 0) {
479  // Omit the label: there's a user-defined guide bar in the same space.
480  return;
481  }
482  }
483 
484  int this_x = _graph_left + x - size.cx / 2;
485  if (x >= 0 && x < get_xsize()) {
486  TextOut(hdc, this_x, y,
487  label.data(), label.length());
488  }
489 }
490 
491 /**
492  * Creates the window for this strip chart.
493  */
494 void WinStatsPianoRoll::
495 create_window() {
496  if (_window) {
497  return;
498  }
499 
500  HINSTANCE application = GetModuleHandle(nullptr);
501  register_window_class(application);
502 
503  const PStatClientData *client_data =
504  WinStatsGraph::_monitor->get_client_data();
505  std::string thread_name = client_data->get_thread_name(_thread_index);
506  std::string window_title = thread_name + " thread piano roll";
507 
508 
509  RECT win_rect = {
510  0, 0,
511  _left_margin + get_xsize() + _right_margin,
512  _top_margin + get_ysize() + _bottom_margin
513  };
514 
515  // compute window size based on desired client area size
516  AdjustWindowRect(&win_rect, graph_window_style, FALSE);
517 
518  _window =
519  CreateWindow(_window_class_name, window_title.c_str(), graph_window_style,
520  CW_USEDEFAULT, CW_USEDEFAULT,
521  win_rect.right - win_rect.left,
522  win_rect.bottom - win_rect.top,
523  WinStatsGraph::_monitor->get_window(), nullptr, application, 0);
524  if (!_window) {
525  nout << "Could not create PianoRoll window!\n";
526  exit(1);
527  }
528 
529  SetWindowLongPtr(_window, 0, (LONG_PTR)this);
530  setup_label_stack();
531 
532  // Ensure that the window is on top of the stack.
533  SetWindowPos(_window, HWND_TOP, 0, 0, 0, 0,
534  SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
535 }
536 
537 /**
538  * Registers the window class for the pianoRoll window, if it has not already
539  * been registered.
540  */
541 void WinStatsPianoRoll::
542 register_window_class(HINSTANCE application) {
543  if (_window_class_registered) {
544  return;
545  }
546 
547  WNDCLASS wc;
548 
549  ZeroMemory(&wc, sizeof(WNDCLASS));
550  wc.style = 0;
551  wc.lpfnWndProc = (WNDPROC)static_window_proc;
552  wc.hInstance = application;
553  wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
554  wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
555  wc.lpszMenuName = nullptr;
556  wc.lpszClassName = _window_class_name;
557 
558  // Reserve space to associate the this pointer with the window.
559  wc.cbWndExtra = sizeof(WinStatsPianoRoll *);
560 
561  if (!RegisterClass(&wc)) {
562  nout << "Could not register PianoRoll window class!\n";
563  exit(1);
564  }
565 
566  _window_class_registered = true;
567 }
568 
569 /**
570  *
571  */
572 LONG WINAPI WinStatsPianoRoll::
573 static_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
574  WinStatsPianoRoll *self = (WinStatsPianoRoll *)GetWindowLongPtr(hwnd, 0);
575  if (self != nullptr && self->_window == hwnd) {
576  return self->window_proc(hwnd, msg, wparam, lparam);
577  } else {
578  return DefWindowProc(hwnd, msg, wparam, lparam);
579  }
580 }
The data associated with a particular client, but not with any one particular frame or thread: the li...
std::string get_thread_name(int index) const
Returns the name of the indicated thread.
GuideBar get_user_guide_bar(int n) const
Returns the nth user-defined guide bar.
Definition: pStatGraph.cxx:108
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:144
int get_num_user_guide_bars() const
Returns the current number of user-defined guide bars.
Definition: pStatGraph.cxx:100
int add_user_guide_bar(double height)
Creates a new user guide bar and returns its index number.
Definition: pStatGraph.cxx:125
void move_user_guide_bar(int n, double height)
Adjusts the height of the nth user-defined guide bar.
Definition: pStatGraph.cxx:117
void set_guide_bar_units(int unit_mask)
Sets the units that are displayed for the guide bar labels.
Definition: pStatGraph.I:99
int get_guide_bar_units() const
Returns the units that are displayed for the guide bar labels.
Definition: pStatGraph.I:111
void remove_user_guide_bar(int n)
Removes the user guide bar with the indicated index number.
Definition: pStatGraph.cxx:134
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:75
int get_num_labels() const
Returns the number of labels to be drawn for this chart.
Definition: pStatGraph.I:26
int get_label_collector(int n) const
Returns the collector index associated with the nth label.
Definition: pStatGraph.I:34
int get_xsize() const
Returns the width of the chart in pixels.
Definition: pStatGraph.I:82
int get_ysize() const
Returns the height of the chart in pixels.
Definition: pStatGraph.I:90
const GuideBar & get_guide_bar(int n) const
Returns the nth horizontal guide bar.
Definition: pStatGraph.cxx:87
const PStatClientData * get_client_data() const
Returns the client data associated with this monitor.
Definition: pStatMonitor.I:26
This is an abstract class that presents the interface for drawing a piano- roll type chart: it shows ...
void set_horizontal_scale(double time_width)
Changes the amount of time the width of the horizontal axis represents.
void update()
Updates the chart with the latest data.
double pixel_to_height(int y) const
Converts a horizontal pixel offset to a value (a "height" in the strip chart).
int height_to_pixel(double value) const
Converts a value (i.e.
This is just an abstract base class to provide a common pointer type for the various kinds of graphs ...
Definition: winStatsGraph.h:32
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.
A window that draws a piano-roll style chart, which shows the collectors explicitly stopping and star...
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.
void set_horizontal_scale(double time_width)
Changes the amount of time the width of the horizontal axis represents.
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 ...
virtual void clicked_label(int collector_index)
Called when the user single-clicks on a label.
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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.