Panda3D
Loading...
Searching...
No Matches
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
18static const int default_piano_roll_width = 400;
19static const int default_piano_roll_height = 200;
20
21bool WinStatsPianoRoll::_window_class_registered = false;
22const char * const WinStatsPianoRoll::_window_class_name = "piano";
23
24/**
25 *
26 */
27WinStatsPianoRoll::
28WinStatsPianoRoll(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 */
49WinStatsPianoRoll::
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 */
60new_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 */
78changed_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 */
88set_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 */
106clicked_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 */
117set_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 */
129void WinStatsPianoRoll::
130clear_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 */
138void WinStatsPianoRoll::
139begin_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 */
152void WinStatsPianoRoll::
153draw_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 */
172void WinStatsPianoRoll::
173end_draw() {
174 InvalidateRect(_graph_window, nullptr, FALSE);
175}
176
177/**
178 * Called at the end of the draw cycle.
179 */
180void WinStatsPianoRoll::
181idle() {
182 if (_labels_changed) {
183 update_labels();
184 }
185}
186
187/**
188 *
189 */
190LONG WinStatsPianoRoll::
191window_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 */
211LONG WinStatsPianoRoll::
212graph_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 */
325void WinStatsPianoRoll::
326additional_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 */
352void WinStatsPianoRoll::
353additional_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 */
365WinStatsGraph::DragMode WinStatsPianoRoll::
366consider_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 */
393int WinStatsPianoRoll::
394get_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 */
412void WinStatsPianoRoll::
413update_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 */
427void WinStatsPianoRoll::
428draw_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 */
454void WinStatsPianoRoll::
455draw_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 */
494void WinStatsPianoRoll::
495create_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 */
541void WinStatsPianoRoll::
542register_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 */
572LONG WINAPI WinStatsPianoRoll::
573static_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.
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...
int get_num_user_guide_bars() const
Returns the current number of user-defined guide bars.
int add_user_guide_bar(double height)
Creates a new user guide bar and returns its index number.
void move_user_guide_bar(int n, double height)
Adjusts the height of the nth user-defined guide bar.
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.
int get_num_guide_bars() const
Returns the number of horizontal guide bars that should be drawn, based on the indicated target frame...
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.
const PStatClientData * get_client_data() const
Returns the client data associated with this monitor.
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 ...
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.