parent
aa13953f2b
commit
71154492b9
|
|
@ -1077,6 +1077,9 @@ if (COMPILER=="GCC"):
|
|||
LibName("COCOA", "-framework Cocoa")
|
||||
# Fix for a bug in OSX Leopard:
|
||||
LibName("GL", "-dylib_file /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib:/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib")
|
||||
# When using pre-11.0 SDKs, for PStats
|
||||
if os.path.basename(SDK["MACOSX"]).startswith("MacOSX10."):
|
||||
LibName("COCOA", "-Wl,-U,_OBJC_CLASS_$_NSTrackingSeparatorToolbarItem")
|
||||
|
||||
# Temporary exceptions to removal of this flag
|
||||
if not PkgSkip("FFMPEG"):
|
||||
|
|
@ -5911,10 +5914,13 @@ if not PkgSkip("PANDATOOL"):
|
|||
# DIRECTORY: pandatool/src/gtk-stats/
|
||||
#
|
||||
|
||||
if not PkgSkip("PANDATOOL") and (GetTarget() == 'windows' or not PkgSkip("GTK3")):
|
||||
if not PkgSkip("PANDATOOL") and (GetTarget() in ('windows', 'darwin') or not PkgSkip("GTK3")):
|
||||
if GetTarget() == 'windows':
|
||||
OPTS=['DIR:pandatool/src/win-stats']
|
||||
TargetAdd('pstats_composite1.obj', opts=OPTS, input='winstats_composite1.cxx')
|
||||
elif GetTarget() == 'darwin':
|
||||
OPTS=['DIR:pandatool/src/mac-stats']
|
||||
TargetAdd('pstats_composite1.obj', opts=OPTS, input='macstats_composite1.mm')
|
||||
else:
|
||||
OPTS=['DIR:pandatool/src/gtk-stats', 'GTK3']
|
||||
TargetAdd('pstats_composite1.obj', opts=OPTS, input='gtkstats_composite1.cxx')
|
||||
|
|
@ -5923,7 +5929,7 @@ if not PkgSkip("PANDATOOL") and (GetTarget() == 'windows' or not PkgSkip("GTK3")
|
|||
TargetAdd('pstats.exe', input='libp3progbase.lib')
|
||||
TargetAdd('pstats.exe', input='libp3pandatoolbase.lib')
|
||||
TargetAdd('pstats.exe', input=COMMON_PANDA_LIBS)
|
||||
TargetAdd('pstats.exe', opts=['SUBSYSTEM:WINDOWS', 'WINCOMCTL', 'WINCOMDLG', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'UXTHEME', 'GTK3'])
|
||||
TargetAdd('pstats.exe', opts=['SUBSYSTEM:WINDOWS', 'WINCOMCTL', 'WINCOMDLG', 'WINSOCK', 'WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM', 'UXTHEME', 'GTK3', 'COCOA', 'CARBON', 'QUARTZ'])
|
||||
|
||||
#
|
||||
# DIRECTORY: pandatool/src/xfileprogs/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.panda3d.pstats</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>pstats</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>PStats</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>PStats</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>LSHasLocalizedDisplayName</key>
|
||||
<false/>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<false/>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>PStats session file</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>pstats</string>
|
||||
</array>
|
||||
<key>CFBundleTypeMIMETypes</key>
|
||||
<array>
|
||||
<string>application/vnd.panda3d.pstats</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* 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 cocoa_compat.h
|
||||
* @author rdb
|
||||
* @date 2023-08-28
|
||||
*/
|
||||
|
||||
#ifndef COCOA_COMPAT_H
|
||||
#define COCOA_COMPAT_H
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
// Allow building with older SDKs.
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000
|
||||
typedef NS_ENUM(NSInteger, NSWindowToolbarStyle) {
|
||||
NSWindowToolbarStyleAutomatic,
|
||||
NSWindowToolbarStyleExpanded,
|
||||
NSWindowToolbarStylePreference,
|
||||
NSWindowToolbarStyleUnified,
|
||||
NSWindowToolbarStyleUnifiedCompact
|
||||
} API_AVAILABLE(macos(11.0));
|
||||
|
||||
API_AVAILABLE(macos(11.0)) API_UNAVAILABLE(ios)
|
||||
@interface NSTrackingSeparatorToolbarItem : NSToolbarItem
|
||||
+ (instancetype)trackingSeparatorToolbarItemWithIdentifier:(NSString *)identifier splitView:(NSSplitView *)splitView dividerIndex:(NSInteger)dividerIndex API_UNAVAILABLE(ios);
|
||||
@property (strong) NSSplitView *splitView API_UNAVAILABLE(ios);
|
||||
@property NSInteger dividerIndex API_UNAVAILABLE(ios);
|
||||
@end
|
||||
|
||||
#endif // __MAC_OS_X_VERSION_MAX_ALLOWED
|
||||
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101400
|
||||
@protocol NSViewToolTipOwner <NSObject>
|
||||
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(nullable void *)data;
|
||||
@end
|
||||
|
||||
#endif // __MAC_OS_X_VERSION_MAX_ALLOWED
|
||||
|
||||
#endif // COCOA_COMPAT_H
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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 macStats.h
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#ifndef MACSTATS_H
|
||||
#define MACSTATS_H
|
||||
|
||||
#include "pStatServer.h"
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* 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 macStats.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#include "pandatoolbase.h"
|
||||
#include "macStats.h"
|
||||
#include "macStatsServer.h"
|
||||
#include "config_pstatclient.h"
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <objc/runtime.h>
|
||||
|
||||
extern "C" {
|
||||
OSStatus CPSSetProcessName(ProcessSerialNumber *psn, char *name);
|
||||
};
|
||||
|
||||
@implementation NSBundle(swizzle)
|
||||
- (NSString *)__bundleIdentifier {
|
||||
if (self == [NSBundle mainBundle]) {
|
||||
return @"org.panda3d.pstats";
|
||||
} else {
|
||||
return [self __bundleIdentifier];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
void
|
||||
keyboard_interrupt(int sig) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
|
||||
[NSApp terminate:NSApp];
|
||||
});
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[]) {
|
||||
// This hack is necessary to allow showing notifications when run as a console app.
|
||||
Class cls = objc_getClass("NSBundle");
|
||||
if (cls) {
|
||||
method_exchangeImplementations(class_getInstanceMethod(cls, @selector(bundleIdentifier)),
|
||||
class_getInstanceMethod(cls, @selector(__bundleIdentifier)));
|
||||
}
|
||||
|
||||
// Set the bundle name of the application.
|
||||
ProcessSerialNumber psn;
|
||||
GetCurrentProcess(&psn);
|
||||
CPSSetProcessName(&psn, (char *)"PStats");
|
||||
|
||||
@autoreleasepool {
|
||||
// Create the server application.
|
||||
MacStatsServer *server = new MacStatsServer;
|
||||
|
||||
// Register a SIGINT handler to terminate the application correctly.
|
||||
// Otherwise, notifications may linger in the notification center.
|
||||
struct sigaction act = {};
|
||||
act.sa_handler = &keyboard_interrupt;
|
||||
act.sa_flags = SA_RESETHAND;
|
||||
sigaction(SIGINT, &act, nullptr);
|
||||
|
||||
// Get lost in the Cocoa main loop.
|
||||
server->run(argc, argv);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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 macStatsAppDelegate.h
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSAPPDELEGATE_H
|
||||
#define MACSTATSAPPDELEGATE_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
class MacStatsServer;
|
||||
|
||||
@interface MacStatsAppDelegate : NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate> {
|
||||
@private
|
||||
MacStatsServer *_server;
|
||||
NSTimer *_timer;
|
||||
}
|
||||
|
||||
- (id)initWithServer:(MacStatsServer *)server;
|
||||
- (void)applicationDidFinishLaunching:(NSApplication *)sender;
|
||||
- (BOOL)applicationShouldTerminate:(NSApplication *)sender;
|
||||
- (void)applicationWillTerminate:(NSApplication *)sender;
|
||||
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
|
||||
shouldPresentNotification:(NSUserNotification *)notification;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
/**
|
||||
* 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 macStatsAppDelegate.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#include "macStatsAppDelegate.h"
|
||||
#include "macStatsServer.h"
|
||||
#include "pStatGraph.h"
|
||||
|
||||
@implementation MacStatsAppDelegate
|
||||
|
||||
- (id)initWithServer:(MacStatsServer *)server {
|
||||
if (self = [super init]) {
|
||||
_server = server;
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
|
||||
// Squelches an annoying warning.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)application:(NSApplication *)sender
|
||||
openFile:(NSString *)filename {
|
||||
Filename fn([filename UTF8String]);
|
||||
fn.set_binary();
|
||||
return _server->open_session(fn);
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSApplication *)sender {
|
||||
// Set this object as delegate for user notifications.
|
||||
{
|
||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||
center.delegate = self;
|
||||
}
|
||||
|
||||
// Register default preferences.
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSDictionary *dict = [NSDictionary
|
||||
dictionaryWithObjects:@[@"", [NSNumber numberWithBool:YES], [NSNumber numberWithInt:PStatGraph::GBU_ms]]
|
||||
forKeys:@[@"Appearance", @"ShowStatusItem", @"TimeUnits"]];
|
||||
[defaults registerDefaults:dict];
|
||||
|
||||
if (_server != nil) {
|
||||
// Apply preferences.
|
||||
[self applyDefaults:nil];
|
||||
|
||||
// Create a timer to poll the server.
|
||||
_timer = [NSTimer scheduledTimerWithTimeInterval:0.2
|
||||
target:self
|
||||
selector:@selector(pollServer)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
|
||||
// Start new session if we don't have one yet.
|
||||
if (_server->get_monitor() == nullptr) {
|
||||
_server->new_session();
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for defaults change.
|
||||
{
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
[center addObserver:self
|
||||
selector:@selector(applyDefaults:)
|
||||
name:NSUserDefaultsDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyDefaults:(NSNotification *)notification {
|
||||
if (_server != nil) {
|
||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||
_server->set_show_status_item([defaults boolForKey:@"ShowStatusItem"]);
|
||||
_server->set_appearance([defaults stringForKey:@"Appearance"]);
|
||||
_server->set_time_units([defaults integerForKey:@"TimeUnits"]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pollServer {
|
||||
_server->poll();
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminate:(NSApplication *)sender {
|
||||
if (_server != nil) {
|
||||
return _server->close_session();
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(NSApplication *)sender {
|
||||
if (_timer != nil) {
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
if (_server != nil) {
|
||||
delete _server;
|
||||
_server = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
|
||||
shouldPresentNotification:(NSUserNotification *)notification {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(NSUserNotificationCenter *)center
|
||||
didActivateNotification:(NSUserNotification *)notification {
|
||||
NSUserNotificationAction *action = notification.additionalActivationAction;
|
||||
if (action != nil) {
|
||||
NSString *ident = action.identifier;
|
||||
if ([ident isEqual:@"quit"]) {
|
||||
[NSApp terminate:self];
|
||||
}
|
||||
else if ([ident isEqual:@"new"]) {
|
||||
_server->new_session();
|
||||
}
|
||||
else if ([ident isEqual:@"open"]) {
|
||||
_server->open_session();
|
||||
}
|
||||
else if ([ident isEqual:@"openLast"]) {
|
||||
_server->open_last_session();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleNewSession:(NSMenuItem *)item {
|
||||
_server->new_session();
|
||||
}
|
||||
|
||||
- (void)handleOpenSession:(NSMenuItem *)item {
|
||||
_server->open_session();
|
||||
}
|
||||
|
||||
- (void)handleOpenLastSession:(NSMenuItem *)item {
|
||||
_server->open_last_session();
|
||||
}
|
||||
|
||||
- (void)handleSaveSession:(NSMenuItem *)item {
|
||||
_server->save_session();
|
||||
}
|
||||
|
||||
- (void)handleCloseSession:(NSMenuItem *)item {
|
||||
_server->close_session();
|
||||
}
|
||||
|
||||
- (void)handleExportSession:(NSMenuItem *)item {
|
||||
_server->export_session();
|
||||
}
|
||||
|
||||
- (void)handleToggleSettingsBool:(NSMenuItem *)item {
|
||||
[[NSUserDefaults standardUserDefaults] setBool:(item.state != NSOnState) forKey:item.representedObject];
|
||||
}
|
||||
|
||||
- (void)handleSettingsInteger:(NSMenuItem *)item {
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:item.tag forKey:item.representedObject];
|
||||
}
|
||||
|
||||
- (void)handleSettingsAppearance:(NSMenuItem *)item {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:item.representedObject forKey:@"Appearance"];
|
||||
}
|
||||
|
||||
- (void)handleSpeed:(NSMenuItem *)item {
|
||||
MacStatsMonitor *monitor = _server->get_monitor();
|
||||
if (monitor != nullptr) {
|
||||
monitor->set_scroll_speed(item.tag);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePause:(NSMenuItem *)item {
|
||||
MacStatsMonitor *monitor = _server->get_monitor();
|
||||
if (monitor != nullptr) {
|
||||
monitor->set_pause(!item.state);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleClickStatusItem:(id)sender {
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
|
||||
- (void)handleChooseCollectorColor:(NSColorPanel *)panel {
|
||||
MacStatsMonitor *monitor = _server->get_monitor();
|
||||
if (monitor != nullptr) {
|
||||
NSColor *color = panel.color;
|
||||
monitor->handle_choose_collector_color(LRGBColor(color.redComponent, color.greenComponent, color.blueComponent));
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* 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 macStatsChartMenu.h
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSCHARTMENU_H
|
||||
#define MACSTATSCHARTMENU_H
|
||||
|
||||
#include "pandatoolbase.h"
|
||||
#include "macStatsMonitor.h"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
class PStatView;
|
||||
class PStatViewLevel;
|
||||
|
||||
/**
|
||||
* A pulldown menu of charts available for a particular thread.
|
||||
*/
|
||||
class MacStatsChartMenu {
|
||||
public:
|
||||
MacStatsChartMenu(MacStatsMonitor *monitor, int thread_index);
|
||||
~MacStatsChartMenu();
|
||||
|
||||
int get_thread_index() const { return _thread_index; }
|
||||
|
||||
void add_to_menu(NSMenu *menu, int position);
|
||||
void remove_from_menu(NSMenu *menu);
|
||||
|
||||
void check_update();
|
||||
void do_update();
|
||||
|
||||
private:
|
||||
bool add_view(NSMenu *parent_menu, const PStatViewLevel *view_level,
|
||||
bool show_level, int insert_at);
|
||||
NSMenuItem *make_menu_item(NSMenu *parent_menu, int insert_at,
|
||||
const char *label, SEL action,
|
||||
int collector_index = -1);
|
||||
|
||||
MacStatsMonitor *_monitor;
|
||||
int _thread_index;
|
||||
|
||||
int _last_level_index;
|
||||
NSMenu *_menu;
|
||||
|
||||
// Pair of menu item, submenu
|
||||
std::vector<std::pair<NSMenuItem *, NSMenu *> > _collector_items;
|
||||
int _time_items_end = 0;
|
||||
int _level_items_end = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
/**
|
||||
* 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 macStatsChartMenu.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#include "macStatsChartMenu.h"
|
||||
#include "macStatsChartMenuDelegate.h"
|
||||
#include "macStatsMonitor.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsChartMenu::
|
||||
MacStatsChartMenu(MacStatsMonitor *monitor, int thread_index) :
|
||||
_monitor(monitor),
|
||||
_thread_index(thread_index)
|
||||
{
|
||||
_menu = [[NSMenu alloc] init];
|
||||
_menu.delegate = [[MacStatsChartMenuDelegate alloc] initWithMonitor:monitor threadIndex:thread_index];
|
||||
|
||||
if (thread_index == 0) {
|
||||
_menu.title = @"Graphs";
|
||||
|
||||
// Timeline goes first.
|
||||
make_menu_item(_menu, -1, "Timeline", @selector(handleOpenTimeline:));
|
||||
|
||||
// Then the piano roll (even though it's not very useful nowadays)
|
||||
make_menu_item(_menu, -1, "Piano Roll", @selector(handleOpenPianoRoll:));
|
||||
}
|
||||
else {
|
||||
make_menu_item(_menu, -1, "Open Strip Chart", @selector(handleOpenStripChart:), 0);
|
||||
make_menu_item(_menu, -1, "Open Flame Graph", @selector(handleOpenFlameGraph:));
|
||||
}
|
||||
|
||||
[_menu addItem:[NSMenuItem separatorItem]];
|
||||
_time_items_end = 3;
|
||||
|
||||
// Put a separator between time items and level items.
|
||||
[_menu addItem:[NSMenuItem separatorItem]];
|
||||
_level_items_end = _time_items_end + 1;
|
||||
|
||||
// For the main thread menu, also some options relating to all graph windows.
|
||||
if (thread_index == 0) {
|
||||
[_menu addItem:[NSMenuItem separatorItem]];
|
||||
make_menu_item(_menu, -1, "Close All Graphs", @selector(handleCloseAllGraphs:));
|
||||
make_menu_item(_menu, -1, "Reopen Default Graphs", @selector(handleReopenDefaultGraphs:));
|
||||
make_menu_item(_menu, -1, "Save Current Layout as Default", @selector(handleSaveDefaultGraphs:));
|
||||
}
|
||||
|
||||
do_update();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsChartMenu::
|
||||
~MacStatsChartMenu() {
|
||||
MacStatsChartMenuDelegate *delegate = (MacStatsChartMenuDelegate *)_menu.delegate;
|
||||
[_menu release];
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the menu to the end of the indicated menu bar.
|
||||
*/
|
||||
void MacStatsChartMenu::
|
||||
add_to_menu(NSMenu *menu, int position) {
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
[menu insertItem:item atIndex:position];
|
||||
[menu setSubmenu:_menu forItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the menu from the menu bar.
|
||||
*/
|
||||
void MacStatsChartMenu::
|
||||
remove_from_menu(NSMenu *menu) {
|
||||
int index = [menu indexOfItemWithSubmenu:_menu];
|
||||
if (index >= 0) {
|
||||
[menu removeItemAtIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the menu needs to be updated (e.g. because of new data
|
||||
* from the client), and updates it if necessary.
|
||||
*/
|
||||
void MacStatsChartMenu::
|
||||
check_update() {
|
||||
PStatView &view = _monitor->get_view(_thread_index);
|
||||
if (view.get_level_index() != _last_level_index) {
|
||||
do_update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unconditionally updates the menu with the latest data from the client.
|
||||
*/
|
||||
void MacStatsChartMenu::
|
||||
do_update() {
|
||||
PStatView &view = _monitor->get_view(_thread_index);
|
||||
_last_level_index = view.get_level_index();
|
||||
|
||||
const PStatClientData *client_data = _monitor->get_client_data();
|
||||
if (_thread_index != 0) {
|
||||
std::string thread_name = client_data->get_thread_name(_thread_index);
|
||||
_menu.title = [NSString stringWithUTF8String:thread_name.c_str()];
|
||||
}
|
||||
|
||||
if (client_data->get_num_collectors() > _collector_items.size()) {
|
||||
_collector_items.resize(client_data->get_num_collectors(), std::make_pair(nullptr, nullptr));
|
||||
}
|
||||
|
||||
// The menu item(s) for the thread's frame time goes second.
|
||||
const PStatViewLevel *view_level = view.get_top_level();
|
||||
if (_thread_index == 0) {
|
||||
if (add_view(_menu, view_level, false, _time_items_end)) {
|
||||
++_time_items_end;
|
||||
++_level_items_end;
|
||||
}
|
||||
} else {
|
||||
for (int c = 0; c < view_level->get_num_children(); ++c) {
|
||||
if (add_view(_menu, view_level->get_child(c), false, _time_items_end)) {
|
||||
++_time_items_end;
|
||||
++_level_items_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// And then the menu item(s) for each of the level values.
|
||||
int num_toplevel_collectors = client_data->get_num_toplevel_collectors();
|
||||
for (int tc = 0; tc < num_toplevel_collectors; tc++) {
|
||||
int collector = client_data->get_toplevel_collector(tc);
|
||||
if (client_data->has_collector(collector) &&
|
||||
client_data->get_collector_has_level(collector, _thread_index)) {
|
||||
|
||||
PStatView &level_view = _monitor->get_level_view(collector, _thread_index);
|
||||
add_view(_menu, level_view.get_top_level(), true, _level_items_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new entry or entries to the menu for the indicated view and its
|
||||
* children. Returns true if an item was added, false if not.
|
||||
*/
|
||||
bool MacStatsChartMenu::
|
||||
add_view(NSMenu *parent_menu, const PStatViewLevel *view_level,
|
||||
bool show_level, int insert_at) {
|
||||
int collector = view_level->get_collector();
|
||||
|
||||
NSMenuItem *&menu_item = _collector_items[collector].first;
|
||||
NSMenu *&menu = _collector_items[collector].second;
|
||||
|
||||
const PStatClientData *client_data = _monitor->get_client_data();
|
||||
|
||||
int num_children = view_level->get_num_children();
|
||||
if (menu == nullptr && num_children == 0) {
|
||||
// For a collector without children, no point in making a submenu. We just
|
||||
// have the item open a strip chart directly (no point in creating a flame
|
||||
// graph if there are no children).
|
||||
if (menu_item != nullptr) {
|
||||
// Already exists.
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string collector_name = client_data->get_collector_name(collector);
|
||||
if (show_level) {
|
||||
menu_item = make_menu_item(parent_menu, insert_at,
|
||||
collector_name.c_str(), @selector(handleOpenStripChartLevel:), collector);
|
||||
} else {
|
||||
menu_item = make_menu_item(parent_menu, insert_at,
|
||||
collector_name.c_str(), @selector(handleOpenStripChart:), collector);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (menu_item != nullptr && menu == nullptr) {
|
||||
// Unhook the signal handler, we are creating a submenu.
|
||||
menu_item.action = nil;
|
||||
}
|
||||
|
||||
// Create a submenu.
|
||||
bool added_item = false;
|
||||
if (menu_item == nullptr) {
|
||||
std::string collector_name = client_data->get_collector_name(collector);
|
||||
menu_item = make_menu_item(parent_menu, insert_at, collector_name.c_str(), nil);
|
||||
added_item = true;
|
||||
}
|
||||
|
||||
if (menu == nullptr) {
|
||||
menu = [[NSMenu alloc] init];
|
||||
[parent_menu setSubmenu:menu forItem:menu_item];
|
||||
|
||||
if (show_level) {
|
||||
make_menu_item(menu, -1, "Open Strip Chart",
|
||||
@selector(handleOpenStripChartLevel:), collector);
|
||||
} else {
|
||||
make_menu_item(menu, -1, "Open Strip Chart",
|
||||
@selector(handleOpenStripChart:), collector);
|
||||
|
||||
if (collector == 0) {
|
||||
collector = -1;
|
||||
}
|
||||
|
||||
make_menu_item(menu, -1, "Open Flame Graph",
|
||||
@selector(handleOpenFlameGraph:), collector);
|
||||
}
|
||||
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
[menu release];
|
||||
}
|
||||
|
||||
for (int c = 0; c < num_children; ++c) {
|
||||
add_view(menu, view_level->get_child(c), show_level, 2 + !show_level);
|
||||
}
|
||||
|
||||
return added_item;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
NSMenuItem *MacStatsChartMenu::
|
||||
make_menu_item(NSMenu *parent_menu, int insert_at, const char *label, SEL action, int collector_index) {
|
||||
NSMenuItem *menu_item = [[NSMenuItem alloc] init];
|
||||
menu_item.title = [NSString stringWithUTF8String:label];
|
||||
menu_item.target = _menu.delegate;
|
||||
menu_item.action = action;
|
||||
menu_item.tag = collector_index;
|
||||
if (insert_at >= 0) {
|
||||
[parent_menu insertItem:menu_item atIndex:insert_at];
|
||||
} else {
|
||||
[parent_menu addItem:menu_item];
|
||||
}
|
||||
[menu_item release];
|
||||
return menu_item;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* 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 macStatsChartMenuDelegate.h
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSCHARTMENUDELEGATE_H
|
||||
#define MACSTATSCHARTMENUDELEGATE_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
class MacStatsMonitor;
|
||||
|
||||
@interface MacStatsChartMenuDelegate : NSObject<NSMenuDelegate> {
|
||||
@private
|
||||
MacStatsMonitor *_monitor;
|
||||
int _thread_index;
|
||||
}
|
||||
|
||||
- (id)initWithMonitor:(MacStatsMonitor *)monitor threadIndex:(int)index;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* 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 macStatsChartMenuDelegate.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#include "macStatsChartMenuDelegate.h"
|
||||
#include "macStatsMonitor.h"
|
||||
|
||||
@implementation MacStatsChartMenuDelegate
|
||||
|
||||
- (id)initWithMonitor:(MacStatsMonitor *)monitor threadIndex:(int)index {
|
||||
if (self = [super init]) {
|
||||
_monitor = monitor;
|
||||
_thread_index = index;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)handleOpenTimeline:(NSMenuItem *)item {
|
||||
_monitor->open_timeline();
|
||||
}
|
||||
|
||||
- (void)handleOpenStripChart:(NSMenuItem *)item {
|
||||
_monitor->open_strip_chart(_thread_index, item.tag, NO);
|
||||
}
|
||||
|
||||
- (void)handleOpenStripChartLevel:(NSMenuItem *)item {
|
||||
_monitor->open_strip_chart(_thread_index, item.tag, YES);
|
||||
}
|
||||
|
||||
- (void)handleOpenFlameGraph:(NSMenuItem *)item {
|
||||
_monitor->open_flame_graph(_thread_index, item.tag);
|
||||
}
|
||||
|
||||
- (void)handleOpenPianoRoll:(NSMenuItem *)item {
|
||||
_monitor->open_piano_roll(_thread_index);
|
||||
}
|
||||
|
||||
- (void)handleCloseAllGraphs:(NSMenuItem *)item {
|
||||
_monitor->close_all_graphs();
|
||||
}
|
||||
|
||||
- (void)handleReopenDefaultGraphs:(NSMenuItem *)item {
|
||||
_monitor->close_all_graphs();
|
||||
_monitor->open_default_graphs();
|
||||
}
|
||||
|
||||
- (void)handleSaveDefaultGraphs:(NSMenuItem *)item {
|
||||
_monitor->save_default_graphs();
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* 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.h
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSFLAMEGRAPH_H
|
||||
#define MACSTATSFLAMEGRAPH_H
|
||||
|
||||
#include "macStatsGraph.h"
|
||||
#include "pStatFlameGraph.h"
|
||||
#include "macStatsChartMenuDelegate.h"
|
||||
|
||||
/**
|
||||
* A window that draws a flame chart, which shows the collectors explicitly
|
||||
* stopping and starting, one frame at a time.
|
||||
*/
|
||||
class MacStatsFlameGraph final : public PStatFlameGraph, public MacStatsGraph {
|
||||
public:
|
||||
MacStatsFlameGraph(MacStatsMonitor *monitor, int thread_index,
|
||||
int collector_index=-1);
|
||||
virtual ~MacStatsFlameGraph();
|
||||
|
||||
virtual void new_collector(int collector_index);
|
||||
virtual void new_data(int thread_index, int frame_number);
|
||||
virtual void force_redraw();
|
||||
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
|
||||
|
||||
virtual void set_time_units(int unit_mask);
|
||||
virtual void on_click_label(int collector_index);
|
||||
virtual void on_enter_label(int collector_index);
|
||||
virtual void on_leave_label(int collector_index);
|
||||
virtual NSMenu *get_label_menu(int collector_index) const;
|
||||
|
||||
protected:
|
||||
virtual void normal_guide_bars();
|
||||
|
||||
void clear_region();
|
||||
virtual void begin_draw();
|
||||
virtual void draw_bar(int depth, int from_x, int to_x,
|
||||
int collector_index, int parent_index);
|
||||
virtual void end_draw();
|
||||
virtual void idle();
|
||||
|
||||
virtual bool animate(double time, double dt);
|
||||
|
||||
virtual bool get_window_state(int &x, int &y, int &width, int &height,
|
||||
bool &maximized, bool &minimized) const;
|
||||
virtual void set_window_state(int x, int y, int width, int height,
|
||||
bool maximized, bool minimized);
|
||||
|
||||
virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const;
|
||||
virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const;
|
||||
virtual DragMode consider_drag_start(int graph_x, int graph_y);
|
||||
|
||||
virtual void handle_button_press(int graph_x, int graph_y,
|
||||
bool double_click, int button);
|
||||
virtual void handle_button_release(int graph_x, int graph_y);
|
||||
virtual void handle_motion(int graph_x, int graph_y);
|
||||
virtual void handle_leave();
|
||||
virtual void handle_draw_graph(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_back();
|
||||
|
||||
public:
|
||||
void handle_toggle_average(bool state);
|
||||
|
||||
private:
|
||||
int pixel_to_depth(int y) const;
|
||||
void draw_guide_bar(CGContextRef ctx, const PStatGraph::GuideBar &bar);
|
||||
void draw_guide_labels(CGContextRef ctx);
|
||||
void draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar);
|
||||
|
||||
private:
|
||||
NSToolbarItem *_total_item;
|
||||
|
||||
MacStatsChartMenuDelegate *_menu_delegate;
|
||||
|
||||
std::vector<int> _back_stack;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,871 @@
|
|||
/**
|
||||
* 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) :
|
||||
PStatFlameGraph(monitor, thread_index, collector_index, 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 (_back_stack.empty()) {
|
||||
_graph_view_controller.backToolbarItemVisible = YES;
|
||||
}
|
||||
_back_stack.push_back(current);
|
||||
set_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!_back_stack.empty()) {
|
||||
_back_stack.clear();
|
||||
_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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!_back_stack.empty()) {
|
||||
int collector_index = _back_stack.back();
|
||||
_back_stack.pop_back();
|
||||
set_collector_index(collector_index);
|
||||
|
||||
if (_back_stack.empty()) {
|
||||
_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
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
/**
|
||||
* 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 macStatsGraph.h
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSGRAPH_H
|
||||
#define MACSTATSGRAPH_H
|
||||
|
||||
#include "pandatoolbase.h"
|
||||
#include "macStatsGraphViewController.h"
|
||||
#include "macStatsLabelStack.h"
|
||||
#include "pmap.h"
|
||||
#include "luse.h"
|
||||
|
||||
class MacStatsMonitor;
|
||||
|
||||
/**
|
||||
* This is just an abstract base class to provide a common pointer type for
|
||||
* the various kinds of graphs that may be created for a MacStatsMonitor.
|
||||
*/
|
||||
class MacStatsGraph {
|
||||
public:
|
||||
// What is the user adjusting by dragging the mouse in a window?
|
||||
enum DragMode {
|
||||
DM_none,
|
||||
DM_scale,
|
||||
DM_guide_bar,
|
||||
DM_new_guide_bar,
|
||||
DM_sizing,
|
||||
DM_pan,
|
||||
};
|
||||
|
||||
public:
|
||||
MacStatsGraph(MacStatsMonitor *monitor, MacStatsGraphViewController *controller);
|
||||
virtual ~MacStatsGraph();
|
||||
|
||||
void close();
|
||||
|
||||
MacStatsMonitor *get_monitor() { return _monitor; }
|
||||
NSSplitView *get_split_view() { return _split_view; }
|
||||
|
||||
virtual void new_collector(int collector_index);
|
||||
virtual void new_data(int thread_index, int frame_number);
|
||||
virtual void force_redraw()=0;
|
||||
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
|
||||
|
||||
virtual void set_time_units(int unit_mask);
|
||||
virtual void set_scroll_speed(double scroll_speed);
|
||||
void set_pause(bool pause);
|
||||
|
||||
void user_guide_bars_changed();
|
||||
virtual void on_click_label(int collector_index);
|
||||
virtual void on_enter_label(int collector_index);
|
||||
virtual void on_leave_label(int collector_index);
|
||||
virtual NSMenu *get_label_menu(int collector_index) const;
|
||||
virtual std::string get_label_tooltip(int collector_index) const;
|
||||
|
||||
void reset_collector_color(int collector_index);
|
||||
|
||||
protected:
|
||||
void start_animation();
|
||||
virtual bool animate(double time, double dt);
|
||||
|
||||
void get_window_state(int &x, int &y, int &width, int &height,
|
||||
bool &maximized, bool &minimized) const;
|
||||
void set_window_state(int x, int y, int width, int height,
|
||||
bool maximized, bool minimized);
|
||||
|
||||
public:
|
||||
// These must be public, because we can't declare Objective-C friends here.
|
||||
virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const;
|
||||
virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const;
|
||||
virtual DragMode consider_drag_start(int graph_x, int graph_y);
|
||||
virtual void set_drag_mode(DragMode drag_mode);
|
||||
|
||||
virtual bool handle_key(int graph_x, int graph_y, bool pressed,
|
||||
UniChar c, unsigned short key_code);
|
||||
virtual void handle_button_press(int graph_x, int graph_y,
|
||||
bool double_click, int button);
|
||||
virtual void handle_button_release(int graph_x, int graph_y);
|
||||
virtual void handle_motion(int graph_x, int graph_y);
|
||||
virtual void handle_leave();
|
||||
virtual void handle_scroll();
|
||||
virtual void handle_wheel(int graph_x, int graph_y, double dx, double dy);
|
||||
virtual void handle_magnify(int graph_x, int graph_y, double scale);
|
||||
virtual void handle_draw_graph(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_draw_graph_overhang(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_draw_scale_area(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_back();
|
||||
virtual void handle_timer();
|
||||
|
||||
protected:
|
||||
CGColorRef _background_color;
|
||||
|
||||
MacStatsMonitor *_monitor;
|
||||
NSWindow *_window;
|
||||
NSView *_graph_view;
|
||||
MacStatsGraphViewController *_graph_view_controller;
|
||||
NSView *_scale_area = nullptr;
|
||||
NSSplitView *_split_view = nullptr;
|
||||
NSScrollView *_sidebar_view = nullptr;
|
||||
MacStatsLabelStack _label_stack;
|
||||
|
||||
CGContextRef _ctx = nullptr;
|
||||
int _bitmap_xsize, _bitmap_ysize;
|
||||
|
||||
DragMode _drag_mode;
|
||||
DragMode _potential_drag_mode;
|
||||
int _drag_start_x, _drag_start_y;
|
||||
double _drag_scale_start;
|
||||
int _drag_guide_bar;
|
||||
|
||||
int _highlighted_index = -1;
|
||||
|
||||
bool _pause;
|
||||
|
||||
NSTimer *_animation_timer = nil;
|
||||
double _time = 0.0;
|
||||
|
||||
static const CGFloat rgb_white[4];
|
||||
static const CGFloat rgb_light_gray[4];
|
||||
static const CGFloat rgb_dark_gray[4];
|
||||
static const CGFloat rgb_black[4];
|
||||
static const CGFloat rgb_user_guide_bar[4];
|
||||
|
||||
private:
|
||||
void setup_bitmap(int xsize, int ysize, double scale);
|
||||
void release_bitmap();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,491 @@
|
|||
/**
|
||||
* 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 macStatsGraph.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#include "macStatsGraph.h"
|
||||
#include "macStatsGraphView.h"
|
||||
#include "macStatsMonitor.h"
|
||||
#include "macStatsLabelStack.h"
|
||||
|
||||
const CGFloat MacStatsGraph::rgb_light_gray[4] = {
|
||||
0x9a / (CGFloat)0xff, 0x9a / (CGFloat)0xff, 0x9a / (CGFloat)0xff, 1.0,
|
||||
};
|
||||
const CGFloat MacStatsGraph::rgb_dark_gray[4] = {
|
||||
0xb0 / (CGFloat)0xff, 0xb0 / (CGFloat)0xff, 0xb0 / (CGFloat)0xff, 1.0,
|
||||
};
|
||||
const CGFloat MacStatsGraph::rgb_user_guide_bar[4] = {
|
||||
0x82 / (CGFloat)0xff, 0x96 / (CGFloat)0xff, 0xff / (CGFloat)0xff, 1.0,
|
||||
};
|
||||
|
||||
static const NSInteger style_mask = NSWindowStyleMaskTitled
|
||||
| NSWindowStyleMaskClosable
|
||||
| NSWindowStyleMaskMiniaturizable
|
||||
| NSWindowStyleMaskResizable;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsGraph::
|
||||
MacStatsGraph(MacStatsMonitor *monitor, MacStatsGraphViewController *controller) :
|
||||
_monitor(monitor)
|
||||
{
|
||||
_background_color = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
_drag_mode = DM_none;
|
||||
_potential_drag_mode = DM_none;
|
||||
_drag_scale_start = 0.0f;
|
||||
|
||||
_pause = false;
|
||||
|
||||
NSInteger this_style_mask = style_mask;
|
||||
if (@available(macOS 11.0, *)) {
|
||||
this_style_mask |= NSWindowStyleMaskFullSizeContentView;
|
||||
}
|
||||
|
||||
_window = [NSWindow alloc];
|
||||
[_window initWithContentRect:NSMakeRect(100, 500, 500, 150)
|
||||
styleMask:this_style_mask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
_window.releasedWhenClosed = NO;
|
||||
_window.titlebarAppearsTransparent = NO;
|
||||
_window.excludedFromWindowsMenu = NO;
|
||||
|
||||
_window.contentMinSize = NSMakeSize(68, _window.contentView.frame.size.height - _window.contentLayoutRect.size.height);
|
||||
|
||||
_graph_view_controller = [controller initWithGraph:this];
|
||||
_graph_view = ((MacStatsGraphViewController *)_graph_view_controller).graphView;
|
||||
|
||||
// Get notified when the window closes.
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
[center addObserver:_graph_view_controller
|
||||
selector:@selector(windowWillClose:)
|
||||
name:NSWindowWillCloseNotification
|
||||
object:_window];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsGraph::
|
||||
~MacStatsGraph() {
|
||||
if (_animation_timer != nil) {
|
||||
[_animation_timer invalidate];
|
||||
[_animation_timer release];
|
||||
_animation_timer = nil;
|
||||
}
|
||||
|
||||
_monitor = nullptr;
|
||||
release_bitmap();
|
||||
|
||||
_label_stack.clear_labels();
|
||||
|
||||
[_graph_view_controller release];
|
||||
[_window release];
|
||||
|
||||
CGColorRelease(_background_color);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
close() {
|
||||
[_window close];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a new Collector definition is received from the client.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
new_collector(int new_collector) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever new data arrives.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
new_data(int thread_index, int frame_number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user has resized the window, forcing a resize of the graph.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
changed_graph_size(int graph_xsize, int 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 MacStatsGraph::
|
||||
set_time_units(int unit_mask) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user selects a new scroll speed from the monitor pulldown
|
||||
* menu, this should adjust the speed for the graph to the indicated value.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
set_scroll_speed(double scroll_speed) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the pause flag for the graph. When this flag is true, the graph
|
||||
* does not update in response to new data.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
set_pause(bool pause) {
|
||||
_pause = pause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user guide bars have been changed.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
user_guide_bars_changed() {
|
||||
if (_scale_area != nullptr) {
|
||||
_scale_area.needsDisplay = YES;
|
||||
}
|
||||
_graph_view.needsDisplay = YES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user single-clicks on a label.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
on_click_label(int collector_index) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user hovers the mouse over a label.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
on_enter_label(int collector_index) {
|
||||
if (collector_index != _highlighted_index) {
|
||||
_highlighted_index = collector_index;
|
||||
force_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user's mouse cursor leaves a label.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
on_leave_label(int collector_index) {
|
||||
if (collector_index == _highlighted_index && collector_index != -1) {
|
||||
_highlighted_index = -1;
|
||||
force_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse right-clicks on a label, and should return the menu
|
||||
* that should pop up.
|
||||
*/
|
||||
NSMenu *MacStatsGraph::
|
||||
get_label_menu(int collector_index) const {
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse hovers over a label, and should return the text that
|
||||
* should appear on the tooltip.
|
||||
*/
|
||||
std::string MacStatsGraph::
|
||||
get_label_tooltip(int collector_index) const {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on the animation timer, if it hasn't already been turned on.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
start_animation() {
|
||||
if (_animation_timer != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
_time = 0.0;
|
||||
_animation_timer = [NSTimer scheduledTimerWithTimeInterval:1 / 60.0 target:_graph_view selector:@selector(handleTimer:) userInfo:nil repeats:YES];
|
||||
[_animation_timer retain];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden by a derived class to implement an animation. If it returns
|
||||
* false, the animation timer is stopped.
|
||||
*/
|
||||
bool MacStatsGraph::
|
||||
animate(double time, double dt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current window dimensions.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
get_window_state(int &x, int &y, int &width, int &height,
|
||||
bool &maximized, bool &minimized) const {
|
||||
|
||||
NSRect screen_frame = _window.screen.visibleFrame;
|
||||
|
||||
NSRect frame = _window.frame;
|
||||
x = frame.origin.x - screen_frame.origin.x;
|
||||
y = (screen_frame.origin.y + screen_frame.size.height) - (frame.origin.y + frame.size.height);
|
||||
width = frame.size.width;
|
||||
height = frame.size.height;
|
||||
maximized = _window.zoomed;
|
||||
minimized = _window.miniaturized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to restore the graph window to its previous dimensions.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
set_window_state(int x, int y, int width, int height,
|
||||
bool maximized, bool minimized) {
|
||||
|
||||
NSRect screen_frame = _window.screen.visibleFrame;
|
||||
|
||||
NSRect frame;
|
||||
frame.origin.x = screen_frame.origin.x + x;
|
||||
frame.origin.y = (screen_frame.origin.y + screen_frame.size.height) - (y + height);
|
||||
frame.size.width = width;
|
||||
frame.size.height = height;
|
||||
[_window setFrame:frame display:NO];
|
||||
|
||||
if (maximized != _window.zoomed) {
|
||||
[_window zoom:_window];
|
||||
}
|
||||
|
||||
if (minimized != _window.miniaturized) {
|
||||
if (minimized) {
|
||||
[_window miniaturize:_window];
|
||||
} else {
|
||||
[_window deminiaturize:_window];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the given collector has changed colors.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
reset_collector_color(int collector_index) {
|
||||
force_redraw();
|
||||
_label_stack.update_label_color(collector_index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse right-clicks on the graph, and should return the menu
|
||||
* that should pop up.
|
||||
*/
|
||||
NSMenu *MacStatsGraph::
|
||||
get_graph_menu(int mouse_x, int mouse_y) const {
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse hovers over the graph, and should return the text that
|
||||
* should appear on the tooltip.
|
||||
*/
|
||||
std::string MacStatsGraph::
|
||||
get_graph_tooltip(int mouse_x, int mouse_y) const {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the mouse position within the graph window, look for draggable
|
||||
* things the mouse might be hovering over and return the appropriate DragMode
|
||||
* enum or DM_none if nothing is indicated.
|
||||
*/
|
||||
MacStatsGraph::DragMode MacStatsGraph::
|
||||
consider_drag_start(int graph_x, int graph_y) {
|
||||
return DM_none;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be called whenever the drag mode needs to change state. It
|
||||
* provides hooks for a derived class to do something special.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
set_drag_mode(MacStatsGraph::DragMode drag_mode) {
|
||||
_drag_mode = drag_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
bool MacStatsGraph::
|
||||
handle_key(int graph_x, int graph_y, bool pressed, UniChar c, unsigned short key_code) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse button is depressed within the window, or any nested
|
||||
* window.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_button_press(int graph_x, int graph_y, bool double_click, int button) {
|
||||
if (_potential_drag_mode != DM_none && button == 1) {
|
||||
set_drag_mode(_potential_drag_mode);
|
||||
_drag_start_x = graph_x;
|
||||
_drag_start_y = graph_y;
|
||||
// SetCapture(_window);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse button is released within the window, or any nested
|
||||
* window.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_button_release(int graph_x, int graph_y) {
|
||||
set_drag_mode(DM_none);
|
||||
// ReleaseCapture();
|
||||
|
||||
return handle_motion(graph_x, graph_y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse is moved within the window, or any nested window.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_motion(int graph_x, int graph_y) {
|
||||
_potential_drag_mode = consider_drag_start(graph_x, graph_y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse has left the graph window.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_leave() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse is moved within the graph window.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_scroll() {
|
||||
force_redraw();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_wheel(int graph_x, int graph_y, double dx, double dy) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_magnify(int graph_x, int graph_y, double scale) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_timer() {
|
||||
_time += 1.0 / 60.0;
|
||||
|
||||
if (!animate(_time, 1.0 / 60.0)) {
|
||||
[_animation_timer invalidate];
|
||||
[_animation_timer release];
|
||||
_animation_timer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the graph window.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_draw_graph(CGContextRef ctx, NSRect rect) {
|
||||
CGContextSetBlendMode(ctx, kCGBlendModeCopy);
|
||||
CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);
|
||||
|
||||
// Quantize this so that changed_graph_size will always call force_redraw()
|
||||
NSRect full_rect = _graph_view.bounds;
|
||||
full_rect.size.width = (int)full_rect.size.width;
|
||||
full_rect.size.height = (int)full_rect.size.height;
|
||||
|
||||
CGSize size = CGContextConvertSizeToDeviceSpace(ctx, full_rect.size);
|
||||
int width = abs((int)size.width);
|
||||
int height = abs((int)size.height);
|
||||
|
||||
if (_ctx == nullptr || _bitmap_xsize != width || _bitmap_ysize != height) {
|
||||
if (_ctx == nullptr && _scale_area != nullptr) {
|
||||
_scale_area.needsDisplay = YES;
|
||||
}
|
||||
setup_bitmap(width, height, _window.backingScaleFactor);
|
||||
|
||||
changed_graph_size(full_rect.size.width, full_rect.size.height);
|
||||
}
|
||||
|
||||
CGImageRef image = CGBitmapContextCreateImage(_ctx);
|
||||
CGContextDrawImage(ctx, CGRectMake(0, 0, full_rect.size.width, full_rect.size.height), image);
|
||||
CGImageRelease(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the graph window overhang, which is the area outside the graph
|
||||
* bounds that may become visible momentarily due to scroll elasticity.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_draw_graph_overhang(CGContextRef ctx, NSRect rect) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the scale area.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_draw_scale_area(CGContextRef ctx, NSRect rect) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse clicks the back button in the toolbar.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
handle_back() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a backing-store bitmap of the indicated size.
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
setup_bitmap(int xsize, int ysize, double scale) {
|
||||
release_bitmap();
|
||||
|
||||
_bitmap_xsize = xsize;
|
||||
_bitmap_ysize = ysize;
|
||||
|
||||
// Ostensibly, a layer context is more efficient, but I tried it and the
|
||||
// performance was horrible compared to a bitmap context.
|
||||
_ctx = CGBitmapContextCreate(nullptr, xsize, ysize, 8, 0, CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), kCGImageAlphaPremultipliedLast);
|
||||
CGContextSetBlendMode(_ctx, kCGBlendModeCopy);
|
||||
CGContextScaleCTM(_ctx, scale, scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the backing-store bitmap created by setup_bitmap().
|
||||
*/
|
||||
void MacStatsGraph::
|
||||
release_bitmap() {
|
||||
if (_ctx != nullptr) {
|
||||
CGContextRelease(_ctx);
|
||||
_ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* 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 macStatsGraphView.h
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSGRAPHVIEW_H
|
||||
#define MACSTATSGRAPHVIEW_H
|
||||
|
||||
#include "cocoa_compat.h"
|
||||
|
||||
class MacStatsGraph;
|
||||
|
||||
@interface MacStatsGraphView : NSView<NSViewToolTipOwner> {
|
||||
@public
|
||||
MacStatsGraph *_graph;
|
||||
}
|
||||
|
||||
- (id)initWithGraph:(MacStatsGraph *)graph;
|
||||
- (void)drawRect:(NSRect)dirtyRect;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* 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 macStatsGraphView.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#include "macStatsGraphView.h"
|
||||
#include "macStatsGraph.h"
|
||||
#include "macStatsStripChart.h"
|
||||
#include "macStatsFlameGraph.h"
|
||||
#include "macStatsTimeline.h"
|
||||
#include "macStatsScaleArea.h"
|
||||
|
||||
@implementation MacStatsGraphView
|
||||
|
||||
- (id)initWithGraph:(MacStatsGraph *)graph {
|
||||
if (self = [super init]) {
|
||||
_graph = graph;
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect) owner:self userInfo:nil];
|
||||
[self addTrackingArea:area];
|
||||
[area release];
|
||||
|
||||
[self addToolTipRect:NSMakeRect(0, 0, 1000, 1000) owner:self userData:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isFlipped {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
if (!event.isARepeat) {
|
||||
NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil];
|
||||
UniChar c = 0;
|
||||
NSString *str = [event charactersIgnoringModifiers];
|
||||
if (str != nil && str.length == 1) {
|
||||
c = [str characterAtIndex:0];
|
||||
}
|
||||
_graph->handle_key(pos.x, pos.y, true, c, event.keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil];
|
||||
UniChar c = 0;
|
||||
NSString *str = [event charactersIgnoringModifiers];
|
||||
if (str != nil && str.length == 1) {
|
||||
c = [str characterAtIndex:0];
|
||||
}
|
||||
_graph->handle_key(pos.x, pos.y, false, c, event.keyCode);
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil];
|
||||
_graph->handle_button_press(pos.x, pos.y, event.clickCount > 1, event.buttonNumber);
|
||||
}
|
||||
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil];
|
||||
_graph->handle_button_release(pos.x, pos.y);
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil];
|
||||
_graph->handle_motion(pos.x, pos.y);
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil];
|
||||
_graph->handle_motion(pos.x, pos.y);
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)event {
|
||||
_graph->handle_leave();
|
||||
}
|
||||
|
||||
- (void)scrollWheel:(NSEvent *)event {
|
||||
[super scrollWheel:event];
|
||||
if (event.deltaX != 0) {
|
||||
NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil];
|
||||
_graph->handle_wheel(pos.x, pos.y, event.deltaX, event.deltaY);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)magnifyWithEvent:(NSEvent *)event {
|
||||
NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil];
|
||||
_graph->handle_magnify(pos.x, pos.y, event.magnification);
|
||||
}
|
||||
|
||||
- (void)handleTimer:(NSTimer *)timer {
|
||||
_graph->handle_timer();
|
||||
}
|
||||
|
||||
- (void)viewDidChangeEffectiveAppearance {
|
||||
if (_graph != nullptr) {
|
||||
// Don't call this initially
|
||||
if (self.window != nil) {
|
||||
[NSAppearance setCurrentAppearance:self.effectiveAppearance];
|
||||
_graph->force_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect {
|
||||
if (_graph != nullptr) {
|
||||
CGContextRef ctx = [NSGraphicsContext currentContext].CGContext;
|
||||
_graph->handle_draw_graph(ctx, dirtyRect);
|
||||
}
|
||||
}
|
||||
|
||||
// Not called when building with macOS 14 SDK due to change in clipsToBounds
|
||||
// (but there it simply calls drawRect with the overhang region)
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 140000
|
||||
- (void)drawBackgroundOverhangInRect:(NSRect)dirtyRect {
|
||||
if (_graph != nullptr) {
|
||||
CGContextRef ctx = [NSGraphicsContext currentContext].CGContext;
|
||||
_graph->handle_draw_graph_overhang(ctx, dirtyRect);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
- (NSMenu *)menuForEvent:(NSEvent *)event {
|
||||
if (_graph != nullptr) {
|
||||
NSPoint pos = [self convertPoint:event.locationInWindow fromView:nil];
|
||||
return _graph->get_graph_menu(pos.x, pos.y);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)view:(NSView *)view
|
||||
stringForToolTip:(NSToolTipTag)tag
|
||||
point:(NSPoint)point
|
||||
userData:(void *)data {
|
||||
|
||||
if (_graph != nullptr) {
|
||||
std::string text = _graph->get_graph_tooltip(point.x, point.y);
|
||||
return [NSString stringWithUTF8String:text.c_str()];
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* 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 macStatsGraphViewController.h
|
||||
* @author rdb
|
||||
* @date 2023-08-28
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSGRAPHVIEWCONTROLLER_H
|
||||
#define MACSTATSGRAPHVIEWCONTROLLER_H
|
||||
|
||||
#include "macStatsGraphView.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
class MacStatsGraph;
|
||||
|
||||
@interface MacStatsGraphViewController : NSViewController<NSToolbarDelegate> {
|
||||
@protected
|
||||
MacStatsGraph *_graph;
|
||||
}
|
||||
|
||||
- (id)initWithGraph:(MacStatsGraph *)graph;
|
||||
- (MacStatsGraphView *)graphView;
|
||||
- (BOOL)backToolbarItemVisible;
|
||||
- (void)setBackToolbarItemVisible:(BOOL)show;
|
||||
|
||||
@end
|
||||
|
||||
@interface MacStatsScrollableGraphViewController : MacStatsGraphViewController
|
||||
|
||||
- (MacStatsGraphView *)graphView;
|
||||
- (NSClipView *)clipView;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
/**
|
||||
* 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 macStatsGraphViewController.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-28
|
||||
*/
|
||||
|
||||
#include "macStatsGraphViewController.h"
|
||||
#include "macStatsGraph.h"
|
||||
#include "macStatsMonitor.h"
|
||||
#include "cocoa_compat.h"
|
||||
|
||||
@implementation MacStatsGraphViewController
|
||||
|
||||
- (id)initWithGraph:(MacStatsGraph *)graph {
|
||||
if (self = [super init]) {
|
||||
_graph = graph;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
MacStatsGraph *graph = _graph;
|
||||
if (graph != nullptr) {
|
||||
MacStatsMonitor *monitor = graph->get_monitor();
|
||||
if (monitor != nullptr) {
|
||||
_graph = nullptr;
|
||||
monitor->remove_graph(graph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadView {
|
||||
NSView *graph_view = [[MacStatsGraphView alloc] initWithGraph:_graph];
|
||||
NSView *background;
|
||||
if (@available(macOS 10.14, *)) {
|
||||
NSVisualEffectView *effect_view = [[NSVisualEffectView alloc] init];
|
||||
effect_view.material = (NSVisualEffectMaterial)18;//NSVisualEffectMaterialContentBackground;
|
||||
background = effect_view;
|
||||
} else {
|
||||
background = [[NSView alloc] init];
|
||||
background.wantsLayer = YES;
|
||||
background.layer.backgroundColor = [NSColor controlBackgroundColor].CGColor;
|
||||
}
|
||||
[background addSubview:graph_view];
|
||||
self.view = background;
|
||||
|
||||
[graph_view.widthAnchor constraintEqualToAnchor:background.widthAnchor].active = YES;
|
||||
[graph_view.heightAnchor constraintEqualToAnchor:background.heightAnchor].active = YES;
|
||||
[graph_view release];
|
||||
[background release];
|
||||
}
|
||||
|
||||
- (MacStatsGraphView *)graphView {
|
||||
return (MacStatsGraphView *)self.view.subviews[0];
|
||||
}
|
||||
|
||||
- (void)handleSplitViewResize:(NSNotification *)notification {
|
||||
NSSplitView *split_view = (NSSplitView *)notification.object;
|
||||
NSWindow *window = split_view.window;
|
||||
NSToolbar *toolbar = window.toolbar;
|
||||
if ([split_view isSubviewCollapsed:split_view.arrangedSubviews[0]]) {
|
||||
if (toolbar.items[0].itemIdentifier != NSToolbarToggleSidebarItemIdentifier) {
|
||||
[toolbar insertItemWithItemIdentifier:NSToolbarToggleSidebarItemIdentifier atIndex:0];
|
||||
}
|
||||
} else {
|
||||
if (toolbar.items[0].itemIdentifier == NSToolbarToggleSidebarItemIdentifier) {
|
||||
[toolbar removeItemAtIndex:0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)backToolbarItemVisible {
|
||||
NSToolbar *toolbar = self.view.window.toolbar;
|
||||
if ([toolbar.items[0].itemIdentifier isEqual:@"back"] ||
|
||||
[toolbar.items[1].itemIdentifier isEqual:@"back"]) {
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBackToolbarItemVisible:(BOOL)show {
|
||||
NSToolbar *toolbar = self.view.window.toolbar;
|
||||
if ([toolbar.items[1].itemIdentifier isEqual:@"back"]) {
|
||||
if (!show) {
|
||||
[toolbar removeItemAtIndex:1];
|
||||
}
|
||||
}
|
||||
else if ([toolbar.items[0].itemIdentifier isEqual:@"back"]) {
|
||||
if (!show) {
|
||||
[toolbar removeItemAtIndex:0];
|
||||
}
|
||||
}
|
||||
else if (show) {
|
||||
// Insert it after the sidebar toggle, if we have one.
|
||||
int index = ([toolbar.items[0].itemIdentifier isEqual:NSToolbarToggleSidebarItemIdentifier]);
|
||||
[toolbar insertItemWithItemIdentifier:@"back" atIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
|
||||
return @[];
|
||||
}
|
||||
|
||||
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
|
||||
return @[];
|
||||
}
|
||||
|
||||
- (NSToolbarItem *) toolbar:(NSToolbar *)toolbar
|
||||
itemForItemIdentifier:(NSString *)ident
|
||||
willBeInsertedIntoToolbar:(BOOL)flag {
|
||||
|
||||
if (@available(macOS 11.0, *)) {
|
||||
if ([ident isEqual:@"sep"]) {
|
||||
return [NSTrackingSeparatorToolbarItem trackingSeparatorToolbarItemWithIdentifier:@"sep" splitView:_graph->get_split_view() dividerIndex:0];
|
||||
}
|
||||
|
||||
if ([ident isEqual:@"back"]) {
|
||||
NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease];
|
||||
item.label = @"Back";
|
||||
item.image = [NSImage imageWithSystemSymbolName:@"chevron.left" accessibilityDescription:@""];
|
||||
item.target = self;
|
||||
item.action = @selector(handleBack:);
|
||||
[item setNavigational:YES];
|
||||
[item setBordered:YES];
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)handleSetAsFocus:(NSMenuItem *)item {
|
||||
_graph->on_click_label(item.tag);
|
||||
}
|
||||
|
||||
- (void)handleChangeColor:(NSMenuItem *)item {
|
||||
_graph->get_monitor()->choose_collector_color(item.tag);
|
||||
}
|
||||
|
||||
- (void)handleResetColor:(NSMenuItem *)item {
|
||||
_graph->get_monitor()->reset_collector_color(item.tag);
|
||||
}
|
||||
|
||||
- (void)handleBack:(id)sender {
|
||||
_graph->handle_back();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MacStatsScrollableGraphViewController
|
||||
|
||||
- (void)loadView {
|
||||
NSView *graph_view = [[MacStatsGraphView alloc] initWithGraph:_graph];
|
||||
NSScrollView *scroll = [[NSScrollView alloc] init];
|
||||
scroll.hasHorizontalScroller = NO;
|
||||
scroll.hasVerticalScroller = YES;
|
||||
scroll.horizontalScrollElasticity = NSScrollElasticityNone;
|
||||
scroll.usesPredominantAxisScrolling = NO;
|
||||
scroll.drawsBackground = YES;
|
||||
scroll.scrollerStyle = NSScrollerStyleOverlay;
|
||||
scroll.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
//scroll.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
scroll.automaticallyAdjustsContentInsets = YES;
|
||||
scroll.documentView = graph_view;
|
||||
self.view = scroll;
|
||||
|
||||
[graph_view.widthAnchor constraintEqualToAnchor:scroll.widthAnchor].active = YES;
|
||||
|
||||
if (@available(macOS 11.0, *)) {
|
||||
[graph_view.heightAnchor constraintGreaterThanOrEqualToAnchor:((NSLayoutGuide *)[scroll safeAreaLayoutGuide]).heightAnchor].active = YES;
|
||||
} else {
|
||||
[graph_view.heightAnchor constraintGreaterThanOrEqualToAnchor:scroll.heightAnchor].active = YES;
|
||||
}
|
||||
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
[center addObserver:self
|
||||
selector:@selector(handleScroll:)
|
||||
name:NSScrollViewDidLiveScrollNotification
|
||||
object:scroll];
|
||||
[scroll release];
|
||||
[graph_view release];
|
||||
}
|
||||
|
||||
- (MacStatsGraphView *)graphView {
|
||||
return (MacStatsGraphView *)((NSScrollView *)self.view).documentView;
|
||||
}
|
||||
|
||||
- (NSClipView *)clipView {
|
||||
return ((NSScrollView *)self.view).contentView;
|
||||
}
|
||||
|
||||
- (void)viewDidLayout {
|
||||
if (_graph != nullptr) {
|
||||
_graph->handle_scroll();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleScroll:(NSNotification *)notification {
|
||||
if (_graph != nullptr) {
|
||||
_graph->handle_scroll();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleSideScroll:(NSNotification *)notification {
|
||||
// Graph view is flipped, side bar isn't, so we need to convert coordinates
|
||||
NSScrollView *side_sv = ((NSScrollView *)notification.object);
|
||||
NSScrollView *graph_sv = (NSScrollView *)self.view;
|
||||
NSPoint point;
|
||||
point.x = 0;
|
||||
point.y = self.graphView.frame.size.height - (side_sv.documentVisibleRect.size.height + side_sv.documentVisibleRect.origin.y) - graph_sv.contentInsets.top;
|
||||
[graph_sv.contentView scrollToPoint:point];
|
||||
[graph_sv reflectScrolledClipView:graph_sv.contentView];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* 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 macStatsLabel.h
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSLABEL_H
|
||||
#define MACSTATSLABEL_H
|
||||
|
||||
#include "pandatoolbase.h"
|
||||
#include "luse.h"
|
||||
|
||||
#include "cocoa_compat.h"
|
||||
|
||||
class MacStatsMonitor;
|
||||
class MacStatsGraph;
|
||||
|
||||
/**
|
||||
* A text label that will draw in color appropriate for a particular
|
||||
* collector. It also responds when the user double-clicks on it. This is
|
||||
* handy for putting colored labels on strip charts.
|
||||
*/
|
||||
@interface MacStatsLabel : NSTextField<NSViewToolTipOwner> {
|
||||
@private
|
||||
MacStatsGraph *_graph;
|
||||
int _thread_index;
|
||||
int _collector_index;
|
||||
bool _highlight;
|
||||
bool _mouse_within;
|
||||
NSColor *_fg_color;
|
||||
NSColor *_highlight_fg_color;
|
||||
NSColor *_bg_color;
|
||||
NSColor *_highlight_bg_color;
|
||||
}
|
||||
|
||||
- (id)initWithText:(NSString *)text
|
||||
graph:(MacStatsGraph *)graph
|
||||
threadIndex:(int)thread_index
|
||||
collectorIndex:(int)collector_index;
|
||||
|
||||
- (int)threadIndex;
|
||||
- (int)collectorIndex;
|
||||
|
||||
- (void)updateColor;
|
||||
|
||||
- (BOOL)highlight;
|
||||
- (void)setHighlight:(BOOL)highlight;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* 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 macStatsLabel.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#include "macStatsLabel.h"
|
||||
#include "macStatsMonitor.h"
|
||||
#include "macStatsGraph.h"
|
||||
|
||||
@implementation MacStatsLabel
|
||||
|
||||
- (id)initWithText:(NSString *)text
|
||||
graph:(MacStatsGraph *)graph
|
||||
threadIndex:(int)thread_index
|
||||
collectorIndex:(int)collector_index {
|
||||
if (self = [super init]) {
|
||||
_graph = graph;
|
||||
_thread_index = thread_index;
|
||||
_collector_index = collector_index;
|
||||
_bg_color = nil;
|
||||
_highlight_bg_color = nil;
|
||||
|
||||
[self setStringValue:text];
|
||||
self.bezeled = NO;
|
||||
self.drawsBackground = YES;
|
||||
self.selectable = NO;
|
||||
self.editable = NO;
|
||||
self.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
//self.autoresizingMask = NSViewWidthSizable | NSViewMaxXMargin | NSViewMinXMargin;
|
||||
//self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:NSZeroRect options:(NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect) owner:self userInfo:nil];
|
||||
[self addTrackingArea:area];
|
||||
[area release];
|
||||
|
||||
[self addToolTipRect:NSMakeRect(0, 0, 1000, 1000) owner:self userData:nil];
|
||||
|
||||
[self updateColor];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_bg_color release];
|
||||
[_highlight_bg_color release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSSize)intrinsicContentSize {
|
||||
// Allow resizing down.
|
||||
NSSize size = [super intrinsicContentSize];
|
||||
return NSMakeSize(NSViewNoIntrinsicMetric, size.height);
|
||||
}
|
||||
|
||||
- (int)threadIndex {
|
||||
return _thread_index;
|
||||
}
|
||||
|
||||
- (int)collectorIndex {
|
||||
return _collector_index;
|
||||
}
|
||||
|
||||
- (void)updateColor {
|
||||
if (_bg_color != nil) {
|
||||
[_bg_color release];
|
||||
}
|
||||
if (_highlight_bg_color != nil) {
|
||||
[_highlight_bg_color release];
|
||||
}
|
||||
|
||||
_bg_color = [NSColor colorWithCGColor:_graph->get_monitor()->get_collector_color(_collector_index, false)];
|
||||
_highlight_bg_color = [NSColor colorWithCGColor:_graph->get_monitor()->get_collector_color(_collector_index, true)];
|
||||
|
||||
[_bg_color retain];
|
||||
[_highlight_bg_color retain];
|
||||
|
||||
_fg_color = _graph->get_monitor()->get_collector_text_color(_collector_index, false);
|
||||
_highlight_fg_color = _graph->get_monitor()->get_collector_text_color(_collector_index, true);
|
||||
|
||||
[self setHighlight:_highlight];
|
||||
}
|
||||
|
||||
- (BOOL)highlight {
|
||||
return _highlight;
|
||||
}
|
||||
|
||||
- (void)setHighlight:(BOOL)highlight {
|
||||
_highlight = highlight;
|
||||
if (highlight || _mouse_within) {
|
||||
self.backgroundColor = _highlight_bg_color;
|
||||
self.textColor = _highlight_fg_color;
|
||||
} else {
|
||||
self.backgroundColor = _bg_color;
|
||||
self.textColor = _fg_color;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)event {
|
||||
_mouse_within = true;
|
||||
self.backgroundColor = _highlight_bg_color;
|
||||
self.textColor = _highlight_fg_color;
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)event {
|
||||
_mouse_within = false;
|
||||
[self setHighlight:_highlight];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
if (event.buttonNumber == 0 && event.clickCount == 2) {
|
||||
_graph->on_click_label(_collector_index);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMenu *)menuForEvent:(NSEvent *)event {
|
||||
return _graph->get_label_menu(_collector_index);
|
||||
}
|
||||
|
||||
- (NSString *)view:(NSView *)view
|
||||
stringForToolTip:(NSToolTipTag)tag
|
||||
point:(NSPoint)point
|
||||
userData:(void *)data {
|
||||
|
||||
std::string text = _graph->get_label_tooltip(_collector_index);
|
||||
return [NSString stringWithUTF8String:text.c_str()];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* 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 macStatsLabelStack.h
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSLABELSTACK_H
|
||||
#define MACSTATSLABELSTACK_H
|
||||
|
||||
#include "pandatoolbase.h"
|
||||
#include "pvector.h"
|
||||
#include "macStatsLabel.h"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
class MacStatsMonitor;
|
||||
class MacStatsGraph;
|
||||
|
||||
/**
|
||||
* A widget that contains a stack of labels from bottom to top.
|
||||
*/
|
||||
class MacStatsLabelStack {
|
||||
public:
|
||||
MacStatsLabelStack();
|
||||
~MacStatsLabelStack();
|
||||
|
||||
NSView *get_view() const;
|
||||
|
||||
int get_label_y(int label_index, NSView *target_view) const;
|
||||
int get_label_height(int label_index) const;
|
||||
int get_label_collector_index(int label_index) const;
|
||||
|
||||
void clear_labels();
|
||||
int add_label(MacStatsMonitor *monitor, MacStatsGraph *graph,
|
||||
int thread_index, int collector_index, bool use_fullname);
|
||||
int get_num_labels() const;
|
||||
|
||||
void highlight_label(int collector_index);
|
||||
void update_label_color(int collector_index);
|
||||
|
||||
private:
|
||||
NSStackView *_stack_view;
|
||||
NSLayoutConstraint *_constraint;
|
||||
int _highlight_label;
|
||||
|
||||
typedef pvector<MacStatsLabel *> Labels;
|
||||
Labels _labels;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
/**
|
||||
* 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 macStatsLabelStack.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#include "macStatsLabelStack.h"
|
||||
#include "macStatsLabel.h"
|
||||
#include "macStatsMonitor.h"
|
||||
|
||||
static const NSEdgeInsets insets = {8, 8, 8, 8};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsLabelStack::
|
||||
MacStatsLabelStack() {
|
||||
_stack_view = [[NSStackView alloc] init];
|
||||
_stack_view.edgeInsets = insets;
|
||||
_stack_view.orientation = NSUserInterfaceLayoutOrientationVertical;
|
||||
_stack_view.alignment = NSLayoutAttributeRight;//NSLayoutAttributeCenterX;
|
||||
_stack_view.distribution = NSStackViewDistributionGravityAreas;
|
||||
_stack_view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
_stack_view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_stack_view.spacing = 0;
|
||||
_highlight_label = -1;
|
||||
|
||||
// May never be wider than the widest label, or 68, whichever is larger
|
||||
//FIXME
|
||||
_constraint = [_stack_view.widthAnchor constraintLessThanOrEqualToConstant:68];
|
||||
_constraint.active = NO;
|
||||
[_constraint retain];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsLabelStack::
|
||||
~MacStatsLabelStack() {
|
||||
clear_labels();
|
||||
[_constraint release];
|
||||
[_stack_view release];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the view for this stack.
|
||||
*/
|
||||
NSView *MacStatsLabelStack::
|
||||
get_view() const {
|
||||
return _stack_view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y position of the indicated label's bottom edge, relative to
|
||||
* the indicated target widget.
|
||||
*/
|
||||
int MacStatsLabelStack::
|
||||
get_label_y(int label_index, NSView *target_view) const {
|
||||
nassertr(label_index >= 0 && label_index < (int)_labels.size(), 0);
|
||||
|
||||
MacStatsLabel *label = _labels[label_index];
|
||||
NSPoint pos = [target_view convertPoint:NSMakePoint(0, label.frame.size.height) fromView:label];
|
||||
return pos.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the indicated label.
|
||||
*/
|
||||
int MacStatsLabelStack::
|
||||
get_label_height(int label_index) const {
|
||||
nassertr(label_index >= 0 && label_index < (int)_labels.size(), 0);
|
||||
return _labels[label_index].frame.size.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collector index associated with the indicated label.
|
||||
*/
|
||||
int MacStatsLabelStack::
|
||||
get_label_collector_index(int label_index) const {
|
||||
nassertr(label_index >= 0 && label_index < (int)_labels.size(), -1);
|
||||
return _labels[label_index].collectorIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the set of labels and starts a new set.
|
||||
*/
|
||||
void MacStatsLabelStack::
|
||||
clear_labels() {
|
||||
for (MacStatsLabel *label : _labels) {
|
||||
[_stack_view removeView:label];
|
||||
[label release];
|
||||
}
|
||||
_labels.clear();
|
||||
|
||||
_constraint.constant = 68;
|
||||
|
||||
NSRect frame = _stack_view.frame;
|
||||
frame.size.width = 0;
|
||||
_stack_view.frame = frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new label to the top of the stack; returns the new label index.
|
||||
*/
|
||||
int MacStatsLabelStack::
|
||||
add_label(MacStatsMonitor *monitor, MacStatsGraph *graph,
|
||||
int thread_index, int collector_index, bool use_fullname) {
|
||||
const PStatClientData *client_data = monitor->get_client_data();
|
||||
std::string text;
|
||||
if (use_fullname) {
|
||||
text = client_data->get_collector_fullname(collector_index);
|
||||
} else {
|
||||
text = client_data->get_collector_name(collector_index);
|
||||
}
|
||||
|
||||
MacStatsLabel *label = [MacStatsLabel alloc];
|
||||
[label initWithText:[NSString stringWithUTF8String:text.c_str()]
|
||||
graph:graph
|
||||
threadIndex:thread_index
|
||||
collectorIndex:collector_index];
|
||||
|
||||
[_stack_view insertView:label atIndex:0 inGravity:NSStackViewGravityBottom];
|
||||
[label.leadingAnchor constraintEqualToAnchor:_stack_view.leadingAnchor constant:8].active = YES;
|
||||
[label.trailingAnchor constraintLessThanOrEqualToAnchor:_stack_view.trailingAnchor constant:-8].active = YES;
|
||||
|
||||
_constraint.constant = std::max(_constraint.constant, label.intrinsicContentSize.width);
|
||||
|
||||
int label_index = (int)_labels.size();
|
||||
_labels.push_back(label);
|
||||
|
||||
return label_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of labels in the stack.
|
||||
*/
|
||||
int MacStatsLabelStack::
|
||||
get_num_labels() const {
|
||||
return _labels.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a highlight around the label representing the indicated collector,
|
||||
* and removes the highlight from any other label. Specify -1 to remove the
|
||||
* highlight from all labels.
|
||||
*/
|
||||
void MacStatsLabelStack::
|
||||
highlight_label(int collector_index) {
|
||||
if (_highlight_label != collector_index) {
|
||||
_highlight_label = collector_index;
|
||||
for (MacStatsLabel *label : _labels) {
|
||||
label.highlight = (label.collectorIndex == _highlight_label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the color of the label with the given index.
|
||||
*/
|
||||
void MacStatsLabelStack::
|
||||
update_label_color(int collector_index) {
|
||||
for (MacStatsLabel *label : _labels) {
|
||||
if (label.collectorIndex == collector_index) {
|
||||
[label updateColor];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* 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 macStatsMonitor.h
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSMONITOR_H
|
||||
#define MACSTATSMONITOR_H
|
||||
|
||||
#include "pandatoolbase.h"
|
||||
|
||||
#include "pStatMonitor.h"
|
||||
#include "pointerTo.h"
|
||||
#include "pset.h"
|
||||
#include "pvector.h"
|
||||
#include "pmap.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
class MacStatsGraph;
|
||||
class MacStatsServer;
|
||||
class MacStatsChartMenu;
|
||||
|
||||
/**
|
||||
* This class represents a connection to a PStatsClient and manages the data
|
||||
* exchange with the client.
|
||||
*/
|
||||
class MacStatsMonitor : public PStatMonitor {
|
||||
public:
|
||||
MacStatsMonitor(MacStatsServer *server);
|
||||
virtual ~MacStatsMonitor();
|
||||
|
||||
void close();
|
||||
|
||||
virtual std::string get_monitor_name();
|
||||
|
||||
virtual void initialized();
|
||||
virtual void got_hello();
|
||||
virtual void got_bad_version(int client_major, int client_minor,
|
||||
int server_major, int server_minor);
|
||||
virtual void new_collector(int collector_index);
|
||||
virtual void new_thread(int thread_index);
|
||||
virtual void new_data(int thread_index, int frame_number);
|
||||
virtual void remove_thread(int thread_index);
|
||||
virtual void lost_connection();
|
||||
virtual void idle();
|
||||
virtual bool has_idle();
|
||||
|
||||
virtual void user_guide_bars_changed();
|
||||
|
||||
PStatGraph *open_timeline();
|
||||
PStatGraph *open_strip_chart(int thread_index, int collector_index, bool show_level);
|
||||
PStatGraph *open_flame_graph(int thread_index, int collector_index = -1);
|
||||
PStatGraph *open_piano_roll(int thread_index);
|
||||
|
||||
CGColorRef get_collector_color(int collector_index, bool highlight = false);
|
||||
NSColor *get_collector_text_color(int collector_index, bool highlight = false);
|
||||
|
||||
void choose_collector_color(int collector_index);
|
||||
void handle_choose_collector_color(const LRGBColor &color);
|
||||
void reset_collector_color(int collector_index);
|
||||
|
||||
void set_show_status_item(bool show);
|
||||
void set_time_units(int unit_mask);
|
||||
void set_scroll_speed(double scroll_speed);
|
||||
void set_pause(bool pause);
|
||||
|
||||
void add_graph(MacStatsGraph *graph);
|
||||
void remove_graph(MacStatsGraph *graph);
|
||||
void close_all_graphs();
|
||||
|
||||
private:
|
||||
void setup_speed_menu();
|
||||
void update_status_bar();
|
||||
|
||||
private:
|
||||
typedef pset<MacStatsGraph *> Graphs;
|
||||
Graphs _graphs;
|
||||
|
||||
typedef pvector<MacStatsChartMenu *> ChartMenus;
|
||||
ChartMenus _chart_menus;
|
||||
|
||||
NSUserNotification *_notification = nullptr;
|
||||
NSMenu *_main_menu;
|
||||
NSMenuItem *_speed_menu_item = nullptr;
|
||||
NSMenuItem *_speed_menu_item_1;
|
||||
NSMenuItem *_speed_menu_item_2;
|
||||
NSMenuItem *_speed_menu_item_3;
|
||||
NSMenuItem *_speed_menu_item_6;
|
||||
NSMenuItem *_speed_menu_item_12;
|
||||
NSMenuItem *_speed_menu_item_pause;
|
||||
int _next_chart_index;
|
||||
NSStatusItem *_frame_rate_status_item = nullptr;
|
||||
double _scroll_speed;
|
||||
bool _pause;
|
||||
bool _have_data = false;
|
||||
int _choosing_color_collector_index;
|
||||
|
||||
struct ColorSet {
|
||||
CGColorRef _bg[2];
|
||||
NSColor *_fg[2];
|
||||
};
|
||||
typedef pmap<int, ColorSet> Colors;
|
||||
Colors _colors;
|
||||
|
||||
friend class MacStatsGraph;
|
||||
friend class MacStatsServer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,650 @@
|
|||
/**
|
||||
* 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 macStatsMonitor.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#include "macStatsMonitor.h"
|
||||
#include "macStats.h"
|
||||
#include "macStatsServer.h"
|
||||
#include "macStatsStripChart.h"
|
||||
#include "macStatsChartMenu.h"
|
||||
#include "macStatsPianoRoll.h"
|
||||
#include "macStatsFlameGraph.h"
|
||||
#include "macStatsTimeline.h"
|
||||
#include "pStatGraph.h"
|
||||
#include "pStatCollectorDef.h"
|
||||
|
||||
#include "convert_srgb.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsMonitor::
|
||||
MacStatsMonitor(MacStatsServer *server) : PStatMonitor(server) {
|
||||
_main_menu = NSApp.mainMenu;
|
||||
|
||||
// These will be filled in later when the menu is created.
|
||||
_scroll_speed = 0.0;
|
||||
_pause = false;
|
||||
_next_chart_index = 2;
|
||||
|
||||
setup_speed_menu();
|
||||
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowStatusItem"]) {
|
||||
set_show_status_item(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsMonitor::
|
||||
~MacStatsMonitor() {
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all the graphs associated with this monitor.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
close() {
|
||||
PStatMonitor::close();
|
||||
|
||||
if (_notification != nil) {
|
||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||
[center removeDeliveredNotification:_notification];
|
||||
[_notification release];
|
||||
_notification = nil;
|
||||
}
|
||||
|
||||
close_all_graphs();
|
||||
|
||||
if (_speed_menu_item != nullptr) {
|
||||
[_main_menu removeItem:_speed_menu_item];
|
||||
[_speed_menu_item release];
|
||||
_speed_menu_item = nullptr;
|
||||
}
|
||||
|
||||
for (MacStatsChartMenu *chart_menu : _chart_menus) {
|
||||
chart_menu->remove_from_menu(_main_menu);
|
||||
delete chart_menu;
|
||||
}
|
||||
_chart_menus.clear();
|
||||
|
||||
set_show_status_item(false);
|
||||
|
||||
for (auto &item : _colors) {
|
||||
CGColorRelease(item.second._bg[0]);
|
||||
CGColorRelease(item.second._bg[1]);
|
||||
}
|
||||
_colors.clear();
|
||||
|
||||
_next_chart_index = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be redefined to return a descriptive name for the type of
|
||||
* PStatsMonitor this is.
|
||||
*/
|
||||
std::string MacStatsMonitor::
|
||||
get_monitor_name() {
|
||||
return "MacStats";
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the monitor has been fully set up. At this time, it will have
|
||||
* a valid _client_data pointer, and things like is_alive() and close() will
|
||||
* be meaningful. However, we may not yet know who we're connected to
|
||||
* (is_client_known() may return false), and we may not know anything about
|
||||
* the threads or collectors we're about to get data on.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
initialized() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the "hello" message has been received from the client. At this
|
||||
* time, the client's hostname and program name will be known.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
got_hello() {
|
||||
std::string progname = get_client_progname();
|
||||
std::string hostname = get_client_hostname();
|
||||
|
||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||
_notification = [[NSUserNotification alloc] init];
|
||||
_notification.title = @"PStats Server";
|
||||
_notification.informativeText = [NSString
|
||||
stringWithFormat:@"Connected to %s on %s", progname.c_str(), hostname.c_str()];
|
||||
|
||||
[center deliverNotification:_notification];
|
||||
}
|
||||
|
||||
/**
|
||||
* Like got_hello(), this is called when the "hello" message has been received
|
||||
* from the client. At this time, the client's hostname and program name will
|
||||
* be known. However, the client appears to be an incompatible version and
|
||||
* the connection will be terminated; the monitor should issue a message to
|
||||
* that effect.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
got_bad_version(int client_major, int client_minor,
|
||||
int server_major, int server_minor) {
|
||||
std::ostringstream str;
|
||||
str << "Unable to honor connection attempt from "
|
||||
<< get_client_progname() << " on " << get_client_hostname()
|
||||
<< ": unsupported PStats version "
|
||||
<< client_major << "." << client_minor;
|
||||
|
||||
if (server_minor == 0) {
|
||||
str << " (server understands version " << server_major
|
||||
<< "." << server_minor << " only).";
|
||||
} else {
|
||||
str << " (server understands versions " << server_major
|
||||
<< ".0 through " << server_major << "." << server_minor << ").";
|
||||
}
|
||||
|
||||
std::string message = str.str();
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"PStats Error";
|
||||
alert.informativeText = [NSString stringWithUTF8String:message.c_str()];
|
||||
alert.alertStyle = NSCriticalAlertStyle;
|
||||
[alert runModal];
|
||||
[alert release];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a new Collector definition is received from the client.
|
||||
* Generally, the client will send all of its collectors over shortly after
|
||||
* connecting, but there's no guarantee that they will all be received before
|
||||
* the first frames are received. The monitor should be prepared to accept
|
||||
* new Collector definitions midstream.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
new_collector(int collector_index) {
|
||||
for (MacStatsGraph *graph : _graphs) {
|
||||
graph->new_collector(collector_index);
|
||||
}
|
||||
|
||||
// We might need to update our menus.
|
||||
for (MacStatsChartMenu *chart_menu : _chart_menus) {
|
||||
chart_menu->check_update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a new Thread definition is received from the client.
|
||||
* Generally, the client will send all of its threads over shortly after
|
||||
* connecting, but there's no guarantee that they will all be received before
|
||||
* the first frames are received. The monitor should be prepared to accept
|
||||
* new Thread definitions midstream.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
new_thread(int thread_index) {
|
||||
MacStatsChartMenu *chart_menu = new MacStatsChartMenu(this, thread_index);
|
||||
chart_menu->add_to_menu(_main_menu, _next_chart_index);
|
||||
++_next_chart_index;
|
||||
_chart_menus.push_back(chart_menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 MacStatsMonitor::
|
||||
new_data(int thread_index, int frame_number) {
|
||||
for (MacStatsGraph *graph : _graphs) {
|
||||
graph->new_data(thread_index, frame_number);
|
||||
}
|
||||
|
||||
if (!_have_data) {
|
||||
open_default_graphs();
|
||||
_have_data = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a thread should be removed from the list of threads.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
remove_thread(int thread_index) {
|
||||
for (ChartMenus::iterator it = _chart_menus.begin(); it != _chart_menus.end(); ++it) {
|
||||
MacStatsChartMenu *chart_menu = *it;
|
||||
if (chart_menu->get_thread_index() == thread_index) {
|
||||
chart_menu->remove_from_menu(_main_menu);
|
||||
delete chart_menu;
|
||||
_chart_menus.erase(it);
|
||||
--_next_chart_index;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the connection to the client has been lost. This is a
|
||||
* permanent state change. The monitor should update its display to represent
|
||||
* this, and may choose to close down automatically.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
lost_connection() {
|
||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||
|
||||
if (_notification != nil) {
|
||||
[center removeDeliveredNotification:_notification];
|
||||
[_notification release];
|
||||
}
|
||||
|
||||
std::string hostname = get_client_hostname();
|
||||
|
||||
_notification = [[NSUserNotification alloc] init];
|
||||
_notification.title = @"PStats Server";
|
||||
_notification.informativeText = [NSString
|
||||
stringWithFormat:@"Lost connection to %s", hostname.c_str()];
|
||||
|
||||
_notification.additionalActions = @[
|
||||
[NSUserNotificationAction actionWithIdentifier:@"new" title:@"New Session"],
|
||||
[NSUserNotificationAction actionWithIdentifier:@"quit" title:@"Quit PStats"]
|
||||
];
|
||||
|
||||
[center deliverNotification:_notification];
|
||||
}
|
||||
|
||||
/**
|
||||
* If has_idle() returns true, this will be called periodically to allow the
|
||||
* monitor to update its display or whatever it needs to do.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
idle() {
|
||||
// Check if any of our chart menus need updating.
|
||||
for (MacStatsChartMenu *chart_menu : _chart_menus) {
|
||||
chart_menu->check_update();
|
||||
}
|
||||
|
||||
// Update the frame rate label from the main thread (thread 0).
|
||||
if (_frame_rate_status_item != nil) {
|
||||
const PStatThreadData *thread_data = get_client_data()->get_thread_data(0);
|
||||
double frame_rate = thread_data->get_frame_rate();
|
||||
if (frame_rate != 0.0f) {
|
||||
_frame_rate_status_item.button.title =
|
||||
[NSString stringWithFormat:@"%0.1f ms / %0.1f Hz", 1000.0f / frame_rate, frame_rate];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be redefined to return true if you want to redefine idle() and
|
||||
* expect it to be called.
|
||||
*/
|
||||
bool MacStatsMonitor::
|
||||
has_idle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user guide bars have been changed.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
user_guide_bars_changed() {
|
||||
for (MacStatsGraph *graph : _graphs) {
|
||||
graph->user_guide_bars_changed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new timeline.
|
||||
*/
|
||||
PStatGraph *MacStatsMonitor::
|
||||
open_timeline() {
|
||||
MacStatsTimeline *graph = new MacStatsTimeline(this);
|
||||
add_graph(graph);
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new flame graph showing the indicated data.
|
||||
*/
|
||||
PStatGraph *MacStatsMonitor::
|
||||
open_flame_graph(int thread_index, int collector_index) {
|
||||
MacStatsFlameGraph *graph =
|
||||
new MacStatsFlameGraph(this, thread_index, collector_index);
|
||||
add_graph(graph);
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new strip chart showing the indicated data.
|
||||
*/
|
||||
PStatGraph *MacStatsMonitor::
|
||||
open_strip_chart(int thread_index, int collector_index, bool show_level) {
|
||||
MacStatsStripChart *graph =
|
||||
new MacStatsStripChart(this, thread_index, collector_index, show_level);
|
||||
add_graph(graph);
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new piano roll showing the indicated data.
|
||||
*/
|
||||
PStatGraph *MacStatsMonitor::
|
||||
open_piano_roll(int thread_index) {
|
||||
MacStatsPianoRoll *graph = new MacStatsPianoRoll(this, thread_index);
|
||||
add_graph(graph);
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color suitable for drawing the indicated collector.
|
||||
*/
|
||||
CGColorRef MacStatsMonitor::
|
||||
get_collector_color(int collector_index, bool highlight) {
|
||||
Colors::iterator bi;
|
||||
bi = _colors.find(collector_index);
|
||||
if (bi != _colors.end()) {
|
||||
return (*bi).second._bg[highlight];
|
||||
}
|
||||
|
||||
LRGBColor rgb = PStatMonitor::get_collector_color(collector_index);
|
||||
rgb[0] = encode_sRGB_float(rgb[0]);
|
||||
rgb[1] = encode_sRGB_float(rgb[1]);
|
||||
rgb[2] = encode_sRGB_float(rgb[2]);
|
||||
|
||||
PN_stdfloat bright = rgb.dot(LRGBColor(0.2126, 0.7152, 0.0722));
|
||||
CGColorRef color = CGColorCreateGenericRGB(rgb[0], rgb[1], rgb[2], 1.0);
|
||||
|
||||
rgb *= 0.75;
|
||||
CGColorRef hcolor = CGColorCreateGenericRGB(rgb[0], rgb[1], rgb[2], 1.0);
|
||||
|
||||
ColorSet &set = _colors[collector_index];
|
||||
set._bg[0] = color;
|
||||
set._bg[1] = hcolor;
|
||||
|
||||
if (bright >= 0.5) {
|
||||
set._fg[0] = [NSColor blackColor];
|
||||
} else {
|
||||
set._fg[0] = [NSColor whiteColor];
|
||||
}
|
||||
|
||||
if (bright * 0.75 >= 0.5) {
|
||||
set._fg[1] = [NSColor blackColor];
|
||||
} else {
|
||||
set._fg[1] = [NSColor whiteColor];
|
||||
}
|
||||
|
||||
return highlight ? hcolor : color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color suitable for drawing text on the indicated collector.
|
||||
*/
|
||||
NSColor *MacStatsMonitor::
|
||||
get_collector_text_color(int collector_index, bool highlight) {
|
||||
get_collector_color(collector_index, false);
|
||||
return _colors[collector_index]._fg[highlight];
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a dialog to change the given collector color.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
choose_collector_color(int collector_index) {
|
||||
_choosing_color_collector_index = collector_index;
|
||||
|
||||
const LRGBColor &rgb = PStatMonitor::get_collector_color(collector_index);
|
||||
NSColor *color = [NSColor colorWithSRGBRed:rgb[0] green:rgb[1] blue:rgb[2] alpha:1.0];
|
||||
|
||||
NSColorPanel *panel = [NSColorPanel sharedColorPanel];
|
||||
panel.target = [NSApp delegate];
|
||||
panel.action = @selector(handleChooseCollectorColor:);
|
||||
panel.showsAlpha = NO;
|
||||
panel.color = color;
|
||||
[NSApp orderFrontColorPanel:panel];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom color associated with the given collector.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
handle_choose_collector_color(const LRGBColor &color) {
|
||||
int collector_index = _choosing_color_collector_index;
|
||||
|
||||
PStatMonitor::set_collector_color(collector_index, color);
|
||||
|
||||
if (_colors.erase(collector_index)) {
|
||||
for (MacStatsGraph *graph : _graphs) {
|
||||
graph->reset_collector_color(collector_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the color of the given collector to the default.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
reset_collector_color(int collector_index) {
|
||||
PStatMonitor::clear_collector_color(collector_index);
|
||||
|
||||
if (_colors.erase(collector_index)) {
|
||||
for (MacStatsGraph *graph : _graphs) {
|
||||
graph->reset_collector_color(collector_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the frame rate label on the right end of the menu bar. This is
|
||||
* used as a text label to display the main thread's frame rate to the user,
|
||||
* although it is implemented as a right-justified toplevel menu item that
|
||||
* doesn't open to anything.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
set_show_status_item(bool show) {
|
||||
if (show && _frame_rate_status_item == nil) {
|
||||
_frame_rate_status_item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
|
||||
_frame_rate_status_item.button.action = @selector(handleClickStatusItem:);
|
||||
[_frame_rate_status_item retain];
|
||||
|
||||
const PStatClientData *client_data = get_client_data();
|
||||
if (client_data != nullptr) {
|
||||
const PStatThreadData *thread_data = client_data->get_thread_data(0);
|
||||
if (thread_data != nullptr) {
|
||||
double frame_rate = thread_data->get_frame_rate();
|
||||
if (frame_rate != 0.0f) {
|
||||
_frame_rate_status_item.button.title =
|
||||
[NSString stringWithFormat:@"%0.1f ms / %0.1f Hz", 1000.0f / frame_rate, frame_rate];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!show && _frame_rate_status_item != nil) {
|
||||
// Careful: for some reason, this can get called recursively.
|
||||
NSStatusItem *status_item = _frame_rate_status_item;
|
||||
_frame_rate_status_item = nil;
|
||||
[[NSStatusBar systemStatusBar] removeStatusItem:status_item];
|
||||
[status_item release];
|
||||
status_item = nil;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user selects a new time units from the monitor pulldown
|
||||
* menu, this should adjust the units for all graphs to the indicated mask if
|
||||
* it is a time-based graph.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
set_time_units(int unit_mask) {
|
||||
for (MacStatsGraph *graph : _graphs) {
|
||||
graph->set_time_units(unit_mask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user selects a new scroll speed from the monitor pulldown
|
||||
* menu, this should adjust the speeds for all graphs to the indicated value.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
set_scroll_speed(double scroll_speed) {
|
||||
_scroll_speed = scroll_speed;
|
||||
|
||||
// First, change all of the open graphs appropriately.
|
||||
for (MacStatsGraph *graph : _graphs) {
|
||||
graph->set_scroll_speed(_scroll_speed);
|
||||
}
|
||||
|
||||
// Then, change the state of the menu items.
|
||||
_speed_menu_item_1.state = (scroll_speed == 1.0);
|
||||
_speed_menu_item_2.state = (scroll_speed == 2.0);
|
||||
_speed_menu_item_3.state = (scroll_speed == 3.0);
|
||||
_speed_menu_item_6.state = (scroll_speed == 6.0);
|
||||
_speed_menu_item_12.state = (scroll_speed == 12.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user selects a pause on or pause off option from the menu.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
set_pause(bool pause) {
|
||||
_pause = pause;
|
||||
|
||||
// First, change all of the open graphs appropriately.
|
||||
for (MacStatsGraph *graph : _graphs) {
|
||||
graph->set_pause(_pause);
|
||||
}
|
||||
|
||||
// Then, change the state of the menu item.
|
||||
_speed_menu_item_pause.state = pause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the newly-created graph to the list of managed graphs.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
add_graph(MacStatsGraph *graph) {
|
||||
_graphs.insert(graph);
|
||||
|
||||
int units = [[NSUserDefaults standardUserDefaults] integerForKey:@"TimeUnits"];
|
||||
graph->set_time_units(units);
|
||||
graph->set_scroll_speed(_scroll_speed);
|
||||
graph->set_pause(_pause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the indicated graph.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
remove_graph(MacStatsGraph *graph) {
|
||||
Graphs::iterator gi = _graphs.find(graph);
|
||||
if (gi != _graphs.end()) {
|
||||
_graphs.erase(gi);
|
||||
delete graph;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks all open graphs to close.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
close_all_graphs() {
|
||||
while (!_graphs.empty()) {
|
||||
(*_graphs.begin())->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the "Speed" pulldown menu.
|
||||
*/
|
||||
void MacStatsMonitor::
|
||||
setup_speed_menu() {
|
||||
NSMenu *menu = [[NSMenu alloc] init];
|
||||
menu.title = @"Speed";
|
||||
menu.autoenablesItems = NO;
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSpeed:);
|
||||
item.title = @"1";
|
||||
item.tag = 1;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
|
||||
_speed_menu_item_1 = item;
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSpeed:);
|
||||
item.title = @"2";
|
||||
item.tag = 2;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
|
||||
_speed_menu_item_2 = item;
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSpeed:);
|
||||
item.title = @"3";
|
||||
item.tag = 3;
|
||||
item.state = NSOnState;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
|
||||
_speed_menu_item_3 = item;
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSpeed:);
|
||||
item.title = @"6";
|
||||
item.tag = 6;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
|
||||
_speed_menu_item_6 = item;
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSpeed:);
|
||||
item.title = @"12";
|
||||
item.tag = 12;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
|
||||
_speed_menu_item_12 = item;
|
||||
}
|
||||
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handlePause:);
|
||||
item.title = @"pause";
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
|
||||
_speed_menu_item_pause = item;
|
||||
}
|
||||
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
_speed_menu_item = item;
|
||||
[_main_menu addItem:item];
|
||||
[_main_menu setSubmenu:menu forItem:item];
|
||||
[menu release];
|
||||
|
||||
set_scroll_speed(3);
|
||||
set_pause(false);
|
||||
|
||||
++_next_chart_index;
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* 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 macStatsPianoRoll.h
|
||||
* @author rdb
|
||||
* @date 2023-08-19
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSPIANOROLL_H
|
||||
#define MACSTATSPIANOROLL_H
|
||||
|
||||
#include "macStatsGraph.h"
|
||||
#include "pStatPianoRoll.h"
|
||||
#include "macStatsChartMenuDelegate.h"
|
||||
|
||||
class MacStatsMonitor;
|
||||
|
||||
/**
|
||||
* A window that draws a piano-roll style chart, which shows the collectors
|
||||
* explicitly stopping and starting, one frame at a time.
|
||||
*/
|
||||
class MacStatsPianoRoll final : public PStatPianoRoll, public MacStatsGraph {
|
||||
public:
|
||||
MacStatsPianoRoll(MacStatsMonitor *monitor, int thread_index);
|
||||
virtual ~MacStatsPianoRoll();
|
||||
|
||||
virtual void new_data(int thread_index, int frame_number);
|
||||
virtual void force_redraw();
|
||||
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
|
||||
|
||||
virtual void set_time_units(int unit_mask);
|
||||
virtual void on_click_label(int collector_index);
|
||||
virtual NSMenu *get_label_menu(int collector_index) const;
|
||||
virtual std::string get_label_tooltip(int collector_index) const;
|
||||
void set_horizontal_scale(double time_width);
|
||||
|
||||
protected:
|
||||
void clear_region();
|
||||
virtual void begin_draw();
|
||||
virtual void begin_row(int row);
|
||||
virtual void draw_bar(int row, int from_x, int to_x);
|
||||
virtual void end_draw();
|
||||
virtual void idle();
|
||||
|
||||
virtual bool get_window_state(int &x, int &y, int &width, int &height,
|
||||
bool &maximized, bool &minimized) const;
|
||||
virtual void set_window_state(int x, int y, int width, int height,
|
||||
bool maximized, bool minimized);
|
||||
|
||||
virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const;
|
||||
virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const;
|
||||
virtual DragMode consider_drag_start(int graph_x, int graph_y);
|
||||
|
||||
virtual void handle_button_press(int graph_x, int graph_y,
|
||||
bool double_click, int button);
|
||||
virtual void handle_button_release(int graph_x, int graph_y);
|
||||
virtual void handle_motion(int graph_x, int graph_y);
|
||||
virtual void handle_leave();
|
||||
virtual void handle_scroll();
|
||||
virtual void handle_magnify(int graph_x, int graph_y, double scale);
|
||||
virtual void handle_draw_graph(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_draw_graph_overhang(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_draw_scale_area(CGContextRef ctx, NSRect rect);
|
||||
|
||||
private:
|
||||
int get_collector_under_pixel(int xpoint, int ypoint) const;
|
||||
void update_labels();
|
||||
void draw_guide_bars(CGContextRef ctx, int y, int height);
|
||||
void draw_guide_bar(CGContextRef ctx, const PStatGraph::GuideBar &bar,
|
||||
int y, int height);
|
||||
void draw_guide_labels(CGContextRef ctx);
|
||||
void draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar);
|
||||
|
||||
private:
|
||||
MacStatsChartMenuDelegate *_menu_delegate;
|
||||
NSScrollView *_sidebar_scroll_view;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,764 @@
|
|||
/**
|
||||
* 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 macStatsPianoRoll.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-19
|
||||
*/
|
||||
|
||||
#include "macStatsPianoRoll.h"
|
||||
#include "macStatsMonitor.h"
|
||||
#include "macStatsLabelStack.h"
|
||||
#include "macStatsScaleArea.h"
|
||||
|
||||
static const int default_piano_roll_width = 800;
|
||||
static const int default_piano_roll_height = 400;
|
||||
|
||||
static const int minimum_piano_roll_sidebar_width = 68;
|
||||
static const int default_piano_roll_sidebar_width = 200;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsPianoRoll::
|
||||
MacStatsPianoRoll(MacStatsMonitor *monitor, int thread_index) :
|
||||
PStatPianoRoll(monitor, thread_index, 0, 0),
|
||||
MacStatsGraph(monitor, [MacStatsScrollableGraphViewController alloc])
|
||||
{
|
||||
// Used for popup menus.
|
||||
_menu_delegate = [[MacStatsChartMenuDelegate alloc] initWithMonitor:monitor threadIndex:thread_index];
|
||||
|
||||
// Let's show the units on the guide bar labels. There's room.
|
||||
set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
|
||||
|
||||
const PStatClientData *client_data =
|
||||
MacStatsGraph::_monitor->get_client_data();
|
||||
std::string thread_name = client_data->get_thread_name(_thread_index);
|
||||
std::string window_title = thread_name + " thread piano roll";
|
||||
_window.title = [NSString stringWithUTF8String:window_title.c_str()];
|
||||
|
||||
if (@available(macOS 11.0, *)) {
|
||||
_window.titleVisibility = NSWindowTitleHidden;
|
||||
}
|
||||
|
||||
// Set the initial size of the graph.
|
||||
_graph_view.frame = NSMakeRect(0, 0, default_piano_roll_width, default_piano_roll_height);
|
||||
_graph_view_controller.view.frame = NSMakeRect(0, 0, default_piano_roll_width, default_piano_roll_height);
|
||||
|
||||
// It's put inside a scroll view that tracks the main scroll view.
|
||||
NSScrollView *scroll_view = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, default_piano_roll_sidebar_width, 0)];
|
||||
scroll_view.documentView = _label_stack.get_view();
|
||||
scroll_view.drawsBackground = NO;
|
||||
scroll_view.automaticallyAdjustsContentInsets = YES;
|
||||
//scroll_view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
scroll_view.hasHorizontalScroller = NO;
|
||||
scroll_view.hasVerticalScroller = NO;
|
||||
_sidebar_scroll_view = scroll_view;
|
||||
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
[center addObserver:_graph_view_controller
|
||||
selector:@selector(handleSideScroll:)
|
||||
name:NSScrollViewDidLiveScrollNotification
|
||||
object:scroll_view];
|
||||
|
||||
NSViewController *sidebar_controller = [[NSViewController alloc] init];
|
||||
sidebar_controller.view = scroll_view;
|
||||
|
||||
NSSplitViewController *svc = [[NSSplitViewController alloc] init];
|
||||
[svc addSplitViewItem:[NSSplitViewItem sidebarWithViewController:sidebar_controller]];
|
||||
[svc addSplitViewItem:[NSSplitViewItem splitViewItemWithViewController:_graph_view_controller]];
|
||||
|
||||
svc.splitViewItems[0].minimumThickness = minimum_piano_roll_sidebar_width;
|
||||
svc.splitViewItems[0].canCollapse = NO;
|
||||
|
||||
NSSplitView *split_view = svc.splitView;
|
||||
split_view.vertical = YES;
|
||||
split_view.dividerStyle = NSSplitViewDividerStyleThin;
|
||||
split_view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
_split_view = split_view;
|
||||
|
||||
_window.contentViewController = svc;
|
||||
|
||||
[svc release];
|
||||
[sidebar_controller release];
|
||||
|
||||
// Scale area goes on top, as a titlebar accessory view.
|
||||
MacStatsScaleAreaController *scale_area_controller = [[MacStatsScaleAreaController alloc] initWithGraph:this];
|
||||
scale_area_controller.fullScreenMinHeight = 20;
|
||||
scale_area_controller.layoutAttribute = NSLayoutAttributeRight;
|
||||
_scale_area = scale_area_controller.view;
|
||||
[_window addTitlebarAccessoryViewController:scale_area_controller];
|
||||
[scale_area_controller release];
|
||||
|
||||
[_label_stack.get_view().widthAnchor constraintEqualToAnchor:scroll_view.widthAnchor].active = YES;
|
||||
[_label_stack.get_view().heightAnchor constraintEqualToAnchor:_graph_view.heightAnchor].active = YES;
|
||||
|
||||
idle();
|
||||
|
||||
[_window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsPianoRoll::
|
||||
~MacStatsPianoRoll() {
|
||||
[_sidebar_scroll_view release];
|
||||
[_menu_delegate release];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 MacStatsPianoRoll::
|
||||
new_data(int thread_index, int frame_number) {
|
||||
if (!_pause) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when it is necessary to redraw the entire graph.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
force_redraw() {
|
||||
if (_ctx) {
|
||||
PStatPianoRoll::force_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user has resized the window, forcing a resize of the graph.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
changed_graph_size(int graph_xsize, int graph_ysize) {
|
||||
PStatPianoRoll::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 MacStatsPianoRoll::
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user single-clicks on a label.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
on_click_label(int collector_index) {
|
||||
if (collector_index >= 0) {
|
||||
MacStatsGraph::_monitor->open_strip_chart(_thread_index, collector_index, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse right-clicks on a label, and should return the menu
|
||||
* that should pop up.
|
||||
*/
|
||||
NSMenu *MacStatsPianoRoll::
|
||||
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:@"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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse hovers over a label, and should return the text that
|
||||
* should appear on the tooltip.
|
||||
*/
|
||||
std::string MacStatsPianoRoll::
|
||||
get_label_tooltip(int collector_index) const {
|
||||
return PStatPianoRoll::get_label_tooltip(collector_index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the amount of time the width of the horizontal axis represents.
|
||||
* This may force a redraw.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
set_horizontal_scale(double time_width) {
|
||||
PStatPianoRoll::set_horizontal_scale(time_width);
|
||||
|
||||
_graph_view.needsDisplay = YES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erases the chart area.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
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 MacStatsPianoRoll::
|
||||
begin_draw() {
|
||||
clear_region();
|
||||
|
||||
// Draw in the guide bars.
|
||||
CGContextSetStrokeColorWithColor(_ctx, [NSColor gridColor].CGColor);
|
||||
int num_guide_bars = get_num_guide_bars();
|
||||
for (int i = 0; i < num_guide_bars; ++i) {
|
||||
draw_guide_bar(_ctx, get_guide_bar(i), 0, get_ysize());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be overridden by the user class. This hook will be called before
|
||||
* drawing any one row of bars. These bars correspond to the collector whose
|
||||
* index is get_row_collector(row), and in the color get_row_color(row).
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
begin_row(int row) {
|
||||
int collector_index = get_label_collector(row);
|
||||
bool is_highlighted = collector_index == _highlighted_index;
|
||||
CGContextSetFillColorWithColor(_ctx,
|
||||
MacStatsGraph::_monitor->get_collector_color(collector_index, is_highlighted));
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a single bar on the chart.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
draw_bar(int row, int from_x, int to_x) {
|
||||
if (row >= 0 && row < _label_stack.get_num_labels()) {
|
||||
int y = _label_stack.get_label_y(row, _graph_view);
|
||||
int height = _label_stack.get_label_height(row);
|
||||
|
||||
CGContextFillRect(_ctx, CGRectMake(from_x, (y - height + 2), to_x - from_x, (height - 4)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after all the bars have been drawn, this triggers a refresh event to
|
||||
* draw it to the window.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
end_draw() {
|
||||
_graph_view.needsDisplay = YES;
|
||||
|
||||
if (_guide_bars_changed) {
|
||||
_scale_area.needsDisplay = YES;
|
||||
_guide_bars_changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of the draw cycle.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
idle() {
|
||||
if (_labels_changed) {
|
||||
update_labels();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current window dimensions.
|
||||
*/
|
||||
bool MacStatsPianoRoll::
|
||||
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 MacStatsPianoRoll::
|
||||
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 *MacStatsPianoRoll::
|
||||
get_graph_menu(int mouse_x, int mouse_y) const {
|
||||
int collector_index = get_collector_under_pixel(mouse_x, mouse_y);
|
||||
if (collector_index >= 0) {
|
||||
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 MacStatsPianoRoll::
|
||||
get_graph_tooltip(int mouse_x, int mouse_y) const {
|
||||
int collector_index = get_collector_under_pixel(mouse_x, mouse_y);
|
||||
if (collector_index >= 0) {
|
||||
return get_label_tooltip(collector_index);
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the mouse position within the graph window, look for draggable
|
||||
* things the mouse might be hovering over and return the appropriate DragMode
|
||||
* enum or DM_none if nothing is indicated.
|
||||
*/
|
||||
MacStatsGraph::DragMode MacStatsPianoRoll::
|
||||
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 MacStatsGraph::consider_drag_start(graph_x, graph_y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse button is depressed within the graph window.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
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()) {
|
||||
if (double_click && button == 1) {
|
||||
// Double-clicking on a color bar in the graph is the same as double-
|
||||
// clicking on the corresponding label.
|
||||
on_click_label(get_collector_under_pixel(graph_x, graph_y));
|
||||
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 MacStatsPianoRoll::
|
||||
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 MacStatsPianoRoll::
|
||||
handle_motion(int graph_x, int graph_y) {
|
||||
if (_drag_mode == DM_none && _potential_drag_mode == DM_none) {
|
||||
// When the mouse is over a color bar, highlight it.
|
||||
int collector_index = get_collector_under_pixel(graph_x, graph_y);
|
||||
_label_stack.highlight_label(collector_index);
|
||||
on_enter_label(collector_index);
|
||||
|
||||
/*
|
||||
// Now we want to get a WM_MOUSELEAVE when the mouse leaves the graph
|
||||
// window.
|
||||
TRACKMOUSEEVENT tme = {
|
||||
sizeof(TRACKMOUSEEVENT),
|
||||
TME_LEAVE,
|
||||
_graph_window,
|
||||
0
|
||||
};
|
||||
TrackMouseEvent(&tme);
|
||||
*/
|
||||
}
|
||||
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_scale) {
|
||||
double ratio = (double)graph_x / (double)get_xsize();
|
||||
if (ratio > 0.0f) {
|
||||
set_horizontal_scale(_drag_scale_start / ratio);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else 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 MacStatsPianoRoll::
|
||||
handle_leave() {
|
||||
_label_stack.highlight_label(-1);
|
||||
on_leave_label(_highlighted_index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse is moved within the graph window.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
handle_scroll() {
|
||||
// Graph view is flipped, side bar isn't, so we need to convert coordinates
|
||||
NSPoint point;
|
||||
point.x = 0;
|
||||
point.y = _graph_view.frame.size.height - (((NSScrollView *)_graph_view_controller.view).documentVisibleRect.size.height + ((NSScrollView *)_graph_view_controller.view).documentVisibleRect.origin.y);
|
||||
[_sidebar_scroll_view.contentView scrollToPoint:point];
|
||||
[_sidebar_scroll_view reflectScrolledClipView:_sidebar_scroll_view.contentView];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
handle_magnify(int graph_x, int graph_y, double scale) {
|
||||
set_horizontal_scale(get_horizontal_scale() * (1.0 - scale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the graph window.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
handle_draw_graph(CGContextRef ctx, NSRect rect) {
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
|
||||
draw_guide_bars(ctx, rect.origin.y, rect.size.height);
|
||||
#endif
|
||||
|
||||
// Copy the drawn bars into the graph.
|
||||
MacStatsGraph::handle_draw_graph(ctx, rect);
|
||||
|
||||
// Draw the scale area.
|
||||
/*
|
||||
CGContextSetRGBStrokeColor(ctx, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2], 1.0);
|
||||
CGContextSetStrokeColor(ctx, rgb_dark_gray);
|
||||
CGContextBeginPath(ctx);
|
||||
CGContextMoveToPoint(ctx, 0, header_height);
|
||||
CGContextAddLineToPoint(ctx, get_xsize(), header_height);
|
||||
CGContextStrokePath(ctx);*/
|
||||
|
||||
int num_user_guide_bars = get_num_user_guide_bars();
|
||||
for (int i = 0; i < num_user_guide_bars; ++i) {
|
||||
draw_guide_bar(ctx, get_user_guide_bar(i), rect.origin.y, rect.size.height);
|
||||
}
|
||||
|
||||
NSRect scale_frame = _scale_area.frame;
|
||||
NSRect graph_frame = _graph_view.frame;
|
||||
if (scale_frame.size.width != graph_frame.size.width) {
|
||||
scale_frame.size.width = graph_frame.size.width;
|
||||
_scale_area.frame = scale_frame;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the graph window overhang, which is the area outside the graph
|
||||
* bounds that may become visible momentarily due to scroll elasticity.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
handle_draw_graph_overhang(CGContextRef ctx, NSRect rect) {
|
||||
CGContextSetFillColorWithColor(ctx, _background_color);
|
||||
CGContextFillRect(ctx, rect);
|
||||
|
||||
draw_guide_bars(ctx, rect.origin.y, rect.size.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the scale area.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
handle_draw_scale_area(CGContextRef ctx, NSRect rect) {
|
||||
/*
|
||||
CGContextSetRGBStrokeColor(ctx, rgb_dark_gray[0], rgb_dark_gray[1], rgb_dark_gray[2], 1.0);
|
||||
CGContextSetStrokeColor(ctx, rgb_dark_gray);
|
||||
CGContextBeginPath(ctx);
|
||||
CGContextMoveToPoint(ctx, 0, header_height);
|
||||
CGContextAddLineToPoint(ctx, get_xsize(), header_height);
|
||||
CGContextStrokePath(ctx);*/
|
||||
|
||||
draw_guide_bars(ctx, rect.origin.y, rect.size.height);
|
||||
draw_guide_labels(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collector index associated with the indicated vertical row, or
|
||||
* -1.
|
||||
*/
|
||||
int MacStatsPianoRoll::
|
||||
get_collector_under_pixel(int xpoint, int ypoint) const {
|
||||
if (_label_stack.get_num_labels() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Assume all of the labels are the same height.
|
||||
int origin = _label_stack.get_label_y(0, _graph_view);
|
||||
int height = _label_stack.get_label_height(0);
|
||||
int row = (origin - ypoint) / height;
|
||||
if (row >= 0 && row < _label_stack.get_num_labels()) {
|
||||
return _label_stack.get_label_collector_index(row);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the list of labels.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
update_labels() {
|
||||
_label_stack.clear_labels();
|
||||
for (int i = 0; i < get_num_labels(); ++i) {
|
||||
_label_stack.add_label(MacStatsGraph::_monitor, this,
|
||||
_thread_index,
|
||||
get_label_collector(i), true);
|
||||
}
|
||||
_labels_changed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
draw_guide_bars(CGContextRef ctx, int y, int height) {
|
||||
CGContextSetStrokeColorWithColor(ctx, [NSColor gridColor].CGColor);
|
||||
|
||||
int num_guide_bars = get_num_guide_bars();
|
||||
for (int i = 0; i < num_guide_bars; ++i) {
|
||||
draw_guide_bar(ctx, get_guide_bar(i), y, height);
|
||||
}
|
||||
|
||||
int num_user_guide_bars = get_num_user_guide_bars();
|
||||
for (int i = 0; i < num_user_guide_bars; ++i) {
|
||||
draw_guide_bar(ctx, get_user_guide_bar(i), y, height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the line for the indicated guide bar on the graph.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
draw_guide_bar(CGContextRef ctx, const PStatGraph::GuideBar &bar,
|
||||
int y, int height) {
|
||||
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.
|
||||
/*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, y);
|
||||
CGContextAddLineToPoint(ctx, x, y + height);
|
||||
CGContextStrokePath(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called during the servicing of the draw event.
|
||||
*/
|
||||
void MacStatsPianoRoll::
|
||||
draw_guide_labels(CGContextRef ctx) {
|
||||
int i;
|
||||
int num_guide_bars = get_num_guide_bars();
|
||||
for (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 (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 MacStatsPianoRoll::
|
||||
draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar) {
|
||||
NSColor *color;
|
||||
if (@available(macOS 11.0, *)) {
|
||||
color = [NSColor tertiaryLabelColor];
|
||||
} else {
|
||||
// Otherwise it's hard to see on the dark titlebars
|
||||
color = [NSColor windowFrameTextColor];
|
||||
}
|
||||
/*
|
||||
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()) {
|
||||
CGContextSetTextPosition(ctx, x + 6, 6);
|
||||
CTLineDraw(line, ctx);
|
||||
}
|
||||
|
||||
CFRelease(line);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* 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 macStatsScaleArea.h
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSSCALEAREA_H
|
||||
#define MACSTATSSCALEAREA_H
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
class MacStatsGraph;
|
||||
|
||||
@interface MacStatsScaleArea : NSView {
|
||||
@private
|
||||
MacStatsGraph *_graph;
|
||||
}
|
||||
|
||||
- (id)initWithGraph:(MacStatsGraph *)graph frame:(NSRect)rect;
|
||||
- (void)drawRect:(NSRect)dirtyRect;
|
||||
|
||||
@end
|
||||
|
||||
@interface MacStatsScaleAreaController : NSTitlebarAccessoryViewController {
|
||||
@private
|
||||
MacStatsGraph *_graph;
|
||||
}
|
||||
|
||||
- (id)initWithGraph:(MacStatsGraph *)graph;
|
||||
- (void)loadView;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 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 macStatsScaleArea.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#include "macStatsScaleArea.h"
|
||||
#include "macStatsGraph.h"
|
||||
#include "macStatsStripChart.h"
|
||||
|
||||
@implementation MacStatsScaleArea
|
||||
|
||||
- (id)initWithGraph:(MacStatsGraph *)graph frame:(NSRect)rect {
|
||||
if (self = [super initWithFrame:rect]) {
|
||||
_graph = graph;
|
||||
|
||||
[self addTrackingArea:[[NSTrackingArea alloc] initWithRect:NSZeroRect options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect) owner:self userInfo:nil]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect {
|
||||
_graph->handle_draw_scale_area([NSGraphicsContext currentContext].CGContext, dirtyRect);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MacStatsScaleAreaController
|
||||
|
||||
- (id)initWithGraph:(MacStatsGraph *)graph {
|
||||
if (self = [super init]) {
|
||||
_graph = graph;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadView {
|
||||
self.view = [[MacStatsScaleArea alloc] initWithGraph:_graph frame:NSMakeRect(0, 0, 100, 100)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* 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 macStatsServer.h
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSSERVER_H
|
||||
#define MACSTATSSERVER_H
|
||||
|
||||
#include "pandatoolbase.h"
|
||||
#include "programBase.h"
|
||||
#include "pStatServer.h"
|
||||
#include "macStatsMonitor.h"
|
||||
#include "macStatsAppDelegate.h"
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
/**
|
||||
* The class that owns the main loop, waiting for client connections.
|
||||
*/
|
||||
class MacStatsServer : public PStatServer, public ProgramBase {
|
||||
public:
|
||||
MacStatsServer();
|
||||
|
||||
MacStatsMonitor *get_monitor() { return _monitor; }
|
||||
|
||||
void run(int argc, char *argv[]);
|
||||
|
||||
protected:
|
||||
virtual bool handle_args(Args &args) override;
|
||||
|
||||
virtual PStatMonitor *make_monitor(const NetAddress &address) override;
|
||||
virtual void lost_connection(PStatMonitor *monitor) override;
|
||||
|
||||
public:
|
||||
bool new_session();
|
||||
bool open_session(const Filename &fn);
|
||||
bool open_session();
|
||||
bool open_last_session();
|
||||
bool save_session();
|
||||
bool export_session();
|
||||
bool close_session();
|
||||
|
||||
void set_show_status_item(bool show);
|
||||
void set_appearance(NSString *name);
|
||||
void set_time_units(int unit_mask);
|
||||
|
||||
private:
|
||||
void create_app();
|
||||
void setup_session_menu();
|
||||
|
||||
private:
|
||||
PT(MacStatsMonitor) _monitor;
|
||||
|
||||
Filename _last_session;
|
||||
Filename _save_filename;
|
||||
|
||||
int _port = -1;
|
||||
NSApplication *_app = nil;
|
||||
NSUserNotification *_listening_notification = nil;
|
||||
NSMenu *_main_menu = nil;
|
||||
NSMenuItem *_show_status_item_menu_item = nil;
|
||||
NSMenuItem *_appearance_system_menu_item = nil;
|
||||
NSMenuItem *_appearance_aqua_menu_item = nil;
|
||||
NSMenuItem *_appearance_dark_aqua_menu_item = nil;
|
||||
NSMenuItem *_units_ms_menu_item = nil;
|
||||
NSMenuItem *_units_hz_menu_item = nil;
|
||||
NSMenuItem *_new_session_menu_item = nil;
|
||||
NSMenuItem *_open_last_session_menu_item = nil;
|
||||
NSMenuItem *_save_session_menu_item = nil;
|
||||
NSMenuItem *_close_session_menu_item = nil;
|
||||
NSMenuItem *_export_session_menu_item = nil;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,732 @@
|
|||
/**
|
||||
* 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 macStatsServer.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-17
|
||||
*/
|
||||
|
||||
#include "macStatsServer.h"
|
||||
#include "macStatsMonitor.h"
|
||||
#include "macStatsAppDelegate.h"
|
||||
#include "pandaVersion.h"
|
||||
#include "pStatGraph.h"
|
||||
#include "config_pstatclient.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsServer::
|
||||
MacStatsServer() : _port(pstats_port) {
|
||||
set_program_brief("macOS PStats client");
|
||||
set_program_description
|
||||
("This is a GUI-based PStats server that listens on a TCP port for a "
|
||||
"connection from a PStatClient in a Panda3D application. It offers "
|
||||
"various graphs for showing the timing information sent by the client."
|
||||
"\n\n"
|
||||
"The full documentation is available online:\n "
|
||||
#ifdef HAVE_PYTHON
|
||||
"https://docs.panda3d.org/" PANDA_ABI_VERSION_STR "/python/optimization/pstats"
|
||||
#else
|
||||
"https://docs.panda3d.org/" PANDA_ABI_VERSION_STR "/cpp/optimization/pstats"
|
||||
#endif
|
||||
"");
|
||||
|
||||
add_option
|
||||
("p", "port", 0,
|
||||
"Specify the TCP port to listen for connections on. By default, this "
|
||||
"is taken from the pstats-port Config variable.",
|
||||
&ProgramBase::dispatch_int, nullptr, &_port);
|
||||
|
||||
add_runline("[-p 5185]");
|
||||
add_runline("session.pstats");
|
||||
|
||||
_last_session = Filename::expand_from(
|
||||
"$HOME/Library/Caches/Panda3D-" PANDA_ABI_VERSION_STR "/last-session.pstats");
|
||||
_last_session.set_binary();
|
||||
|
||||
create_app();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the server.
|
||||
*/
|
||||
void MacStatsServer::
|
||||
run(int argc, char *argv[]) {
|
||||
if (parse_command_line(argc, argv, isatty(STDERR_FILENO)) == ProgramBase::EC_failure) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"PStats Error";
|
||||
alert.informativeText = @"Failed to parse command-line options.";
|
||||
alert.alertStyle = NSCriticalAlertStyle;
|
||||
[alert runModal];
|
||||
[alert release];
|
||||
return;
|
||||
}
|
||||
|
||||
[_app run];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does something with the additional arguments on the command line (after all
|
||||
* the -options have been parsed). Returns true if the arguments are good,
|
||||
* false otherwise.
|
||||
*/
|
||||
bool MacStatsServer::
|
||||
handle_args(ProgramBase::Args &args) {
|
||||
if (args.empty()) {
|
||||
// applicationDidFinishLaunching will call new_session().
|
||||
return true;
|
||||
}
|
||||
else if (args.size() == 1) {
|
||||
// Handled by application:openFile:
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
nout << "At most one filename may be specified on the command-line.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
PStatMonitor *MacStatsServer::
|
||||
make_monitor(const NetAddress &address) {
|
||||
// Enable the "New Session", "Save Session" and "Close Session" menu items.
|
||||
_new_session_menu_item.enabled = YES;
|
||||
_save_session_menu_item.enabled = YES;
|
||||
_close_session_menu_item.enabled = YES;
|
||||
_export_session_menu_item.enabled = YES;
|
||||
|
||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||
if (_listening_notification != nil) {
|
||||
[center removeDeliveredNotification:_listening_notification];
|
||||
[_listening_notification release];
|
||||
_listening_notification = nil;
|
||||
}
|
||||
|
||||
_monitor = new MacStatsMonitor(this);
|
||||
return _monitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when connection has been lost.
|
||||
*/
|
||||
void MacStatsServer::
|
||||
lost_connection(PStatMonitor *monitor) {
|
||||
if (_monitor != nullptr && !_monitor->_have_data) {
|
||||
// We didn't have any data yet. Just silently restart the session.
|
||||
_monitor->close();
|
||||
_monitor = nullptr;
|
||||
if (new_session()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Store a backup now, in case PStats crashes or something.
|
||||
_last_session.make_dir();
|
||||
if (monitor->write(_last_session)) {
|
||||
nout << "Wrote to " << _last_session << "\n";
|
||||
} else {
|
||||
nout << "Failed to write to " << _last_session << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
stop_listening();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new session.
|
||||
*/
|
||||
bool MacStatsServer::
|
||||
new_session() {
|
||||
if (!close_session()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listen(_port)) {
|
||||
NSUserNotification *notification = [[NSUserNotification alloc] init];
|
||||
notification.title = @"PStats Server";
|
||||
notification.informativeText = [NSString stringWithFormat:@"Waiting for client to connect on port %d\u2026", _port];
|
||||
|
||||
// Quick way to do something else if the user prefers.
|
||||
notification.additionalActions = @[
|
||||
[NSUserNotificationAction actionWithIdentifier:@"open" title:@"Open Session\u2026"],
|
||||
[NSUserNotificationAction actionWithIdentifier:@"openLast" title:@"Open Last Session"],
|
||||
[NSUserNotificationAction actionWithIdentifier:@"quit" title:@"Quit PStats"]
|
||||
];
|
||||
|
||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||
[center deliverNotification:notification];
|
||||
_listening_notification = notification;
|
||||
|
||||
_new_session_menu_item.enabled = NO;
|
||||
_save_session_menu_item.enabled = NO;
|
||||
_close_session_menu_item.enabled = YES;
|
||||
_export_session_menu_item.enabled = NO;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"PStats Error";
|
||||
alert.informativeText = [NSString stringWithFormat:@"Unable to open port %d. Try specifying a different port number using pstats-port in your Config file or the -p option on the command-line.", _port];
|
||||
alert.alertStyle = NSCriticalAlertStyle;
|
||||
[alert runModal];
|
||||
[alert release];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a session with the given filename.
|
||||
*/
|
||||
bool MacStatsServer::
|
||||
open_session(const Filename &fn) {
|
||||
if (!close_session()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MacStatsMonitor *monitor = new MacStatsMonitor(this);
|
||||
if (!monitor->read(fn)) {
|
||||
delete monitor;
|
||||
return false;
|
||||
}
|
||||
|
||||
_save_filename = fn;
|
||||
_new_session_menu_item.enabled = YES;
|
||||
_save_session_menu_item.enabled = YES;
|
||||
_close_session_menu_item.enabled = YES;
|
||||
_export_session_menu_item.enabled = YES;
|
||||
|
||||
_monitor = monitor;
|
||||
|
||||
// If the file contained no graphs, open the default graphs.
|
||||
if (monitor->_graphs.empty()) {
|
||||
monitor->open_default_graphs();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offers to open an existing session.
|
||||
*/
|
||||
bool MacStatsServer::
|
||||
open_session() {
|
||||
if (!close_session()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
||||
panel.title = @"Open Session";
|
||||
|
||||
if ([panel runModal] == NSModalResponseOK) {
|
||||
NSString *path = [[panel.URLs firstObject] path];
|
||||
Filename fn([path UTF8String]);
|
||||
fn.set_binary();
|
||||
if (open_session(fn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"PStats Error";
|
||||
alert.informativeText = [NSString stringWithFormat:@"Failed to load session file: %s", fn.c_str()];
|
||||
alert.alertStyle = NSCriticalAlertStyle;
|
||||
[alert runModal];
|
||||
[alert release];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the last session, if any.
|
||||
*/
|
||||
bool MacStatsServer::
|
||||
open_last_session() {
|
||||
if (open_session(_last_session)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"PStats Error";
|
||||
alert.informativeText = [NSString stringWithFormat:@"Failed to load session file: %s", _last_session.c_str()];
|
||||
alert.alertStyle = NSCriticalAlertStyle;
|
||||
[alert runModal];
|
||||
[alert release];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offers to save the current session.
|
||||
*/
|
||||
bool MacStatsServer::
|
||||
save_session() {
|
||||
nassertr_always(_monitor != nullptr, true);
|
||||
|
||||
NSSavePanel *panel = [NSSavePanel savePanel];
|
||||
panel.title = @"Save Session";
|
||||
|
||||
if (_save_filename.empty()) {
|
||||
panel.nameFieldStringValue = @"session.pstats";
|
||||
} else {
|
||||
std::string dirname = _save_filename.get_dirname();
|
||||
std::string basename = _save_filename.get_basename();
|
||||
panel.nameFieldStringValue = [NSString stringWithUTF8String:basename.c_str()];
|
||||
panel.directoryURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:dirname.c_str()]];
|
||||
}
|
||||
|
||||
if ([panel runModal] == NSModalResponseOK) {
|
||||
NSString *path = [panel.URL path];
|
||||
Filename fn([path UTF8String]);
|
||||
fn.set_binary();
|
||||
|
||||
if (!_monitor->write(fn)) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"PStats Error";
|
||||
alert.informativeText = [NSString stringWithFormat:@"Failed to save session file: %s", fn.c_str()];
|
||||
alert.alertStyle = NSCriticalAlertStyle;
|
||||
[alert runModal];
|
||||
[alert release];
|
||||
|
||||
return false;
|
||||
}
|
||||
_save_filename = fn;
|
||||
_monitor->get_client_data()->clear_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offers to export the current session as a JSON file.
|
||||
*/
|
||||
bool MacStatsServer::
|
||||
export_session() {
|
||||
nassertr_always(_monitor != nullptr, true);
|
||||
|
||||
NSSavePanel *panel = [NSSavePanel savePanel];
|
||||
panel.title = @"Export Session";
|
||||
panel.nameFieldStringValue = @"session.json";
|
||||
|
||||
if ([panel runModal] == NSModalResponseOK) {
|
||||
NSString *path = [panel.URL path];
|
||||
Filename fn([path UTF8String]);
|
||||
fn.set_binary();
|
||||
|
||||
std::ofstream stream;
|
||||
if (!fn.open_write(stream)) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"PStats Error";
|
||||
alert.informativeText = [NSString stringWithFormat:@"Failed to open file for export: %s", fn.c_str()];
|
||||
alert.alertStyle = NSCriticalAlertStyle;
|
||||
[alert runModal];
|
||||
[alert release];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int pid = _monitor->get_client_pid();
|
||||
_monitor->get_client_data()->write_json(stream, std::max(0, pid));
|
||||
stream.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current session.
|
||||
*/
|
||||
bool MacStatsServer::
|
||||
close_session() {
|
||||
bool wrote_last_session = false;
|
||||
|
||||
if (_monitor != nullptr) {
|
||||
const PStatClientData *client_data = _monitor->get_client_data();
|
||||
if (client_data != nullptr && client_data->is_dirty()) {
|
||||
if (!_monitor->has_read_filename()) {
|
||||
_last_session.make_dir();
|
||||
if (_monitor->write(_last_session)) {
|
||||
nout << "Wrote to " << _last_session << "\n";
|
||||
wrote_last_session = true;
|
||||
}
|
||||
else {
|
||||
nout << "Failed to write to " << _last_session << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the alert goes on top.
|
||||
[_app activateIgnoringOtherApps:YES];
|
||||
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
alert.messageText = @"Unsaved Data";
|
||||
alert.informativeText = @"Would you like to save the currently open session?";
|
||||
[alert addButtonWithTitle:@"Save\u2026"];
|
||||
[alert addButtonWithTitle:@"Don't Save"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
int response = [alert runModal];
|
||||
[alert release];
|
||||
|
||||
if ((response != 1000 && response != 1001) ||
|
||||
(response == 1000 && !save_session())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_monitor->close();
|
||||
_monitor = nullptr;
|
||||
}
|
||||
|
||||
_save_filename = Filename();
|
||||
stop_listening();
|
||||
|
||||
if (_listening_notification != nil) {
|
||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||
[center removeDeliveredNotification:_listening_notification];
|
||||
[_listening_notification release];
|
||||
_listening_notification = nil;
|
||||
}
|
||||
|
||||
_new_session_menu_item.enabled = YES;
|
||||
if (wrote_last_session) {
|
||||
_open_last_session_menu_item.enabled = YES;
|
||||
}
|
||||
_save_session_menu_item.enabled = NO;
|
||||
_close_session_menu_item.enabled = NO;
|
||||
_export_session_menu_item.enabled = NO;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the frame rate label on the right end of the menu bar. This is
|
||||
* used as a text label to display the main thread's frame rate to the user,
|
||||
* although it is implemented as a right-justified toplevel menu item that
|
||||
* doesn't open to anything.
|
||||
*/
|
||||
void MacStatsServer::
|
||||
set_show_status_item(bool show) {
|
||||
if (_monitor != nullptr) {
|
||||
_monitor->set_show_status_item(show);
|
||||
}
|
||||
|
||||
_show_status_item_menu_item.state = show ? NSOnState : NSOffState;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsServer::
|
||||
set_appearance(NSString *name) {
|
||||
if (@available(macOS 10.14, *)) {
|
||||
if (name == nil || name.length == 0) {
|
||||
[_app setAppearance:nil];
|
||||
_appearance_system_menu_item.state = NSOnState;
|
||||
} else {
|
||||
[_app setAppearance:[NSAppearance appearanceNamed:name]];
|
||||
_appearance_system_menu_item.state = NSOffState;
|
||||
}
|
||||
|
||||
_appearance_aqua_menu_item.state = (name != nil && [name isEqual:@"NSAppearanceNameAqua"]) ? NSOnState : NSOffState;
|
||||
_appearance_dark_aqua_menu_item.state = (name != nil && [name isEqual:@"NSAppearanceNameDarkAqua"]) ? NSOnState : NSOffState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user selects a new time units from the monitor pulldown
|
||||
* menu, this should adjust the units for all graphs to the indicated mask if
|
||||
* it is a time-based graph.
|
||||
*/
|
||||
void MacStatsServer::
|
||||
set_time_units(int unit_mask) {
|
||||
if (_monitor != nullptr) {
|
||||
_monitor->set_time_units(unit_mask);
|
||||
}
|
||||
|
||||
_units_ms_menu_item.state = (unit_mask & PStatGraph::GBU_ms) ? NSOnState : NSOffState;
|
||||
_units_hz_menu_item.state = (unit_mask & PStatGraph::GBU_hz) ? NSOnState : NSOffState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the menu bar for this monitor.
|
||||
*/
|
||||
void MacStatsServer::
|
||||
create_app() {
|
||||
_app = [NSApplication sharedApplication];
|
||||
|
||||
MacStatsAppDelegate *delegate = [[MacStatsAppDelegate alloc] initWithServer:this];
|
||||
[_app setDelegate:delegate];
|
||||
[_app setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
_main_menu = [[NSMenu alloc] init];
|
||||
|
||||
NSMenu *app_menu = [[NSMenu alloc] init];
|
||||
|
||||
NSMenu *settings_menu = [[NSMenu alloc] init];
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleToggleSettingsBool:);
|
||||
item.representedObject = @"ShowStatusItem";
|
||||
item.title = @"Show in Status Bar";
|
||||
item.state = NSOnState;
|
||||
[settings_menu addItem:item];
|
||||
|
||||
_show_status_item_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
|
||||
NSMenu *units_menu;
|
||||
if (@available(macOS 14.0, *)) {
|
||||
// On Sonnoma, use a section with header.
|
||||
[settings_menu addItem:[NSMenuItem separatorItem]];
|
||||
[settings_menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Time Units"]];
|
||||
units_menu = settings_menu;
|
||||
} else {
|
||||
// On older versions, create a sub-menu.
|
||||
units_menu = [[NSMenu alloc] init];
|
||||
units_menu.autoenablesItems = NO;
|
||||
|
||||
NSMenuItem *units_item = [[NSMenuItem alloc] init];
|
||||
units_item.title = @"Time Units";
|
||||
|
||||
[settings_menu addItem:units_item];
|
||||
[settings_menu setSubmenu:units_menu forItem:units_item];
|
||||
[units_item release];
|
||||
[units_menu release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSettingsInteger:);
|
||||
item.representedObject = @"TimeUnits";
|
||||
item.tag = PStatGraph::GBU_ms;
|
||||
item.title = @"ms";
|
||||
item.state = NSOnState;
|
||||
[units_menu addItem:item];
|
||||
|
||||
_units_ms_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSettingsInteger:);
|
||||
item.representedObject = @"TimeUnits";
|
||||
item.tag = PStatGraph::GBU_hz;
|
||||
item.title = @"Hz";
|
||||
item.state = NSOffState;
|
||||
[units_menu addItem:item];
|
||||
|
||||
_units_hz_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
|
||||
// Dark mode available from Mojave.
|
||||
if (@available(macOS 10.14, *)) {
|
||||
NSMenu *appearance_menu;
|
||||
if (@available(macOS 14.0, *)) {
|
||||
// On Sonnoma, use a section with header.
|
||||
[settings_menu addItem:[NSMenuItem separatorItem]];
|
||||
[settings_menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Appearance"]];
|
||||
appearance_menu = settings_menu;
|
||||
} else {
|
||||
// On older versions, create a sub-menu.
|
||||
appearance_menu = [[NSMenu alloc] init];
|
||||
appearance_menu.autoenablesItems = NO;
|
||||
|
||||
NSMenuItem *appearance_item = [[NSMenuItem alloc] init];
|
||||
appearance_item.title = @"Appearance";
|
||||
|
||||
[settings_menu addItem:appearance_item];
|
||||
[settings_menu setSubmenu:appearance_menu forItem:appearance_item];
|
||||
[appearance_item release];
|
||||
[appearance_menu release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSettingsAppearance:);
|
||||
item.representedObject = @"";
|
||||
item.title = @"System";
|
||||
item.state = NSOnState;
|
||||
[appearance_menu addItem:item];
|
||||
_appearance_system_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSettingsAppearance:);
|
||||
item.representedObject = @"NSAppearanceNameAqua";
|
||||
item.title = @"Aqua";
|
||||
item.state = NSOffState;
|
||||
[appearance_menu addItem:item];
|
||||
_appearance_aqua_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSettingsAppearance:);
|
||||
item.representedObject = @"NSAppearanceNameDarkAqua";
|
||||
item.title = @"Dark Aqua";
|
||||
item.state = NSOffState;
|
||||
[appearance_menu addItem:item];
|
||||
_appearance_dark_aqua_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
}
|
||||
|
||||
NSMenuItem *settings_item = [[NSMenuItem alloc] init];
|
||||
settings_item.title = @"Settings";
|
||||
[app_menu addItem:settings_item];
|
||||
[app_menu setSubmenu:settings_menu forItem:settings_item];
|
||||
[settings_item release];
|
||||
[settings_menu release];
|
||||
|
||||
[app_menu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(hide:);
|
||||
item.keyEquivalent = @"h";
|
||||
item.title = @"Hide PStats";
|
||||
[app_menu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(hideOtherApplications:);
|
||||
item.keyEquivalent = @"h";
|
||||
item.keyEquivalentModifierMask |= NSAlternateKeyMask;
|
||||
item.title = @"Hide Others";
|
||||
[app_menu addItem:item];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(unhideAllApplications:);
|
||||
item.title = @"Show All";
|
||||
[app_menu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
[app_menu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(terminate:);
|
||||
item.keyEquivalent = @"q";
|
||||
item.title = @"Quit PStats";
|
||||
[app_menu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
NSMenuItem *app_menu_item = [[NSMenuItem alloc] init];
|
||||
[_main_menu addItem:app_menu_item];
|
||||
[_main_menu setSubmenu:app_menu forItem:app_menu_item];
|
||||
[app_menu_item release];
|
||||
[app_menu release];
|
||||
|
||||
setup_session_menu();
|
||||
|
||||
[_app setMainMenu:_main_menu];
|
||||
[_main_menu release];
|
||||
|
||||
//[_app activateIgnoringOtherApps:YES];
|
||||
//[_app finishLaunching];
|
||||
|
||||
set_time_units(PStatGraph::GBU_ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the "Session" pulldown menu.
|
||||
*/
|
||||
void MacStatsServer::
|
||||
setup_session_menu() {
|
||||
NSMenu *submenu = [[NSMenu alloc] init];
|
||||
submenu.title = @"Session";
|
||||
submenu.autoenablesItems = NO;
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleNewSession:);
|
||||
item.keyEquivalent = @"n";
|
||||
item.title = @"New Session";
|
||||
[submenu addItem:item];
|
||||
|
||||
_new_session_menu_item = item;
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleOpenSession:);
|
||||
item.keyEquivalent = @"o";
|
||||
item.title = @"Open Session\u2026";
|
||||
[submenu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleOpenLastSession:);
|
||||
item.title = @"Open Last Session";
|
||||
item.enabled = _last_session.exists();
|
||||
[submenu addItem:item];
|
||||
|
||||
_open_last_session_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleSaveSession:);
|
||||
item.keyEquivalent = @"s";
|
||||
item.title = @"Save Session\u2026";
|
||||
item.enabled = NO;
|
||||
[submenu addItem:item];
|
||||
|
||||
_save_session_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleCloseSession:);
|
||||
item.keyEquivalent = @"w";
|
||||
item.title = @"Close Session";
|
||||
item.enabled = NO;
|
||||
[submenu addItem:item];
|
||||
|
||||
_close_session_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
|
||||
[submenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
item.action = @selector(handleExportSession:);
|
||||
item.title = @"Export as JSON\u2026";
|
||||
item.enabled = NO;
|
||||
[submenu addItem:item];
|
||||
|
||||
_export_session_menu_item = item;
|
||||
[item release];
|
||||
}
|
||||
|
||||
NSMenuItem *item = [[NSMenuItem alloc] init];
|
||||
[_main_menu addItem:item];
|
||||
[_main_menu setSubmenu:submenu forItem:item];
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* 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 macStatsStripChart.h
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSSTRIPCHART_H
|
||||
#define MACSTATSSTRIPCHART_H
|
||||
|
||||
#include "macStatsGraph.h"
|
||||
#include "pStatStripChart.h"
|
||||
#include "macStatsChartMenuDelegate.h"
|
||||
|
||||
class MacStatsMonitor;
|
||||
|
||||
/**
|
||||
* A window that draws a strip chart, given a view.
|
||||
*/
|
||||
class MacStatsStripChart final : public PStatStripChart, public MacStatsGraph {
|
||||
public:
|
||||
MacStatsStripChart(MacStatsMonitor *monitor,
|
||||
int thread_index, int collector_index, bool show_level);
|
||||
virtual ~MacStatsStripChart();
|
||||
|
||||
virtual void new_collector(int collector_index);
|
||||
virtual void new_data(int thread_index, int frame_number);
|
||||
virtual void force_redraw();
|
||||
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
|
||||
|
||||
virtual void set_time_units(int unit_mask);
|
||||
virtual void set_scroll_speed(double scroll_speed);
|
||||
virtual void on_click_label(int collector_index);
|
||||
virtual NSMenu *get_label_menu(int collector_index) const;
|
||||
virtual std::string get_label_tooltip(int collector_index) const;
|
||||
void set_vertical_scale(double value_height);
|
||||
void set_auto_vertical_scale();
|
||||
|
||||
protected:
|
||||
virtual void update_labels();
|
||||
|
||||
virtual void clear_region();
|
||||
virtual void draw_slice(int x, int w,
|
||||
const PStatStripChart::FrameData &fdata);
|
||||
virtual void draw_empty(int x, int w);
|
||||
virtual void draw_cursor(int x);
|
||||
virtual void end_draw(int from_x, int to_x);
|
||||
|
||||
virtual bool get_window_state(int &x, int &y, int &width, int &height,
|
||||
bool &maximized, bool &minimized) const;
|
||||
virtual void set_window_state(int x, int y, int width, int height,
|
||||
bool maximized, bool minimized);
|
||||
|
||||
virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const;
|
||||
virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const;
|
||||
virtual DragMode consider_drag_start(int graph_x, int graph_y);
|
||||
virtual void set_drag_mode(DragMode drag_mode);
|
||||
|
||||
virtual void handle_button_press(int graph_x, int graph_y,
|
||||
bool double_click, int button);
|
||||
virtual void handle_button_release(int graph_x, int graph_y);
|
||||
virtual void handle_motion(int graph_x, int graph_y);
|
||||
virtual void handle_leave();
|
||||
virtual void handle_magnify(int graph_x, int graph_y, double scale);
|
||||
virtual void handle_draw_graph(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_draw_scale_area(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_back();
|
||||
|
||||
private:
|
||||
void draw_guide_bar(CGContextRef ctx, int from_x, int to_x,
|
||||
const PStatGraph::GuideBar &bar);
|
||||
void draw_guide_labels(CGContextRef ctx);
|
||||
int draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar, int last_y);
|
||||
|
||||
private:
|
||||
MacStatsChartMenuDelegate *_menu_delegate;
|
||||
//NSButton *_smooth_checkbox;
|
||||
//NSTextField *_total_label;
|
||||
NSToolbarItem *_total_item;
|
||||
|
||||
std::vector<int> _back_stack;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,964 @@
|
|||
/**
|
||||
* 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 macStatsStripChart.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-18
|
||||
*/
|
||||
|
||||
#include "macStatsStripChart.h"
|
||||
#include "macStatsMonitor.h"
|
||||
#include "macStatsScaleArea.h"
|
||||
#include "pStatCollectorDef.h"
|
||||
#include "cocoa_compat.h"
|
||||
|
||||
@interface MacStatsStripChartViewController : MacStatsGraphViewController
|
||||
@end
|
||||
|
||||
static const int default_strip_chart_width = 400;
|
||||
static const int default_strip_chart_height = 200;
|
||||
|
||||
static const int minimum_strip_chart_sidebar_width = 116;
|
||||
static const int default_strip_chart_sidebar_width = 116;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsStripChart::
|
||||
MacStatsStripChart(MacStatsMonitor *monitor, int thread_index,
|
||||
int collector_index, bool show_level) :
|
||||
PStatStripChart(monitor, thread_index, collector_index, show_level, 0, 0),
|
||||
MacStatsGraph(monitor, [MacStatsStripChartViewController alloc])
|
||||
{
|
||||
// Used for popup menus.
|
||||
_menu_delegate = [[MacStatsChartMenuDelegate alloc] initWithMonitor:monitor threadIndex:thread_index];
|
||||
|
||||
// Set the initial size of the graph.
|
||||
int height = default_strip_chart_height + _window.frame.size.height - _window.contentLayoutRect.size.height;
|
||||
_graph_view.frame = NSMakeRect(0, 0, default_strip_chart_width, height);
|
||||
_graph_view_controller.view.frame = NSMakeRect(0, 0, default_strip_chart_width, height);
|
||||
|
||||
// It's put inside a scroll view that tracks the main scroll view.
|
||||
NSScrollView *scroll_view = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, default_strip_chart_sidebar_width, 0)];
|
||||
scroll_view.documentView = _label_stack.get_view();
|
||||
scroll_view.drawsBackground = NO;
|
||||
scroll_view.automaticallyAdjustsContentInsets = YES;
|
||||
scroll_view.hasHorizontalScroller = NO;
|
||||
scroll_view.hasVerticalScroller = NO;
|
||||
|
||||
NSViewController *sidebar_controller = [[NSViewController alloc] init];
|
||||
sidebar_controller.view = scroll_view;
|
||||
|
||||
// Add a view to the right of the graph, to display all of the scale units.
|
||||
// Calculate how wide it should be to display a typical label.
|
||||
CGFloat width = [NSTextField labelWithString:@"999 ms"].frame.size.width + 8;
|
||||
_scale_area = [[MacStatsScaleArea alloc] initWithGraph:this frame:NSMakeRect(0, 0, width, 0)];
|
||||
_scale_area.autoresizingMask = NSViewHeightSizable;
|
||||
|
||||
NSViewController *scale_area_controller = [[NSViewController alloc] init];
|
||||
scale_area_controller.view = _scale_area;
|
||||
|
||||
NSSplitViewController *svc = [[NSSplitViewController alloc] init];
|
||||
[svc addSplitViewItem:[NSSplitViewItem sidebarWithViewController:sidebar_controller]];
|
||||
[svc addSplitViewItem:[NSSplitViewItem splitViewItemWithViewController:_graph_view_controller]];
|
||||
[svc addSplitViewItem:[NSSplitViewItem splitViewItemWithViewController:scale_area_controller]];
|
||||
|
||||
svc.splitViewItems[0].minimumThickness = minimum_strip_chart_sidebar_width;
|
||||
svc.splitViewItems[2].minimumThickness = width;
|
||||
svc.splitViewItems[2].maximumThickness = width;
|
||||
|
||||
NSSplitView *split_view = svc.splitView;
|
||||
split_view.vertical = YES;
|
||||
split_view.dividerStyle = NSSplitViewDividerStyleThin;
|
||||
split_view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
_split_view = split_view;
|
||||
|
||||
// When sidebar collapses, show a sidebar icon in the menu bar
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
[center addObserver:_graph_view_controller
|
||||
selector:@selector(handleSplitViewResize:)
|
||||
name:NSSplitViewDidResizeSubviewsNotification
|
||||
object:split_view];
|
||||
|
||||
_window.contentViewController = svc;
|
||||
|
||||
_graph_view_controller.backToolbarItemVisible = NO;
|
||||
|
||||
[svc release];
|
||||
[sidebar_controller release];
|
||||
|
||||
_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];
|
||||
}
|
||||
|
||||
/*{
|
||||
_smooth_checkbox = [NSButton checkboxWithTitle:@"Smooth"
|
||||
target:_graph_view_controller
|
||||
action:@selector(handleToggleAverageMode:)];
|
||||
|
||||
NSStackView *stack_view = [NSStackView stackViewWithViews:@[_smooth_checkbox]];
|
||||
stack_view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
NSTitlebarAccessoryViewController *accessory_controller = [[NSTitlebarAccessoryViewController alloc] init];
|
||||
//accessory_controller.layoutAttribute = NSLayoutAttributeLeft;
|
||||
[accessory_controller.view addSubview:stack_view];
|
||||
//accessory_controller.automaticallyAdjustsSize = NO;
|
||||
[_window addTitlebarAccessoryViewController:accessory_controller];
|
||||
[accessory_controller release];
|
||||
|
||||
[stack_view.leftAnchor constraintEqualToAnchor:_graph_view.leftAnchor constant:8].active = YES;
|
||||
[stack_view.bottomAnchor constraintEqualToAnchor:((NSLayoutGuide *)_window.contentLayoutGuide).topAnchor constant:-8].active = YES;
|
||||
//[stack_view.topAnchor constraintEqualToAnchor:svc.view.topAnchor constant:-8].active = YES;
|
||||
}*/
|
||||
/*{
|
||||
_total_label = [NSTextField labelWithString:@""];
|
||||
|
||||
NSStackView *stack_view = [NSStackView stackViewWithViews:@[_total_label]];
|
||||
stack_view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
NSTitlebarAccessoryViewController *accessory_controller = [[NSTitlebarAccessoryViewController alloc] init];
|
||||
accessory_controller.layoutAttribute = NSLayoutAttributeRight;
|
||||
[accessory_controller.view addSubview:stack_view];
|
||||
//accessory_controller.automaticallyAdjustsSize = NO;
|
||||
[_window addTitlebarAccessoryViewController:accessory_controller];
|
||||
[accessory_controller release];
|
||||
|
||||
[stack_view.rightAnchor constraintEqualToAnchor:_graph_view.rightAnchor].active = YES;
|
||||
[stack_view.bottomAnchor constraintEqualToAnchor:((NSLayoutGuide *)_window.contentLayoutGuide).topAnchor].active = YES;
|
||||
[stack_view.topAnchor constraintEqualToAnchor:svc.view.topAnchor].active = YES;
|
||||
}*/
|
||||
|
||||
if (show_level) {
|
||||
// If it's a level-type graph, show the appropriate units.
|
||||
if (_unit_name.empty()) {
|
||||
set_guide_bar_units(GBU_named);
|
||||
} else {
|
||||
set_guide_bar_units(GBU_named | GBU_show_units);
|
||||
}
|
||||
} else {
|
||||
// If it's a time-type graph, show the ms / Hz units.
|
||||
set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
|
||||
}
|
||||
|
||||
[_label_stack.get_view().widthAnchor constraintEqualToAnchor:scroll_view.widthAnchor].active = YES;
|
||||
|
||||
// Update window title and total label.
|
||||
new_data(0, 0);
|
||||
|
||||
[_window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsStripChart::
|
||||
~MacStatsStripChart() {
|
||||
[_menu_delegate release];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever a new Collector definition is received from the client.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
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 MacStatsStripChart::
|
||||
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 10.15, *)) {
|
||||
std::string text = get_total_text();
|
||||
[_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when it is necessary to redraw the entire graph.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
force_redraw() {
|
||||
if (_ctx) {
|
||||
PStatStripChart::force_redraw();
|
||||
}
|
||||
|
||||
_scale_area.needsDisplay = YES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user has resized the window, forcing a resize of the graph.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
changed_graph_size(int graph_xsize, int graph_ysize) {
|
||||
PStatStripChart::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 MacStatsStripChart::
|
||||
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 10.15, *)) {
|
||||
std::string text = get_total_text();
|
||||
[_total_item setTitle:[NSString stringWithUTF8String:text.c_str()]];
|
||||
}
|
||||
|
||||
_scale_area.needsDisplay = YES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user selects a new scroll speed from the monitor pulldown
|
||||
* menu, this should adjust the speed for the graph to the indicated value.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
set_scroll_speed(double scroll_speed) {
|
||||
// The speed factor indicates chart widths per minute.
|
||||
if (scroll_speed != 0.0f) {
|
||||
set_horizontal_scale(60.0f / scroll_speed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user single-clicks on a label.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
on_click_label(int collector_index) {
|
||||
if (collector_index < 0) {
|
||||
// Clicking on whitespace in the graph is the same as clicking on the top
|
||||
// label.
|
||||
collector_index = get_collector_index();
|
||||
}
|
||||
|
||||
if (collector_index == get_collector_index()) {
|
||||
// Clicking on the top label means to go up to the parent level.
|
||||
if (collector_index != 0) {
|
||||
const PStatClientData *client_data =
|
||||
MacStatsGraph::_monitor->get_client_data();
|
||||
if (client_data->has_collector(collector_index)) {
|
||||
const PStatCollectorDef &def =
|
||||
client_data->get_collector_def(collector_index);
|
||||
if (def._parent_index == 0 && get_view().get_show_level()) {
|
||||
// Unless the parent is "Frame", and we're not a time collector.
|
||||
}
|
||||
else if (def._parent_index != get_collector_index()) {
|
||||
// If we were previously at the parent, pop it from the stack.
|
||||
if (!_back_stack.empty() && _back_stack.back() == def._parent_index) {
|
||||
_back_stack.pop_back();
|
||||
if (_back_stack.empty()) {
|
||||
_graph_view_controller.backToolbarItemVisible = NO;
|
||||
}
|
||||
} else {
|
||||
if (_back_stack.empty()) {
|
||||
_graph_view_controller.backToolbarItemVisible = YES;
|
||||
}
|
||||
_back_stack.push_back(get_collector_index());
|
||||
}
|
||||
|
||||
set_collector_index(def._parent_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (_back_stack.empty()) {
|
||||
_graph_view_controller.backToolbarItemVisible = YES;
|
||||
}
|
||||
_back_stack.push_back(get_collector_index());
|
||||
|
||||
// Clicking on any other label means to focus on that.
|
||||
set_collector_index(collector_index);
|
||||
}
|
||||
|
||||
// Update window title and total label.
|
||||
new_data(0, 0);
|
||||
|
||||
_scale_area.needsDisplay = YES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse right-clicks on a label, and should return the menu
|
||||
* that should pop up.
|
||||
*/
|
||||
NSMenu *MacStatsStripChart::
|
||||
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];
|
||||
}
|
||||
|
||||
{
|
||||
SEL action = get_view().get_show_level() ? @selector(handleOpenStripChartLevel:) : @selector(handleOpenStripChart:);
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Strip Chart" action:action keyEquivalent:@""];
|
||||
item.target = _menu_delegate;
|
||||
item.tag = collector_index;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
if (!get_view().get_show_level()) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse hovers over a label, and should return the text that
|
||||
* should appear on the tooltip.
|
||||
*/
|
||||
std::string MacStatsStripChart::
|
||||
get_label_tooltip(int collector_index) const {
|
||||
return PStatStripChart::get_label_tooltip(collector_index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the value the height of the vertical axis represents. This may
|
||||
* force a redraw.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
set_vertical_scale(double value_height) {
|
||||
PStatStripChart::set_vertical_scale(value_height);
|
||||
|
||||
_graph_view.needsDisplay = YES;
|
||||
_scale_area.needsDisplay = YES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertical scale to make all the data visible.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
set_auto_vertical_scale() {
|
||||
PStatStripChart::set_auto_vertical_scale();
|
||||
set_vertical_scale(get_vertical_scale() * 1.5);
|
||||
|
||||
_graph_view.needsDisplay = YES;
|
||||
_scale_area.needsDisplay = YES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the list of labels.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
update_labels() {
|
||||
PStatStripChart::update_labels();
|
||||
|
||||
_label_stack.clear_labels();
|
||||
for (int i = 0; i < get_num_labels(); i++) {
|
||||
_label_stack.add_label(MacStatsGraph::_monitor, this, _thread_index,
|
||||
get_label_collector(i), false);
|
||||
}
|
||||
_labels_changed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erases the chart area.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
clear_region() {
|
||||
if (_ctx) {
|
||||
//CGContextSetFillColorWithColor(_ctx, _background_color);
|
||||
//CGContextFillRect(_ctx, CGRectMake(0, 0, get_xsize(), get_ysize()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a single vertical slice of the strip chart, at the given pixel
|
||||
* position, and corresponding to the indicated level data.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
draw_slice(int x, int w, const PStatStripChart::FrameData &fdata) {
|
||||
if (!_ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start by clearing the band first.
|
||||
CGContextSetFillColorWithColor(_ctx, _background_color);
|
||||
CGContextFillRect(_ctx, CGRectMake(x, 0, w, get_ysize()));
|
||||
|
||||
double overall_time = 0.0;
|
||||
int y = get_ysize();
|
||||
|
||||
FrameData::const_iterator fi;
|
||||
for (fi = fdata.begin(); fi != fdata.end(); ++fi) {
|
||||
const ColorData &cd = (*fi);
|
||||
overall_time += cd._net_value;
|
||||
|
||||
bool is_highlighted = cd._collector_index == _highlighted_index;
|
||||
CGContextSetFillColorWithColor(_ctx,
|
||||
MacStatsGraph::_monitor->get_collector_color(cd._collector_index, is_highlighted));
|
||||
|
||||
if (overall_time > get_vertical_scale()) {
|
||||
// Off the top. Go ahead and clamp it by hand, in case it's so far off
|
||||
// the top we'd overflow the 16-bit pixel value.
|
||||
CGContextFillRect(_ctx, CGRectMake(x, 0, w, y));
|
||||
// And we can consider ourselves done now.
|
||||
return;
|
||||
}
|
||||
|
||||
int top_y = height_to_pixel(overall_time);
|
||||
CGContextFillRect(_ctx, CGRectMake(x, top_y, w, y - top_y));
|
||||
y = top_y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a single vertical slice of background color.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
draw_empty(int x, int w) {
|
||||
if (!_ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGContextSetFillColorWithColor(_ctx, _background_color);
|
||||
CGContextFillRect(_ctx, CGRectMake(x, 0, w, get_ysize()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a single vertical slice of foreground color.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
draw_cursor(int x) {
|
||||
if (!_ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGContextBeginPath(_ctx);
|
||||
CGContextMoveToPoint(_ctx, x, 0);
|
||||
CGContextAddLineToPoint(_ctx, x, get_ysize());
|
||||
CGContextStrokePath(_ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be overridden by the user class. This hook will be called after
|
||||
* drawing a series of color bars in the strip chart; it gives the pixel range
|
||||
* that was just redrawn.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
end_draw(int from_x, int to_x) {
|
||||
_graph_view.needsDisplay = YES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current window dimensions.
|
||||
*/
|
||||
bool MacStatsStripChart::
|
||||
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 MacStatsStripChart::
|
||||
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 *MacStatsStripChart::
|
||||
get_graph_menu(int mouse_x, int mouse_y) const {
|
||||
NSMenu *menu = nullptr;
|
||||
if (_highlighted_index != -1) {
|
||||
menu = get_label_menu(_highlighted_index);
|
||||
}
|
||||
/*
|
||||
if (menu != nullptr) {
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
} else {
|
||||
menu = [[[NSMenu alloc] init] autorelease];
|
||||
}
|
||||
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Smooth" action:@selector(handleToggleStripChartAverage:) keyEquivalent:@""];
|
||||
item.target = _graph_view_controller;
|
||||
item.state = get_average_mode() ? NSOnState : NSOffState;
|
||||
[menu addItem:item];
|
||||
[item release];*/
|
||||
return menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse hovers over the graph, and should return the text that
|
||||
* should appear on the tooltip.
|
||||
*/
|
||||
std::string MacStatsStripChart::
|
||||
get_graph_tooltip(int mouse_x, int mouse_y) const {
|
||||
if (_highlighted_index != -1) {
|
||||
return get_label_tooltip(_highlighted_index);
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the mouse position within the graph window, look for draggable
|
||||
* things the mouse might be hovering over and return the appropriate DragMode
|
||||
* enum or DM_none if nothing is indicated.
|
||||
*/
|
||||
MacStatsGraph::DragMode MacStatsStripChart::
|
||||
consider_drag_start(int graph_x, int graph_y) {
|
||||
// See if the mouse is over a user-defined guide bar.
|
||||
int y = graph_y;
|
||||
double from_height = pixel_to_height(y + 2);
|
||||
double to_height = pixel_to_height(y - 2);
|
||||
_drag_guide_bar = find_user_guide_bar(from_height, to_height);
|
||||
if (_drag_guide_bar >= 0) {
|
||||
return DM_guide_bar;
|
||||
}
|
||||
|
||||
return MacStatsGraph::consider_drag_start(graph_x, graph_y);
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be called whenever the drag mode needs to change state. It
|
||||
* provides hooks for a derived class to do something special.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
set_drag_mode(MacStatsGraph::DragMode drag_mode) {
|
||||
MacStatsGraph::set_drag_mode(drag_mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse button is depressed within the graph window.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
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()) {
|
||||
if (double_click && button == 0) {
|
||||
// Double-clicking on a color bar in the graph is the same as double-
|
||||
// clicking on the corresponding label.
|
||||
on_click_label(get_collector_under_pixel(graph_x, graph_y));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_potential_drag_mode == DM_none) {
|
||||
set_drag_mode(DM_scale);
|
||||
_drag_scale_start = pixel_to_height(graph_y);
|
||||
// SetCapture(_graph_window);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_potential_drag_mode == DM_guide_bar && _drag_guide_bar >= 0) {
|
||||
set_drag_mode(DM_guide_bar);
|
||||
_drag_start_y = graph_y;
|
||||
// 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 MacStatsStripChart::
|
||||
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_y < 0 || graph_y >= get_ysize()) {
|
||||
remove_user_guide_bar(_drag_guide_bar);
|
||||
} else {
|
||||
move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_y));
|
||||
}
|
||||
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 MacStatsStripChart::
|
||||
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 collector_index = get_collector_under_pixel(graph_x, graph_y);
|
||||
_label_stack.highlight_label(collector_index);
|
||||
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_scale) {
|
||||
double ratio = 1.0 - ((double)graph_y / (double)get_ysize());
|
||||
if (ratio > 0.0) {
|
||||
double new_scale = _drag_scale_start / ratio;
|
||||
if (!IS_NEARLY_EQUAL(get_vertical_scale(), new_scale)) {
|
||||
// Disable smoothing while we do this expensive operation.
|
||||
set_vertical_scale(_drag_scale_start / ratio);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else 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_y >= 0 && graph_y < get_ysize()) {
|
||||
set_drag_mode(DM_guide_bar);
|
||||
_drag_guide_bar = add_user_guide_bar(pixel_to_height(graph_y));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (_drag_mode == DM_guide_bar) {
|
||||
move_user_guide_bar(_drag_guide_bar, pixel_to_height(graph_y));
|
||||
return;
|
||||
}
|
||||
|
||||
MacStatsGraph::handle_motion(graph_x, graph_y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse has left the graph window.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
handle_leave() {
|
||||
_label_stack.highlight_label(-1);
|
||||
on_leave_label(_highlighted_index);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
handle_magnify(int graph_x, int graph_y, double scale) {
|
||||
set_vertical_scale(get_vertical_scale() * (1.0 - scale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the graph window.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
handle_draw_graph(CGContextRef ctx, NSRect rect) {
|
||||
MacStatsGraph::handle_draw_graph(ctx, rect);
|
||||
|
||||
int width = get_xsize();
|
||||
|
||||
// Draw in the guide bars.
|
||||
int num_guide_bars = get_num_guide_bars();
|
||||
for (int i = 0; i < num_guide_bars; i++) {
|
||||
draw_guide_bar(ctx, 0, width, 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_bar(ctx, 0, width, get_user_guide_bar(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the scale area.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
handle_draw_scale_area(CGContextRef ctx, NSRect rect) {
|
||||
MacStatsGraph::handle_draw_scale_area(ctx, rect);
|
||||
draw_guide_labels(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse clicks the back button in the toolbar.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
handle_back() {
|
||||
if (!_back_stack.empty()) {
|
||||
int collector_index = _back_stack.back();
|
||||
_back_stack.pop_back();
|
||||
set_collector_index(collector_index);
|
||||
|
||||
// Update window title and total label.
|
||||
new_data(0, 0);
|
||||
_scale_area.needsDisplay = YES;
|
||||
}
|
||||
|
||||
if (_back_stack.empty()) {
|
||||
_graph_view_controller.backToolbarItemVisible = NO;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the line for the indicated guide bar on the graph.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
draw_guide_bar(CGContextRef ctx, int from_x, int to_x,
|
||||
const PStatGraph::GuideBar &bar) {
|
||||
int y = height_to_pixel(bar._height);
|
||||
|
||||
if (y > 1) {
|
||||
// Only draw it if it's not too close to the top.
|
||||
/*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;
|
||||
}*/
|
||||
CGContextSetStrokeColorWithColor(ctx, [NSColor gridColor].CGColor);
|
||||
CGContextBeginPath(ctx);
|
||||
CGContextMoveToPoint(ctx, from_x, y);
|
||||
CGContextAddLineToPoint(ctx, to_x, y);
|
||||
CGContextStrokePath(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called during the servicing of the draw event.
|
||||
*/
|
||||
void MacStatsStripChart::
|
||||
draw_guide_labels(CGContextRef ctx) {
|
||||
// Draw in the labels for the guide bars.
|
||||
int last_y = 0;
|
||||
|
||||
int i;
|
||||
int num_guide_bars = get_num_guide_bars();
|
||||
for (i = 0; i < num_guide_bars; i++) {
|
||||
last_y = draw_guide_label(ctx, get_guide_bar(i), last_y);
|
||||
}
|
||||
|
||||
GuideBar top_value = make_guide_bar(get_vertical_scale());
|
||||
draw_guide_label(ctx, top_value, last_y);
|
||||
|
||||
last_y = 0;
|
||||
int num_user_guide_bars = get_num_user_guide_bars();
|
||||
for (i = 0; i < num_user_guide_bars; i++) {
|
||||
last_y = draw_guide_label(ctx, get_user_guide_bar(i), last_y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the text for the indicated guide bar label to the right of the graph,
|
||||
* unless it would overlap with the indicated last label, whose top pixel
|
||||
* value is given. Returns the top pixel value of the new label.
|
||||
*/
|
||||
int MacStatsStripChart::
|
||||
draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar, int last_y) {
|
||||
NSColor *color;
|
||||
switch (bar._style) {
|
||||
case GBS_target:
|
||||
color = [NSColor secondaryLabelColor];
|
||||
break;
|
||||
|
||||
case GBS_user:
|
||||
color = [NSColor tertiaryLabelColor];
|
||||
break;
|
||||
|
||||
default:
|
||||
color = [NSColor labelColor];
|
||||
break;
|
||||
}
|
||||
|
||||
int y = 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 height = bounds.size.height;
|
||||
|
||||
if (bar._style != GBS_user) {
|
||||
double from_height = pixel_to_height(y + height);
|
||||
double to_height = pixel_to_height(y - height);
|
||||
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 last_y;
|
||||
}
|
||||
}
|
||||
|
||||
if (y > height && y < get_ysize() - height) {
|
||||
// Now convert our y to a coordinate within our drawing area.
|
||||
|
||||
int this_y = y - height / 2;
|
||||
if (last_y < this_y || last_y > this_y + height) {
|
||||
CGContextSetTextPosition(ctx, 4, get_ysize() - this_y - height);
|
||||
CTLineDraw(line, ctx);
|
||||
|
||||
last_y = this_y;
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease(line);
|
||||
return last_y;
|
||||
}
|
||||
|
||||
@implementation MacStatsStripChartViewController
|
||||
|
||||
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
return @[@"smooth", @"sep", @"total"];
|
||||
} else {
|
||||
return @[@"smooth", @"sep"];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
|
||||
if (@available(macOS 10.15, *)) {
|
||||
return @[@"smooth", @"sep", @"total"];
|
||||
} else {
|
||||
return @[@"smooth", @"sep"];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSToolbarItem *) toolbar:(NSToolbar *)toolbar
|
||||
itemForItemIdentifier:(NSString *)ident
|
||||
willBeInsertedIntoToolbar:(BOOL)flag {
|
||||
|
||||
if (@available(macOS 11.0, *)) {
|
||||
if ([ident isEqual:@"smooth"]) {
|
||||
NSButton *button = [NSButton buttonWithTitle:@"Smooth" target:self action:@selector(handleToggleSmooth:)];
|
||||
button.image = [NSImage imageWithSystemSymbolName:@"alternatingcurrent" accessibilityDescription:@""];
|
||||
button.bezelStyle = NSBezelStyleTexturedRounded;
|
||||
button.buttonType = NSButtonTypePushOnPushOff;
|
||||
button.bordered = YES;
|
||||
button.toolTip = @"Smooth";
|
||||
button.state = NSOffState;
|
||||
NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease];
|
||||
item.label = @"Smooth";
|
||||
item.view = button;
|
||||
return item;
|
||||
}
|
||||
if ([ident isEqual:@"total"]) {
|
||||
NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:ident] autorelease];
|
||||
item.label = @"Total";
|
||||
item.action = @selector(handleClickTotal:);
|
||||
item.target = self;
|
||||
[item setBordered:YES];
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return [super toolbar:toolbar itemForItemIdentifier:ident willBeInsertedIntoToolbar:flag];
|
||||
}
|
||||
|
||||
- (void)handleToggleSmooth:(NSMenuItem *)item {
|
||||
MacStatsStripChart *graph = (MacStatsStripChart *)_graph;
|
||||
graph->set_average_mode(item.state == NSOnState);
|
||||
}
|
||||
|
||||
- (void)handleClickTotal:(NSButton *)button {
|
||||
MacStatsStripChart *graph = (MacStatsStripChart *)_graph;
|
||||
graph->set_auto_vertical_scale();
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* 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 macStatsTimeline.h
|
||||
* @author rdb
|
||||
* @date 2023-08-19
|
||||
*/
|
||||
|
||||
#ifndef MACSTATSTIMELINE_H
|
||||
#define MACSTATSTIMELINE_H
|
||||
|
||||
#include "macStatsGraph.h"
|
||||
#include "pStatTimeline.h"
|
||||
|
||||
class MacStatsMonitor;
|
||||
|
||||
/**
|
||||
* A window that draws all of the start/stop event pairs on each thread on a
|
||||
* horizontal scrolling timeline, with concurrent start/stop pairs stacked
|
||||
* underneath each other.
|
||||
*/
|
||||
class MacStatsTimeline final : public PStatTimeline, public MacStatsGraph {
|
||||
public:
|
||||
MacStatsTimeline(MacStatsMonitor *monitor);
|
||||
virtual ~MacStatsTimeline();
|
||||
|
||||
virtual void new_data(int thread_index, int frame_number);
|
||||
virtual void force_redraw();
|
||||
virtual void changed_graph_size(int graph_xsize, int graph_ysize);
|
||||
|
||||
protected:
|
||||
virtual void clear_region();
|
||||
virtual void begin_draw();
|
||||
virtual void draw_separator(int row);
|
||||
virtual void draw_guide_bar(int x, GuideBarStyle style);
|
||||
virtual void draw_bar(int row, int from_x, int to_x, int collector_index,
|
||||
const std::string &collector_name);
|
||||
virtual void end_draw();
|
||||
virtual void idle();
|
||||
|
||||
virtual bool animate(double time, double dt);
|
||||
|
||||
virtual bool get_window_state(int &x, int &y, int &width, int &height,
|
||||
bool &maximized, bool &minimized) const;
|
||||
virtual void set_window_state(int x, int y, int width, int height,
|
||||
bool maximized, bool minimized);
|
||||
|
||||
virtual NSMenu *get_graph_menu(int mouse_x, int mouse_y) const;
|
||||
virtual std::string get_graph_tooltip(int mouse_x, int mouse_y) const;
|
||||
virtual DragMode consider_drag_start(int graph_x, int graph_y);
|
||||
|
||||
virtual bool handle_key(int graph_x, int graph_y, bool pressed,
|
||||
UniChar c, unsigned short key_code);
|
||||
virtual void handle_button_press(int graph_x, int graph_y,
|
||||
bool double_click, int button);
|
||||
virtual void handle_button_release(int graph_x, int graph_y);
|
||||
virtual void handle_motion(int graph_x, int graph_y);
|
||||
virtual void handle_leave();
|
||||
virtual void handle_scroll();
|
||||
virtual void handle_wheel(int graph_x, int graph_y, double dx, double dy);
|
||||
virtual void handle_magnify(int graph_x, int graph_y, double scale);
|
||||
virtual void handle_draw_graph(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_draw_graph_overhang(CGContextRef ctx, NSRect rect);
|
||||
virtual void handle_draw_scale_area(CGContextRef ctx, NSRect rect);
|
||||
|
||||
public:
|
||||
void handle_zoom_to();
|
||||
void handle_open_strip_chart();
|
||||
void handle_open_flame_graph();
|
||||
void handle_open_piano_roll();
|
||||
|
||||
private:
|
||||
void draw_guide_bar(CGContextRef ctx, GuideBarStyle style, int x, int y, int height);
|
||||
void draw_guide_labels(CGContextRef ctx);
|
||||
void draw_guide_label(CGContextRef ctx, const GuideBar &bar);
|
||||
|
||||
int row_to_pixel(int row) const {
|
||||
return row * 4 * 5 + 4 - _scroll;
|
||||
}
|
||||
int pixel_to_row(int y) const {
|
||||
return (y + _scroll - 4) / (4 * 5);
|
||||
}
|
||||
|
||||
NSView *_thread_area;
|
||||
pvector<std::pair<NSTextField *, NSLayoutConstraint *> > _thread_labels;
|
||||
NSLayoutConstraint *_graph_height_constraint;
|
||||
NSScrollView *_sidebar_scroll_view;
|
||||
|
||||
int _highlighted_row = -1;
|
||||
int _highlighted_x = 0;
|
||||
int _scroll = 0;
|
||||
mutable ColorBar _popup_bar;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,932 @@
|
|||
/**
|
||||
* 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 macStatsTimeline.mm
|
||||
* @author rdb
|
||||
* @date 2023-08-19
|
||||
*/
|
||||
|
||||
#include "macStatsTimeline.h"
|
||||
#include "macStatsMonitor.h"
|
||||
#include "macStatsLabelStack.h"
|
||||
#include "macStatsScaleArea.h"
|
||||
#include "pStatCollectorDef.h"
|
||||
|
||||
@interface MacStatsTimelineViewController : MacStatsScrollableGraphViewController
|
||||
@end
|
||||
|
||||
static const int default_timeline_width = 1000;
|
||||
static const int default_timeline_height = 300;
|
||||
|
||||
static const int minimum_timeline_sidebar_width = 68;
|
||||
static const int default_timeline_sidebar_width = 100;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsTimeline::
|
||||
MacStatsTimeline(MacStatsMonitor *monitor) :
|
||||
PStatTimeline(monitor, 0, 0),
|
||||
MacStatsGraph(monitor, [MacStatsTimelineViewController alloc])
|
||||
{
|
||||
// Let's show the units on the guide bar labels. There's room.
|
||||
set_guide_bar_units(get_guide_bar_units() | GBU_show_units);
|
||||
|
||||
_window.title = @"Timeline";
|
||||
|
||||
if (@available(macOS 11.0, *)) {
|
||||
_window.titleVisibility = NSWindowTitleHidden;
|
||||
}
|
||||
|
||||
// Set the initial size of the graph.
|
||||
const ThreadRow &last_thread_row = _threads.back();
|
||||
int height = row_to_pixel(last_thread_row._row_offset + last_thread_row._rows.size());
|
||||
if (height < default_timeline_height) {
|
||||
height = default_timeline_height;
|
||||
}
|
||||
height += _window.frame.size.height - _window.contentLayoutRect.size.height;
|
||||
_graph_view.frame = NSMakeRect(0, 0, default_timeline_width, height);
|
||||
_graph_view_controller.view.frame = NSMakeRect(0, 0, default_timeline_width, height);
|
||||
|
||||
// Add a drawing area to the left of the graph to show the thread labels.
|
||||
_thread_area = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, default_timeline_sidebar_width, 0)];
|
||||
_thread_area.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
_thread_area.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
// It's put inside a scroll view that tracks the main scroll view.
|
||||
NSScrollView *scroll_view = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, default_timeline_sidebar_width, 0)];
|
||||
scroll_view.documentView = _thread_area;
|
||||
scroll_view.drawsBackground = NO;
|
||||
scroll_view.automaticallyAdjustsContentInsets = YES;
|
||||
//scroll_view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
scroll_view.hasHorizontalScroller = NO;
|
||||
scroll_view.hasVerticalScroller = NO;
|
||||
_sidebar_scroll_view = scroll_view;
|
||||
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
[center addObserver:_graph_view_controller
|
||||
selector:@selector(handleSideScroll:)
|
||||
name:NSScrollViewDidLiveScrollNotification
|
||||
object:scroll_view];
|
||||
|
||||
NSViewController *sidebar_controller = [[NSViewController alloc] init];
|
||||
sidebar_controller.view = scroll_view;
|
||||
|
||||
NSSplitViewController *svc = [[NSSplitViewController alloc] init];
|
||||
[svc addSplitViewItem:[NSSplitViewItem sidebarWithViewController:sidebar_controller]];
|
||||
[svc addSplitViewItem:[NSSplitViewItem splitViewItemWithViewController:_graph_view_controller]];
|
||||
|
||||
svc.splitViewItems[0].minimumThickness = minimum_timeline_sidebar_width;
|
||||
svc.splitViewItems[0].canCollapse = NO;
|
||||
|
||||
NSSplitView *split_view = svc.splitView;
|
||||
split_view.vertical = YES;
|
||||
split_view.dividerStyle = NSSplitViewDividerStyleThin;
|
||||
split_view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
_split_view = split_view;
|
||||
|
||||
_window.contentViewController = svc;
|
||||
|
||||
[svc release];
|
||||
[sidebar_controller release];
|
||||
|
||||
// Scale area goes on top, as a titlebar accessory view.
|
||||
MacStatsScaleAreaController *scale_area_controller = [[MacStatsScaleAreaController alloc] initWithGraph:this];
|
||||
scale_area_controller.fullScreenMinHeight = 20;
|
||||
scale_area_controller.layoutAttribute = NSLayoutAttributeRight;
|
||||
_scale_area = scale_area_controller.view;
|
||||
[_window addTitlebarAccessoryViewController:scale_area_controller];
|
||||
[scale_area_controller release];
|
||||
|
||||
[_thread_area.widthAnchor constraintEqualToAnchor:scroll_view.widthAnchor].active = YES;
|
||||
[_thread_area.heightAnchor constraintEqualToAnchor:_graph_view.heightAnchor].active = YES;
|
||||
|
||||
_graph_height_constraint = [_graph_view.heightAnchor constraintGreaterThanOrEqualToConstant:0];
|
||||
_graph_height_constraint.active = YES;
|
||||
|
||||
[_window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
MacStatsTimeline::
|
||||
~MacStatsTimeline() {
|
||||
[_thread_area release];
|
||||
[_sidebar_scroll_view release];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 MacStatsTimeline::
|
||||
new_data(int thread_index, int frame_number) {
|
||||
PStatTimeline::new_data(thread_index, frame_number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when it is necessary to redraw the entire graph.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
force_redraw() {
|
||||
if (_ctx) {
|
||||
PStatTimeline::force_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user has resized the window, forcing a resize of the graph.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
changed_graph_size(int graph_xsize, int graph_ysize) {
|
||||
PStatTimeline::changed_size(graph_xsize, graph_ysize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erases the chart area.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
clear_region() {
|
||||
if (_ctx) {
|
||||
CGContextSetFillColorWithColor(_ctx, _background_color);
|
||||
CGContextFillRect(_ctx, CGRectMake(0, 0, get_xsize(), get_ysize()));
|
||||
|
||||
CGContextSetTextMatrix(_ctx, CGAffineTransformMakeScale(1, -1));
|
||||
//draw_guide_labels(_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erases the chart area in preparation for drawing a bunch of bars.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
begin_draw() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a horizontal separator.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
draw_separator(int row) {
|
||||
if (_ctx) {
|
||||
CGContextSetFillColorWithColor(_ctx, [NSColor gridColor].CGColor);
|
||||
CGContextFillRect(_ctx, CGRectMake(0, (row_to_pixel(row) + row_to_pixel(row + 1)) / 2.0, get_xsize(), 4.0 / 3.0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a vertical guide bar. If the row is -1, draws it in all rows.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
draw_guide_bar(int x, GuideBarStyle style) {
|
||||
draw_guide_bar(_ctx, style, x, 0, get_ysize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a single bar in the chart for the indicated row, in the color for the
|
||||
* given collector, for the indicated horizontal pixel range.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
draw_bar(int row, int from_x, int to_x, int collector_index,
|
||||
const std::string &collector_name) {
|
||||
int top = row_to_pixel(row);
|
||||
int bottom = row_to_pixel(row + 1);
|
||||
int scale = 4;
|
||||
|
||||
top += 1;
|
||||
|
||||
MacStatsMonitor *monitor = MacStatsGraph::_monitor;
|
||||
|
||||
bool is_highlighted = row == _highlighted_row && _highlighted_x >= from_x && _highlighted_x < to_x;
|
||||
CGContextSetFillColorWithColor(_ctx,
|
||||
monitor->get_collector_color(collector_index, is_highlighted));
|
||||
|
||||
if (to_x < from_x + 1) {
|
||||
// Too tiny to draw.
|
||||
}
|
||||
else if (to_x < from_x + scale) {
|
||||
// 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 {
|
||||
int left = std::max(from_x, -scale - 1);
|
||||
int right = std::min(std::max(to_x, from_x + 1), get_xsize() + scale);
|
||||
|
||||
double radius = std::min((double)scale, (right - left) / 2.0);
|
||||
CGContextBeginPath(_ctx);
|
||||
CGContextAddArc(_ctx, right - radius - 0.5, top + radius, radius, -0.5 * M_PI, 0.0, NO);
|
||||
CGContextAddArc(_ctx, right - radius - 0.5, bottom - radius, radius, 0.0, 0.5 * M_PI, NO);
|
||||
CGContextAddArc(_ctx, left + radius, bottom - radius, radius, 0.5 * M_PI, M_PI, NO);
|
||||
CGContextAddArc(_ctx, left + radius, top + radius, radius, M_PI, 1.5 * M_PI, NO);
|
||||
CGContextClosePath(_ctx);
|
||||
CGContextFillPath(_ctx);
|
||||
|
||||
if ((to_x - from_x) >= scale * 4) {
|
||||
// Only bother drawing the text if we've got some space to draw on.
|
||||
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);
|
||||
|
||||
int text_width = bounds.size.width;
|
||||
int text_height = bounds.size.height;
|
||||
|
||||
double center = (from_x + to_x) / 2.0;
|
||||
double text_left = std::max(from_x, 0) + scale / 2.0;
|
||||
double text_right = std::min(to_x, get_xsize()) - scale / 2.0;
|
||||
double text_top = top + (bottom - top - text_height) / 2.0 + text_height;
|
||||
/*
|
||||
if (text_width >= text_right - text_left) {
|
||||
size_t c = collector_name.rfind(':');
|
||||
if (text_right - text_left < scale * 6) {
|
||||
// It's a really tiny space. Draw a single letter.
|
||||
const char *ch = collector_name.data() + (c != std::string::npos ? c + 1 : 0);
|
||||
pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
|
||||
pango_layout_set_text(layout, ch, 1);
|
||||
} else {
|
||||
// Maybe just use everything after the last colon.
|
||||
if (c != std::string::npos) {
|
||||
pango_layout_set_text(layout, collector_name.data() + c + 1,
|
||||
collector_name.size() - c - 1);
|
||||
pango_layout_get_pixel_size(layout, &text_width, &text_height);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
if (text_width >= text_right - text_left) {
|
||||
// Have CoreText truncate to the correct length.
|
||||
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, text_right - text_left, kCTLineTruncationEnd, token_line);
|
||||
CFRelease(line);
|
||||
CFRelease(token_astr);
|
||||
CFRelease(token_line);
|
||||
line = trunc_line;
|
||||
CGContextSetTextPosition(_ctx, text_left, text_top);
|
||||
}
|
||||
else if (center - text_width / 2.0 < 0.0) {
|
||||
// Put it against the left-most edge.
|
||||
CGContextSetTextPosition(_ctx, scale, text_top);
|
||||
}
|
||||
else if (center + text_width / 2.0 >= get_xsize()) {
|
||||
// Put it against the right-most edge.
|
||||
CGContextSetTextPosition(_ctx, get_xsize() - scale - text_width, text_top);
|
||||
}
|
||||
else {
|
||||
// It fits just fine, center it.
|
||||
CGContextSetTextPosition(_ctx, center - text_width / 2.0, text_top);
|
||||
}
|
||||
|
||||
if (line != nullptr) {
|
||||
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 MacStatsTimeline::
|
||||
end_draw() {
|
||||
// Recalculate the size of the graph.
|
||||
if (!_threads.empty()) {
|
||||
const ThreadRow &last_thread_row = _threads.back();
|
||||
int new_height = row_to_pixel(last_thread_row._row_offset + last_thread_row._rows.size());
|
||||
_graph_height_constraint.constant = new_height;
|
||||
}
|
||||
|
||||
_graph_view.needsDisplay = YES;
|
||||
|
||||
// If we scroll sideways while we're also scrolling vertically such that the
|
||||
// overhang becomes visible due to elasticity, the overhang doesn't update.
|
||||
// I could only find this private method for fixing this problem.
|
||||
// Not needed as of macOS 14 and up, since the normal drawRect DTRT there.
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 140000
|
||||
NSClipView *clip_view = ((MacStatsScrollableGraphViewController *)_graph_view_controller).clipView;
|
||||
if ([clip_view respondsToSelector:@selector(_setNeedsDisplayInOverhang:)]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-method-access"
|
||||
[clip_view _setNeedsDisplayInOverhang:YES];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_threads_changed) {
|
||||
while (_thread_labels.size() < _threads.size()) {
|
||||
NSTextField *label = [NSTextField labelWithString:@"Thread"];
|
||||
label.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[_thread_area addSubview:label];
|
||||
|
||||
[label.rightAnchor constraintEqualToAnchor:_thread_area.rightAnchor constant:-8].active = YES;
|
||||
[_thread_area.widthAnchor constraintGreaterThanOrEqualToAnchor:label.widthAnchor constant:16].active = YES;
|
||||
|
||||
NSLayoutConstraint *constraint;
|
||||
if (@available(macOS 11.0, *)) {
|
||||
constraint = [label.topAnchor constraintEqualToAnchor:_thread_area.topAnchor];
|
||||
} else {
|
||||
constraint = [label.topAnchor constraintEqualToAnchor:_thread_area.topAnchor];
|
||||
}
|
||||
constraint.active = YES;
|
||||
|
||||
_thread_labels.push_back(std::make_pair(label, constraint));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < _threads.size(); ++i) {
|
||||
const ThreadRow &thread_row = _threads[i];
|
||||
NSTextField *label = _thread_labels[i].first;
|
||||
NSLayoutConstraint *label_constraint = _thread_labels[i].second;
|
||||
|
||||
label.stringValue = [NSString stringWithUTF8String:thread_row._label.c_str()];
|
||||
label_constraint.constant = row_to_pixel(thread_row._row_offset);
|
||||
}
|
||||
|
||||
_thread_area.needsDisplay = YES;
|
||||
_threads_changed = false;
|
||||
}
|
||||
|
||||
if (_guide_bars_changed) {
|
||||
_scale_area.needsDisplay = YES;
|
||||
_guide_bars_changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of the draw cycle.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
idle() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden by a derived class to implement an animation. If it returns
|
||||
* false, the animation timer is stopped.
|
||||
*/
|
||||
bool MacStatsTimeline::
|
||||
animate(double time, double dt) {
|
||||
return PStatTimeline::animate(time, dt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current window dimensions.
|
||||
*/
|
||||
bool MacStatsTimeline::
|
||||
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 MacStatsTimeline::
|
||||
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 *MacStatsTimeline::
|
||||
get_graph_menu(int graph_x, int graph_y) const {
|
||||
int row = pixel_to_row(graph_y);
|
||||
ColorBar bar;
|
||||
if (!find_bar(row, graph_x, bar)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_popup_bar = bar;
|
||||
|
||||
NSMenu *menu = [[[NSMenu alloc] init] autorelease];
|
||||
|
||||
std::string label = get_bar_tooltip(row, graph_x);
|
||||
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:@"Zoom To" action:@selector(handleZoomTo:) keyEquivalent:@""];
|
||||
item.target = _graph_view_controller;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Strip Chart" action:@selector(handleOpenStripChart:) keyEquivalent:@""];
|
||||
item.target = _graph_view_controller;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Flame Graph" action:@selector(handleOpenFlameGraph:) keyEquivalent:@""];
|
||||
item.target = _graph_view_controller;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
{
|
||||
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"Open Piano Roll" action:@selector(handleOpenPianoRoll:) keyEquivalent:@""];
|
||||
item.target = _graph_view_controller;
|
||||
[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 = bar._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 = bar._collector_index;
|
||||
[menu addItem:item];
|
||||
[item release];
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse hovers over the graph, and should return the text that
|
||||
* should appear on the tooltip.
|
||||
*/
|
||||
std::string MacStatsTimeline::
|
||||
get_graph_tooltip(int mouse_x, int mouse_y) const {
|
||||
return PStatTimeline::get_bar_tooltip(pixel_to_row(mouse_y), mouse_x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the mouse position within the graph window, look for draggable
|
||||
* things the mouse might be hovering over and return the appropriate DragMode
|
||||
* enum or DM_none if nothing is indicated.
|
||||
*/
|
||||
MacStatsGraph::DragMode MacStatsTimeline::
|
||||
consider_drag_start(int graph_x, int graph_y) {
|
||||
return MacStatsGraph::consider_drag_start(graph_x, graph_y);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
bool MacStatsTimeline::
|
||||
handle_key(int graph_x, int graph_y, bool pressed, UniChar c, unsigned short key_code) {
|
||||
// Accept WASD based on their position rather than their mapping
|
||||
int flag = 0;
|
||||
switch (key_code) {
|
||||
case 13:
|
||||
flag = F_w;
|
||||
break;
|
||||
case 0:
|
||||
flag = F_a;
|
||||
break;
|
||||
case 1:
|
||||
flag = F_s;
|
||||
break;
|
||||
case 2:
|
||||
flag = F_d;
|
||||
break;
|
||||
}
|
||||
if (flag == 0) {
|
||||
switch (c) {
|
||||
case 0x1c:
|
||||
case NSLeftArrowFunctionKey:
|
||||
flag = F_left;
|
||||
break;
|
||||
case 0x1d:
|
||||
case NSRightArrowFunctionKey:
|
||||
flag = F_right;
|
||||
break;
|
||||
case 'w':
|
||||
flag = F_w;
|
||||
break;
|
||||
case 'a':
|
||||
flag = F_a;
|
||||
break;
|
||||
case 's':
|
||||
flag = F_s;
|
||||
break;
|
||||
case 'd':
|
||||
flag = F_d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (flag != 0) {
|
||||
if (pressed) {
|
||||
if (flag & (F_w | F_s)) {
|
||||
_zoom_center = pixel_to_timestamp(graph_x);
|
||||
}
|
||||
if (_keys_held == 0) {
|
||||
start_animation();
|
||||
}
|
||||
_keys_held |= flag;
|
||||
}
|
||||
else if (_keys_held != 0) {
|
||||
_keys_held &= ~flag;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse button is depressed within the graph window.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
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()) {
|
||||
if (double_click && button == 0) {
|
||||
// Double-clicking on a color bar in the graph will zoom the graph into
|
||||
// that collector.
|
||||
int row = pixel_to_row(graph_y);
|
||||
ColorBar bar;
|
||||
if (find_bar(row, graph_x, bar)) {
|
||||
double width = bar._end - bar._start;
|
||||
zoom_to(width * 1.5, pixel_to_timestamp(graph_x));
|
||||
scroll_to(bar._start - width / 4.0);
|
||||
} else {
|
||||
// Double-clicking the white area zooms out.
|
||||
_zoom_speed -= 100.0;
|
||||
}
|
||||
start_animation();
|
||||
}
|
||||
|
||||
if (_potential_drag_mode == DM_none) {
|
||||
set_drag_mode(DM_pan);
|
||||
_drag_start_x = graph_x;
|
||||
_scroll_speed = 0.0;
|
||||
_zoom_center = pixel_to_timestamp(graph_x);
|
||||
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 MacStatsTimeline::
|
||||
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 MacStatsTimeline::
|
||||
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 row = pixel_to_row(graph_y);
|
||||
std::swap(_highlighted_x, graph_x);
|
||||
std::swap(_highlighted_row, row);
|
||||
|
||||
if (row >= 0) {
|
||||
PStatTimeline::force_redraw(row, graph_x, graph_x);
|
||||
}
|
||||
PStatTimeline::force_redraw(_highlighted_row, _highlighted_x, _highlighted_x);
|
||||
|
||||
if ((_keys_held & (F_w | F_s)) != 0) {
|
||||
// Update the zoom center if we move the mouse while zooming with the
|
||||
// keyboard.
|
||||
_zoom_center = pixel_to_timestamp(graph_x);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If the mouse is in some drag mode, stop highlighting.
|
||||
if (_highlighted_row != -1) {
|
||||
int row = _highlighted_row;
|
||||
_highlighted_row = -1;
|
||||
PStatTimeline::force_redraw(row, _highlighted_x, _highlighted_x);
|
||||
}
|
||||
}
|
||||
|
||||
if (_drag_mode == DM_pan) {
|
||||
int delta = _drag_start_x - graph_x;
|
||||
_drag_start_x = graph_x;
|
||||
set_horizontal_scroll(get_horizontal_scroll() + pixel_to_height(delta));
|
||||
return;
|
||||
}
|
||||
|
||||
return MacStatsGraph::handle_motion(graph_x, graph_y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse has left the graph window.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_leave() {
|
||||
if (_highlighted_row != -1) {
|
||||
int row = _highlighted_row;
|
||||
_highlighted_row = -1;
|
||||
PStatTimeline::force_redraw(row, _highlighted_x, _highlighted_x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse is moved within the graph window.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_scroll() {
|
||||
// Graph view is flipped, side bar isn't, so we need to convert coordinates
|
||||
NSPoint point;
|
||||
point.x = 0;
|
||||
point.y = _graph_view.frame.size.height - (((NSScrollView *)_graph_view_controller.view).documentVisibleRect.size.height + ((NSScrollView *)_graph_view_controller.view).documentVisibleRect.origin.y);
|
||||
[_sidebar_scroll_view.contentView scrollToPoint:point];
|
||||
[_sidebar_scroll_view reflectScrolledClipView:_sidebar_scroll_view.contentView];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_wheel(int graph_x, int graph_y, double dx, double dy) {
|
||||
if (dx != 0.0) {
|
||||
_scroll_speed -= dx;
|
||||
start_animation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_magnify(int graph_x, int graph_y, double scale) {
|
||||
zoom_by(scale * 4.0, pixel_to_timestamp(graph_x));
|
||||
start_animation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the graph window.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_draw_graph(CGContextRef ctx, NSRect rect) {
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
|
||||
for (const GuideBar &bar : _guide_bars) {
|
||||
int x = timestamp_to_pixel(bar._height);
|
||||
draw_guide_bar(ctx, bar._style, x, rect.origin.y, rect.size.height);
|
||||
}
|
||||
#endif
|
||||
|
||||
MacStatsGraph::handle_draw_graph(ctx, rect);
|
||||
|
||||
NSRect scale_frame = _scale_area.frame;
|
||||
NSRect graph_frame = _graph_view.frame;
|
||||
if (scale_frame.size.width != graph_frame.size.width) {
|
||||
scale_frame.size.width = graph_frame.size.width;
|
||||
_scale_area.frame = scale_frame;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the graph window overhang, which is the area outside the graph
|
||||
* bounds that may become visible momentarily due to scroll elasticity.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_draw_graph_overhang(CGContextRef ctx, NSRect rect) {
|
||||
CGContextSetFillColorWithColor(ctx, _background_color);
|
||||
CGContextFillRect(ctx, rect);
|
||||
|
||||
for (const GuideBar &bar : _guide_bars) {
|
||||
int x = timestamp_to_pixel(bar._height);
|
||||
draw_guide_bar(ctx, bar._style, x, rect.origin.y, rect.size.height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the scale area.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_draw_scale_area(CGContextRef ctx, NSRect rect) {
|
||||
MacStatsGraph::handle_draw_scale_area(ctx, rect);
|
||||
|
||||
draw_guide_labels(ctx);
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, [NSColor gridColor].CGColor);
|
||||
|
||||
for (const GuideBar &bar : _guide_bars) {
|
||||
int x = timestamp_to_pixel(bar._height);
|
||||
x = [_scale_area convertPoint:NSMakePoint(x, 0) fromView:_graph_view].x;
|
||||
draw_guide_bar(ctx, bar._style, x, rect.origin.y, rect.size.height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_zoom_to() {
|
||||
const ColorBar &bar = _popup_bar;
|
||||
double width = bar._end - bar._start;
|
||||
zoom_to(width * 1.5, (bar._end + bar._start) / 2.0);
|
||||
scroll_to(bar._start - width / 4.0);
|
||||
start_animation();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_open_strip_chart() {
|
||||
const ColorBar &bar = _popup_bar;
|
||||
MacStatsGraph::_monitor->open_strip_chart(bar._thread_index, bar._collector_index, false);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_open_flame_graph() {
|
||||
const ColorBar &bar = _popup_bar;
|
||||
MacStatsGraph::_monitor->open_flame_graph(bar._thread_index, bar._collector_index);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
handle_open_piano_roll() {
|
||||
const ColorBar &bar = _popup_bar;
|
||||
MacStatsGraph::_monitor->open_piano_roll(bar._thread_index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a vertical guide bar. If the row is -1, draws it in all rows.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
draw_guide_bar(CGContextRef ctx, GuideBarStyle style, int x, int y, int height) {
|
||||
double width = 1.0;
|
||||
if (style == GBS_frame) {
|
||||
width *= 2;
|
||||
}
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, [NSColor gridColor].CGColor);
|
||||
CGContextFillRect(ctx, CGRectMake(x - width / 2.0, y, width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called during the servicing of the draw event.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the text for the indicated guide bar label at the top of the graph.
|
||||
*/
|
||||
void MacStatsTimeline::
|
||||
draw_guide_label(CGContextRef ctx, const PStatGraph::GuideBar &bar) {
|
||||
const std::string &label = bar._label;
|
||||
if (label.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSColor *color;
|
||||
if (@available(macOS 11.0, *)) {
|
||||
color = [NSColor tertiaryLabelColor];
|
||||
} else {
|
||||
// Otherwise it's hard to see on the dark titlebars
|
||||
color = [NSColor windowFrameTextColor];
|
||||
}
|
||||
/*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;
|
||||
|
||||
case GBS_normal:
|
||||
color = [NSColor colorWithDeviceRed:rgb_light_gray[0] green:rgb_light_gray[1] blue:rgb_light_gray[2] alpha:1.0];
|
||||
break;
|
||||
|
||||
case GBS_frame:
|
||||
color = [NSColor colorWithDeviceRed:rgb_dark_gray[0] green:rgb_dark_gray[1] blue:rgb_dark_gray[2] alpha:1.0];
|
||||
break;
|
||||
}*/
|
||||
|
||||
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 height = bounds.size.height;
|
||||
int width = bounds.size.width;
|
||||
|
||||
NSRect graph_bounds = _graph_view.bounds;
|
||||
|
||||
int x = timestamp_to_pixel(bar._height);
|
||||
x = [_scale_area convertPoint:NSMakePoint(x, 0) fromView:_graph_view].x;
|
||||
|
||||
if (x + width >= 0 && x + width < get_xsize()) {
|
||||
if (x + width < graph_bounds.size.width) {
|
||||
CGContextSetTextPosition(ctx, x + 6, 6);
|
||||
CTLineDraw(line, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease(line);
|
||||
}
|
||||
|
||||
@implementation MacStatsTimelineViewController
|
||||
|
||||
- (void)handleZoomTo:(NSMenuItem *)item {
|
||||
((MacStatsTimeline *)_graph)->handle_zoom_to();
|
||||
}
|
||||
|
||||
- (void)handleOpenStripChart:(NSMenuItem *)item {
|
||||
((MacStatsTimeline *)_graph)->handle_open_strip_chart();
|
||||
}
|
||||
|
||||
- (void)handleOpenFlameGraph:(NSMenuItem *)item {
|
||||
((MacStatsTimeline *)_graph)->handle_open_flame_graph();
|
||||
}
|
||||
|
||||
- (void)handleOpenPianoRoll:(NSMenuItem *)item {
|
||||
((MacStatsTimeline *)_graph)->handle_open_piano_roll();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#include "macStats.mm"
|
||||
#include "macStatsAppDelegate.mm"
|
||||
#include "macStatsChartMenu.mm"
|
||||
#include "macStatsChartMenuDelegate.mm"
|
||||
#include "macStatsFlameGraph.mm"
|
||||
#include "macStatsGraph.mm"
|
||||
#include "macStatsGraphView.mm"
|
||||
#include "macStatsGraphViewController.mm"
|
||||
#include "macStatsLabel.mm"
|
||||
#include "macStatsLabelStack.mm"
|
||||
#include "macStatsMonitor.mm"
|
||||
#include "macStatsPianoRoll.mm"
|
||||
#include "macStatsScaleArea.mm"
|
||||
#include "macStatsServer.mm"
|
||||
#include "macStatsStripChart.mm"
|
||||
#include "macStatsTimeline.mm"
|
||||
|
|
@ -988,7 +988,12 @@ draw_frames(int first_frame, int last_frame) {
|
|||
if (_scroll_mode) {
|
||||
// In scrolling mode, slide the world back.
|
||||
int slide_pixels = last_pixel - _xsize;
|
||||
// This is really slow on macOS, just redraw instead
|
||||
#ifdef __APPLE__
|
||||
draw_pixels(0, first_pixel - slide_pixels);
|
||||
#else
|
||||
copy_region(slide_pixels, first_pixel, 0);
|
||||
#endif
|
||||
first_pixel -= slide_pixels;
|
||||
last_pixel -= slide_pixels;
|
||||
_start_time += (double)slide_pixels / (double)_xsize * _time_width;
|
||||
|
|
|
|||
Loading…
Reference in New Issue