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  */
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 }
virtual void clicked_label(int collector_index)
Called when the user single-clicks on a label.
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 ...
void set_horizontal_scale(double time_width)
Changes the amount of time the width of the horizontal axis represents.
The data associated with a particular client, but not with any one particular frame or thread: the li...
void open_strip_chart(int thread_index, int collector_index, bool show_level)
Opens a new strip chart showing the indicated data.
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.
This is just an abstract base class to provide a common pointer type for the various kinds of graphs ...
Definition: winStatsGraph.h:29
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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.
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 ...
This class represents a connection to a PStatsClient and manages the data exchange with the client.
A window that draws a piano-roll style chart, which shows the collectors explicitly stopping and star...
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
HWND get_window() const
Returns the window handle to the monitor's window.
virtual void clicked_label(int collector_index)
Called when the user single-clicks on a label.
std::string get_thread_name(int index) const
Returns the name of the indicated thread.