open_toontown_panda3d/pandatool/src/mac-stats/macStatsFlameGraph.mm

920 lines
27 KiB
Plaintext

/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file macStatsFlameGraph.mm
* @author rdb
* @date 2023-08-18
*/
#include "macStatsFlameGraph.h"
#include "macStatsMonitor.h"
#include "macStatsGraphView.h"
#include "macStatsScaleArea.h"
#include "pStatCollectorDef.h"
#include "cocoa_compat.h"
@interface MacStatsFlameGraphViewController : MacStatsGraphViewController
@end
static const int default_flame_graph_width = 800;
static const int default_flame_graph_height = 250;
/**
*
*/
MacStatsFlameGraph::
MacStatsFlameGraph(MacStatsMonitor *monitor, int thread_index,
int collector_index, int frame_number) :
PStatFlameGraph(monitor, thread_index, collector_index, frame_number, 0, 0),
MacStatsGraph(monitor, [MacStatsFlameGraphViewController alloc])
{
// Used for popup menus.
_menu_delegate = [[MacStatsChartMenuDelegate alloc] initWithMonitor:monitor threadIndex:thread_index];
// Set the initial size of the graph.
int height = default_flame_graph_height + _window.frame.size.height - _window.contentLayoutRect.size.height;
_graph_view.frame = NSMakeRect(0, 0, default_flame_graph_width, height);
_graph_view_controller.view.frame = NSMakeRect(0, 0, default_flame_graph_width, height);
_total_item = nil;
if (@available(macOS 11.0, *)) {
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@""];
toolbar.delegate = _graph_view_controller;
toolbar.displayMode = NSToolbarDisplayModeIconOnly;
_window.toolbar = toolbar;
[_window setToolbarStyle:NSWindowToolbarStyleUnifiedCompact];
for (NSToolbarItem *item in toolbar.items) {
if ([item.itemIdentifier isEqual:@"total"]) {
_total_item = item;
break;
}
}
[toolbar release];
}
//MacStatsScaleAreaController *scale_area_controller = [[MacStatsScaleAreaController alloc] initWithGraph:this];
//scale_area_controller.layoutAttribute = NSLayoutAttributeRight;
//_scale_area = scale_area_controller.view;
//[_window addTitlebarAccessoryViewController:scale_area_controller];
_window.contentViewController = _graph_view_controller;
_graph_view_controller.backToolbarItemVisible = NO;
// Let's show the units on the guide bar labels. There's room.
set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
if (get_average_mode()) {
start_animation();
}
if (is_title_unknown()) {
std::string window_title = get_title_text();
if (!is_title_unknown()) {
_window.title = [NSString stringWithUTF8String:window_title.c_str()];
}
}
if (@available(macOS 11.0, *)) {
std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name());
[_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]];
}
[_window makeKeyAndOrderFront:nil];
}
/**
*
*/
MacStatsFlameGraph::
~MacStatsFlameGraph() {
[_menu_delegate release];
}
/**
* Called whenever a new Collector definition is received from the client.
*/
void MacStatsFlameGraph::
new_collector(int collector_index) {
MacStatsGraph::new_collector(collector_index);
}
/**
* Called as each frame's data is made available. There is no guarantee the
* frames will arrive in order, or that all of them will arrive at all. The
* monitor should be prepared to accept frames received out-of-order or
* missing.
*/
void MacStatsFlameGraph::
new_data(int thread_index, int frame_number) {
if (is_title_unknown()) {
std::string window_title = get_title_text();
if (!is_title_unknown()) {
_window.title = [NSString stringWithUTF8String:window_title.c_str()];
}
}
if (!_pause) {
update();
if (@available(macOS 11.0, *)) {
std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name());
[_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]];
}
}
}
/**
* Called when it is necessary to redraw the entire graph.
*/
void MacStatsFlameGraph::
force_redraw() {
if (_ctx) {
PStatFlameGraph::force_redraw();
}
}
/**
* Called when the user has resized the window, forcing a resize of the graph.
*/
void MacStatsFlameGraph::
changed_graph_size(int graph_xsize, int graph_ysize) {
PStatFlameGraph::changed_size(graph_xsize, graph_ysize);
}
/**
* Called when the user selects a new time units from the monitor pulldown
* menu, this should adjust the units for the graph to the indicated mask if
* it is a time-based graph.
*/
void MacStatsFlameGraph::
set_time_units(int unit_mask) {
int old_unit_mask = get_guide_bar_units();
if ((old_unit_mask & (GBU_hz | GBU_ms)) != 0) {
unit_mask = unit_mask & (GBU_hz | GBU_ms);
unit_mask |= (old_unit_mask & GBU_show_units);
set_guide_bar_units(unit_mask);
if (@available(macOS 11.0, *)) {
std::string text = format_number(get_horizontal_scale(), get_guide_bar_units(), get_guide_bar_unit_name());
[_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]];
}
//_scale_area.needsDisplay = YES;
}
}
/**
* Called when the user single-clicks on a label.
*/
void MacStatsFlameGraph::
on_click_label(int collector_index) {
int current = get_collector_index();
if (collector_index != current) {
if (get_history_depth() == 0) {
_graph_view_controller.backToolbarItemVisible = YES;
}
push_collector_index(collector_index);
std::string window_title = get_title_text();
if (!is_title_unknown()) {
_window.title = [NSString stringWithUTF8String:window_title.c_str()];
}
}
}
/**
* Called when the user hovers the mouse over a label.
*/
void MacStatsFlameGraph::
on_enter_label(int collector_index) {
if (collector_index != _highlighted_index) {
_highlighted_index = collector_index;
if (!get_average_mode()) {
PStatFlameGraph::force_redraw();
}
}
}
/**
* Called when the user's mouse cursor leaves a label.
*/
void MacStatsFlameGraph::
on_leave_label(int collector_index) {
if (collector_index == _highlighted_index && collector_index != -1) {
_highlighted_index = -1;
if (!get_average_mode()) {
PStatFlameGraph::force_redraw();
}
}
}
/**
* Called when the mouse right-clicks on a label, and should return the menu
* that should pop up.
*/
NSMenu *MacStatsFlameGraph::
get_label_menu(int collector_index) const {
NSMenu *menu = [[[NSMenu alloc] init] autorelease];
std::string label = get_label_tooltip(collector_index);
if (!label.empty()) {
if (@available(macOS 14.0, *)) {
[menu addItem:[NSMenuItem sectionHeaderWithTitle:[NSString stringWithUTF8String:label.c_str()]]];
} else {
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label.c_str()] action:nil keyEquivalent:@""];
item.enabled = NO;
[menu addItem:item];
[item release];
}
}
{
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Set as Focus" action:@selector(handleSetAsFocus:) keyEquivalent:@""];
item.target = _graph_view_controller;
item.tag = collector_index;
item.enabled = (collector_index != 0 || get_collector_index() != 0);
[menu addItem:item];
[item release];
}
{
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Strip Chart" action:@selector(handleOpenStripChart:) keyEquivalent:@""];
item.target = _menu_delegate;
item.tag = collector_index;
[menu addItem:item];
[item release];
}
{
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Flame Graph" action:@selector(handleOpenFlameGraph:) keyEquivalent:@""];
item.target = _menu_delegate;
item.tag = collector_index;
[menu addItem:item];
[item release];
}
[menu addItem:[NSMenuItem separatorItem]];
{
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Change Color\u2026" action:@selector(handleChangeColor:) keyEquivalent:@""];
item.target = _graph_view_controller;
item.tag = collector_index;
[menu addItem:item];
[item release];
}
{
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Reset Color" action:@selector(handleResetColor:) keyEquivalent:@""];
item.target = _graph_view_controller;
item.tag = collector_index;
[menu addItem:item];
[item release];
}
return menu;
}
/**
* Calls update_guide_bars with parameters suitable to this kind of graph.
*/
void MacStatsFlameGraph::
normal_guide_bars() {
// We want vaguely 100 pixels between guide bars.
int num_bars = get_xsize() / 100;
_guide_bars.clear();
double dist = get_horizontal_scale() / num_bars;
for (int i = 1; i < num_bars; ++i) {
_guide_bars.push_back(make_guide_bar(i * dist));
}
_guide_bars_changed = true;
//nassertv_always(_scale_area != nullptr);
//_scale_area.needsDisplay = YES;
}
/**
* Erases the chart area.
*/
void MacStatsFlameGraph::
clear_region() {
if (_ctx) {
CGContextSetFillColorWithColor(_ctx, _background_color);
CGContextFillRect(_ctx, CGRectMake(0, 0, get_xsize(), get_ysize()));
}
}
/**
* Erases the chart area in preparation for drawing a bunch of bars.
*/
void MacStatsFlameGraph::
begin_draw() {
if (!_ctx) {
return;
}
clear_region();
// isFlipped is true in the NSView, so flip the text again
CGContextSetTextMatrix(_ctx, CGAffineTransformMakeScale(1, -1));
// Draw in the guide bars.
int num_guide_bars = get_num_guide_bars();
for (int i = 0; i < num_guide_bars; i++) {
const GuideBar &bar = get_guide_bar(i);
draw_guide_bar(_ctx, bar);
draw_guide_label(_ctx, bar);
}
}
/**
* Should be overridden by the user class. Should draw a single bar at the
* indicated location.
*/
void MacStatsFlameGraph::
draw_bar(int depth, int from_x, int to_x, int collector_index, int parent_index) {
double bottom = get_ysize() - depth * 4.000 * 5;
double top = bottom - 4.000 * 5;
top += 1;
MacStatsMonitor *monitor = MacStatsGraph::_monitor;
bool is_highlighted = collector_index == _highlighted_index;
CGContextSetFillColorWithColor(_ctx,
monitor->get_collector_color(collector_index, is_highlighted));
if (to_x < from_x + 3) {
// It's just a tiny sliver. This is a more reliable way to draw it.
CGRect rect = CGRectMake(from_x, top, to_x - from_x, bottom - top);
CGContextFillRect(_ctx, rect);
}
else {
double radius = std::min((double)4.000, (to_x - from_x) / 2.0);
CGContextBeginPath(_ctx);
CGContextAddArc(_ctx, to_x - radius, top + radius, radius, -0.5 * M_PI, 0.0, NO);
CGContextAddArc(_ctx, to_x - radius, bottom - radius, radius, 0.0, 0.5 * M_PI, NO);
CGContextAddArc(_ctx, from_x + radius, bottom - radius, radius, 0.5 * M_PI, M_PI, NO);
CGContextAddArc(_ctx, from_x + radius, top + radius, radius, M_PI, 1.5 * M_PI, NO);
CGContextClosePath(_ctx);
CGContextFillPath(_ctx);
if ((to_x - from_x) >= 4.000 * 4) {
// Only bother drawing the text if we've got some space to draw on.
int left = std::max(from_x, 0) + 4.000 / 2;
int right = std::min(to_x, get_xsize()) - 4.000 / 2;
const PStatClientData *client_data = monitor->get_client_data();
const PStatCollectorDef &def = client_data->get_collector_def(collector_index);
const CFStringRef keys[] = {
(__bridge CFStringRef)NSForegroundColorAttributeName,
(__bridge CFStringRef)NSFontAttributeName,
};
const void *values[] = {
monitor->get_collector_text_color(collector_index, is_highlighted),
[NSFont systemFontOfSize:0.0],
};
CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, def._name.c_str(), kCFStringEncodingUTF8);
CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs);
CTLineRef line = CTLineCreateWithAttributedString(astr);
CGRect bounds = CTLineGetImageBounds(line, _ctx);
CFRelease(astr);
CFRelease(str);
if (bounds.size.width < right - left) {
// We have room for more. Show the collector's actual parent, if it's
// different than the block it's shown above.
if (def._parent_index > 0 && def._parent_index != parent_index) {
const PStatCollectorDef &parent_def = client_data->get_collector_def(def._parent_index);
std::string long_name = parent_def._name + ":" + def._name;
CFStringRef long_str = CFStringCreateWithCString(kCFAllocatorDefault, long_name.c_str(), kCFStringEncodingUTF8);
CFAttributedStringRef long_astr = CFAttributedStringCreate(kCFAllocatorDefault, long_str, attribs);
CTLineRef long_line = CTLineCreateWithAttributedString((CFAttributedStringRef)long_astr);
CGRect long_bounds = CTLineGetImageBounds(long_line, _ctx);
if (long_bounds.size.width < right - left) {
CFRelease(line);
line = long_line;
bounds = long_bounds;
} else {
CFRelease(long_line);
}
CFRelease(long_astr);
CFRelease(long_str);
}
}
else {
static CFStringRef token_str = CFSTR("\u2026");
CFAttributedStringRef token_astr = CFAttributedStringCreate(kCFAllocatorDefault, token_str, attribs);
CTLineRef token_line = CTLineCreateWithAttributedString(token_astr);
CTLineRef trunc_line = CTLineCreateTruncatedLine(line, right - left, kCTLineTruncationEnd, token_line);
CFRelease(line);
CFRelease(token_astr);
CFRelease(token_line);
line = trunc_line;
}
// Center the text vertically in the bar.
if (line != nullptr) {
CGContextSetTextPosition(_ctx, left, top + (bottom - top + bounds.size.height) / 2);
CTLineDraw(line, _ctx);
CFRelease(line);
}
CFRelease(attribs);
}
}
}
/**
* Called after all the bars have been drawn, this triggers a refresh event to
* draw it to the window.
*/
void MacStatsFlameGraph::
end_draw() {
_graph_view.needsDisplay = YES;
}
/**
* Called at the end of the draw cycle.
*/
void MacStatsFlameGraph::
idle() {
}
/**
* Overridden by a derived class to implement an animation. If it returns
* false, the animation timer is stopped.
*/
bool MacStatsFlameGraph::
animate(double time, double dt) {
return PStatFlameGraph::animate(time, dt);
}
/**
* Returns the current window dimensions.
*/
bool MacStatsFlameGraph::
get_window_state(int &x, int &y, int &width, int &height,
bool &maximized, bool &minimized) const {
MacStatsGraph::get_window_state(x, y, width, height, maximized, minimized);
return true;
}
/**
* Called to restore the graph window to its previous dimensions.
*/
void MacStatsFlameGraph::
set_window_state(int x, int y, int width, int height,
bool maximized, bool minimized) {
MacStatsGraph::set_window_state(x, y, width, height, maximized, minimized);
}
/**
* Called when the mouse right-clicks on the graph, and should return the menu
* that should pop up.
*/
NSMenu *MacStatsFlameGraph::
get_graph_menu(int mouse_x, int mouse_y) const {
int collector_index = get_bar_collector(pixel_to_depth(mouse_y), mouse_x);
if (collector_index != -1) {
return get_label_menu(collector_index);
}
return nil;
}
/**
* Called when the mouse hovers over the graph, and should return the text that
* should appear on the tooltip.
*/
std::string MacStatsFlameGraph::
get_graph_tooltip(int mouse_x, int mouse_y) const {
return get_bar_tooltip(pixel_to_depth(mouse_y), mouse_x);
}
/**
* Based on the mouse position within the window's client area, look for
* draggable things the mouse might be hovering over and return the
* apprioprate DragMode enum or DM_none if nothing is indicated.
*/
MacStatsGraph::DragMode MacStatsFlameGraph::
consider_drag_start(int graph_x, int graph_y) {
if (graph_y >= 0 && graph_y < get_ysize()) {
if (graph_x >= 0 && graph_x < get_xsize()) {
// See if the mouse is over a user-defined guide bar.
int x = graph_x;
double from_height = pixel_to_height(x - 2);
double to_height = pixel_to_height(x + 2);
_drag_guide_bar = find_user_guide_bar(from_height, to_height);
if (_drag_guide_bar >= 0) {
return DM_guide_bar;
}
} else {
// The mouse is left or right of the graph; maybe create a new guide
// bar.
return DM_new_guide_bar;
}
}
return DM_none;
}
/**
*
*/
bool MacStatsFlameGraph::
handle_key(int graph_x, int graph_y, bool pressed, UniChar c, unsigned short key_code) {
bool changed = false;
if (pressed) {
switch (c) {
case NSLeftArrowFunctionKey:
changed = prev_frame();
break;
case NSRightArrowFunctionKey:
changed = next_frame();
break;
case NSHomeFunctionKey:
changed = first_frame();
break;
case NSEndFunctionKey:
changed = last_frame();
break;
}
}
if (changed) {
std::string window_title = get_title_text();
if (!is_title_unknown()) {
_window.title = [NSString stringWithUTF8String:window_title.c_str()];
}
}
return changed;
}
/**
* Called when the mouse button is depressed within the graph window.
*/
void MacStatsFlameGraph::
handle_button_press(int graph_x, int graph_y, bool double_click, int button) {
if (graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) {
int depth = pixel_to_depth(graph_y);
int collector_index = get_bar_collector(depth, graph_x);
if (double_click && button == 0) {
// Double-clicking on a color bar in the graph will zoom the graph into
// that collector.
if (collector_index >= 0) {
on_click_label(collector_index);
} else {
if (get_history_depth() > 0) {
clear_history();
_graph_view_controller.backToolbarItemVisible = NO;
}
set_collector_index(-1);
std::string window_title = get_title_text();
if (!is_title_unknown()) {
_window.title = [NSString stringWithUTF8String:window_title.c_str()];
}
}
return;
}
}
if (_potential_drag_mode == DM_none) {
set_drag_mode(DM_scale);
_drag_scale_start = pixel_to_height(graph_x);
// SetCapture(_graph_window);
return;
}
else if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) {
set_drag_mode(DM_guide_bar);
_drag_start_x = graph_x;
// SetCapture(_graph_window);
return;
}
return MacStatsGraph::handle_button_press(graph_x, graph_y,
double_click, button);
}
/**
* Called when the mouse button is released within the graph window.
*/
void MacStatsFlameGraph::
handle_button_release(int graph_x, int graph_y) {
if (_drag_mode == DM_scale) {
set_drag_mode(DM_none);
// ReleaseCapture();
return handle_motion(graph_x, graph_y);
}
else if (_drag_mode == DM_guide_bar) {
if (graph_x < 0 || graph_x >= get_xsize()) {
remove_user_guide_bar(_drag_guide_bar);
} else {
move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
}
set_drag_mode(DM_none);
// ReleaseCapture();
return handle_motion(graph_x, graph_y);
}
return MacStatsGraph::handle_button_release(graph_x, graph_y);
}
/**
* Called when the mouse is moved within the graph window.
*/
void MacStatsFlameGraph::
handle_motion(int graph_x, int graph_y) {
if (_drag_mode == DM_none && _potential_drag_mode == DM_none &&
graph_x >= 0 && graph_y >= 0 && graph_x < get_xsize() && graph_y < get_ysize()) {
// When the mouse is over a color bar, highlight it.
int depth = pixel_to_depth(graph_y);
int collector_index = get_bar_collector(depth, graph_x);
on_enter_label(collector_index);
}
else {
// If the mouse is in some drag mode, stop highlighting.
_label_stack.highlight_label(-1);
on_leave_label(_highlighted_index);
}
if (_drag_mode == DM_new_guide_bar) {
// We haven't created the new guide bar yet; we won't until the mouse
// comes within the graph's region.
if (graph_x >= 0 && graph_x < get_xsize()) {
set_drag_mode(DM_guide_bar);
_drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_x));
return;
}
}
else if (_drag_mode == DM_guide_bar) {
move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_x));
return;
}
return MacStatsGraph::handle_motion(graph_x, graph_y);
}
/**
* Called when the mouse has left the graph window.
*/
void MacStatsFlameGraph::
handle_leave() {
_label_stack.highlight_label(-1);
on_leave_label(_highlighted_index);
return;
}
/**
*
*/
void MacStatsFlameGraph::
handle_wheel(int graph_x, int graph_y, double dx, double dy) {
if (dx != 0.0) {
if ((dx > 0.0) ? prev_frame() : next_frame()) {
std::string window_title = get_title_text();
if (!is_title_unknown()) {
_window.title = [NSString stringWithUTF8String:window_title.c_str()];
}
}
}
}
/**
* Fills in the graph window.
*/
void MacStatsFlameGraph::
handle_draw_graph(CGContextRef ctx, NSRect rect) {
MacStatsGraph::handle_draw_graph(ctx, rect);
CGContextSetTextMatrix(ctx, CGAffineTransformMakeScale(1, -1));
int num_user_guide_bars = get_num_user_guide_bars();
for (int i = 0; i < num_user_guide_bars; i++) {
const GuideBar &bar = get_user_guide_bar(i);
draw_guide_bar(ctx, bar);
draw_guide_label(ctx, bar);
}
}
/**
* Called when the mouse clicks the back button in the toolbar.
*/
void MacStatsFlameGraph::
handle_back() {
if (pop_collector_index()) {
if (get_history_depth() == 0) {
_graph_view_controller.backToolbarItemVisible = NO;
}
std::string window_title = get_title_text();
if (!is_title_unknown()) {
_window.title = [NSString stringWithUTF8String:window_title.c_str()];
}
}
}
/**
* Called when the mouse toggles the "Average" checkbox in the toolbar.
*/
void MacStatsFlameGraph::
handle_toggle_average(bool state) {
set_average_mode(state);
if (state) {
start_animation();
}
}
/**
* Converts a pixel to a depth index.
*/
int MacStatsFlameGraph::
pixel_to_depth(int y) const {
return (get_ysize() - 1 - y) / (4.000 * 5);
}
/**
* Draws the line for the indicated guide bar on the graph.
*/
void MacStatsFlameGraph::
draw_guide_bar(CGContextRef ctx, const PStatGraph::GuideBar &bar) {
int x = height_to_pixel(bar._height);
if (x > 0 && x < get_xsize() - 1) {
// Only draw it if it's not too close to the top.
CGContextSetStrokeColorWithColor(ctx, [NSColor gridColor].CGColor);
/*switch (bar._style) {
case GBS_target:
CGContextSetRGBStrokeColor(ctx, rgb_light_gray[0], rgb_light_gray[1], rgb_light_gray[2], 1.0);
break;
case GBS_user:
CGContextSetRGBStrokeColor(ctx, rgb_user_guide_bar[0], rgb_user_guide_bar[1], rgb_user_guide_bar[2], 1.0);
break;
default:
CGContextSetRGBStrokeColor(ctx, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2], 1.0);
break;
}*/
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, x, 0);
CGContextAddLineToPoint(ctx, x, get_ysize());
CGContextStrokePath(ctx);
}
}
/**
* This is called during the servicing of the draw event.
*/
void MacStatsFlameGraph::
draw_guide_labels(CGContextRef ctx) {
int num_guide_bars = get_num_guide_bars();
for (int i = 0; i < num_guide_bars; i++) {
draw_guide_label(ctx, get_guide_bar(i));
}
int num_user_guide_bars = get_num_user_guide_bars();
for (int i = 0; i < num_user_guide_bars; i++) {
draw_guide_label(ctx, get_user_guide_bar(i));
}
}
/**
* Draws the text for the indicated guide bar label at the top of the graph.
*/
void MacStatsFlameGraph::
draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar) {
NSColor *color;
color = [NSColor tertiaryLabelColor];
/*switch (bar._style) {
case GBS_target:
color = [NSColor colorWithDeviceRed:rgb_light_gray[0] green:rgb_light_gray[1] blue:rgb_light_gray[2] alpha:1.0];
break;
case GBS_user:
color = [NSColor colorWithDeviceRed:rgb_user_guide_bar[0] green:rgb_user_guide_bar[1] blue:rgb_user_guide_bar[2] alpha:1.0];
break;
default:
color = [NSColor colorWithDeviceRed:rgb_dark_gray[0] green:rgb_dark_gray[1] blue:rgb_dark_gray[2] alpha:1.0];
break;
}*/
int x = height_to_pixel(bar._height);
const std::string &label = bar._label;
const CFStringRef keys[] = {
(__bridge CFStringRef)NSForegroundColorAttributeName,
(__bridge CFStringRef)NSFontAttributeName,
};
const void *values[] = {
color,
[NSFont systemFontOfSize:0.0],
};
CFDictionaryRef attribs = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, label.c_str(), kCFStringEncodingUTF8);
CFAttributedStringRef astr = CFAttributedStringCreate(kCFAllocatorDefault, str, attribs);
CFRelease(attribs);
CFRelease(str);
CTLineRef line = CTLineCreateWithAttributedString(astr);
CFRelease(astr);
CGRect bounds = CTLineGetImageBounds(line, ctx);
int width = bounds.size.width;
if (bar._style != GBS_user) {
double from_height = pixel_to_height(x - width);
double to_height = pixel_to_height(x + width);
if (find_user_guide_bar(from_height, to_height) >= 0) {
// Omit the label: there's a user-defined guide bar in the same space.
CFRelease(line);
return;
}
}
if (x >= 0 && x < get_xsize()) {
int y = bounds.size.height;
if (@available(macOS 11.0, *)) {
// Account for underlap of title bar
y += _window.frame.size.height - _window.contentLayoutRect.size.height;
}
CGContextSetTextPosition(ctx, x + 6, y + 6);
CTLineDraw(line, ctx);
}
CFRelease(line);
}
@implementation MacStatsFlameGraphViewController
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
return @[@"back", @"average", @"total"];
}
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
return @[@"average", @"total"];
}
- (NSToolbarItem *) toolbar:(NSToolbar *)toolbar
itemForItemIdentifier:(NSString *)ident
willBeInsertedIntoToolbar:(BOOL)flag {
if (@available(macOS 11.0, *)) {
if ([ident isEqual:@"average"]) {
NSButton *button = [NSButton buttonWithTitle:@"Average" target:self action:@selector(handleToggleAverage:)];
button.image = [NSImage imageWithSystemSymbolName:@"sum" accessibilityDescription:@""];
button.bezelStyle = NSBezelStyleTexturedRounded;
button.buttonType = NSButtonTypePushOnPushOff;
button.bordered = YES;
button.toolTip = @"Average";
button.state = NSOffState;
NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease];
item.label = @"Average";
item.view = button;
return item;
}
if ([ident isEqual:@"total"]) {
NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease];
item.label = @"Total";
item.enabled = NO;
[item setBordered:YES];
return item;
}
}
return [super toolbar:toolbar itemForItemIdentifier:ident willBeInsertedIntoToolbar:flag];
}
- (void)handleToggleAverage:(NSButton *)button {
MacStatsFlameGraph *graph = (MacStatsFlameGraph *)_graph;
graph->handle_toggle_average(button.state == NSOnState);
}
@end