// Copyright GreenKeeper Software 2005 #import "MenuController.h" #import "BOINCController.h" #import "Preferences.h" #import "ClientState.h" // Controls many of the user's actions associated with the menu @implementation MenuController - (id)init { [super init]; // Updates the menubar icon whenever client state file changes or boinc task is toggled [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateMenuImage:) name:@"clientStateChanged" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateMenuImage:) name:@"boincToggled" object:nil]; // Update statistics whenever client state file changes or boinc task is toggled [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateStatistics:) name:@"clientStateChanged" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateStatistics:) name:@"boincToggled" object:nil]; // Initialize local variables project = [@"None running" retain]; percentCompleted = [@"---" retain]; projectCredit = [@"---" retain]; project2 = [@"None running" retain]; percentCompleted2 = [@"---" retain]; projectCredit2 = [@"---" retain]; totalCredit = [@"---" retain]; return self; } - (void)awakeFromNib { // Check to see if user has two processors and checks the global preferences to see if user wants to use them both // If either of these statements is not true, then we need to make changes to the interface to reflect that NSString *globalPrefsPath = [[[Preferences prefs] objectForKey:@"boincDataDirectory"] stringByAppendingString:@"global_prefs.xml"]; NSString *globalPrefsString = [NSString stringWithContentsOfFile:globalPrefsPath]; int numProcessors = MPProcessors(); if(numProcessors != 2 || [globalPrefsString rangeOfString:@"2"].location == NSNotFound) { int i; for(i = 0; i <= 7; i++) { [menu removeItemAtIndex:17]; } } // Create and add menu to the status menu NSStatusBar *statusBar = [NSStatusBar systemStatusBar]; statusItem = [statusBar statusItemWithLength:NSSquareStatusItemLength]; [statusItem setHighlightMode:YES]; [statusItem setMenu:menu]; [self updateMenuImage:nil]; [statusItem retain]; } - (void)dealloc { // Local releases [statusItem release]; [project release]; [percentCompleted release]; [projectCredit release]; [project2 release]; [percentCompleted2 release]; [projectCredit2 release]; [totalCredit release]; // Eliminate listeners [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } // Opens the main BOINC site - (IBAction)openBoincSite:(id)sender { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://boinc.berkeley.edu/"]]; } // Opens the GreenKeeper Software site - (IBAction)openGKSSite:(id)sender { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.greenkeepersoftware.com/"]]; } // Opens standard about box - (IBAction)openAbout:(id)sender { [NSApp activateIgnoringOtherApps:YES]; [NSApp orderFrontStandardAboutPanel:self]; } // Opens built-in help - (IBAction)openHelp:(id)sender { [NSApp showHelp:self]; } // Runs benchmarks - (IBAction)runBenchmarks:(id)sender { [NSApp activateIgnoringOtherApps:YES]; NSString *message = @"You are about to run CPU benchmarks!"; NSString *informativeText = @"You only need to do this if you have recently modified your computer's hardware. Would you like to continue?"; int choice = NSRunAlertPanel(message, informativeText, @"Okay", @"Cancel", nil); if (choice != NSAlertDefaultReturn) return; /*TEMP if([BoincTask boincTask]) [[BoincTask boincTask] release]; [[BoincTask alloc] initAndStartWithArguments:[NSArray arrayWithObject:@"-run_cpu_benchmarks"]]; */ } // Starts boinc - (IBAction)start:(id)sender { [BOINCController startBOINC]; } // Stops boinc - (IBAction)stop:(id)sender { [BOINCController stopBOINC]; } // Quits the program - (IBAction)quit:(id)sender { // First warn everybody that we are about to quit [[NSNotificationCenter defaultCenter] postNotificationName:@"userRequestedQuit" object:nil]; [NSApp terminate:self]; } // Updates the statistics listed in the menu - (void)updateStatistics:(NSNotification *)notification { // Use temp to prevent a problem if user opens menu after old is released but before new is set NSString *temp; // Grab information for active projects NSArray *activeWorkUnits = [[ClientState clientState] infoForActiveWorkUnits]; // If there are projects running, update stats if([activeWorkUnits count] && [BOINCController boincIsRunning]) { // Handle info for the first project NSDictionary *workUnitInfo = [activeWorkUnits objectAtIndex:0]; // Update current project temp = project; project = [[workUnitInfo objectForKey:@"name"] retain]; [temp release]; // Update percentage completed temp = percentCompleted; percentCompleted = [[workUnitInfo objectForKey:@"percentCompleted"] retain]; [temp release]; // Update project credit temp = projectCredit; projectCredit = [[workUnitInfo objectForKey:@"credit"] retain]; [temp release]; // Handle info for the second project, if necessary if([activeWorkUnits count] == 2) { workUnitInfo = [activeWorkUnits objectAtIndex:1]; // Update current project temp = project2; project2 = [[workUnitInfo objectForKey:@"name"] retain]; [temp release]; // Update percentage completed temp = percentCompleted2; percentCompleted2 = [[workUnitInfo objectForKey:@"percentCompleted"] retain]; [temp release]; // Update project credit temp = projectCredit2; projectCredit2 = [[workUnitInfo objectForKey:@"credit"] retain]; [temp release]; } } // If boinc is stopped, replaces stats with default values else { temp = project; project = [@"None running" retain]; [temp release]; temp = percentCompleted; percentCompleted = [@"---" retain]; [temp release]; temp = projectCredit; projectCredit = [@"---" retain]; [temp release]; temp = project2; project2 = [@"None running" retain]; [temp release]; temp = percentCompleted2; percentCompleted2 = [@"---" retain]; [temp release]; temp = projectCredit2; projectCredit2 = [@"---" retain]; [temp release]; } // Always display total credit temp = totalCredit; totalCredit = [[[ClientState clientState] totalCredit] retain]; [temp release]; } // Updates the status menu image and menu items - (void)updateMenuImage:(NSNotification *)notification { NSArray *activeWorkUnits = [[ClientState clientState] infoForActiveWorkUnits]; // Handle main image if([BOINCController boincIsRunning] && [activeWorkUnits count]) { NSImage *menubarImage; if([activeWorkUnits count] == 2) menubarImage = [[NSImage alloc] initWithSize:NSMakeSize(44, 22)]; else menubarImage = [[NSImage alloc] initWithSize:NSMakeSize(22, 22)]; float x = [menubarImage size].width - 2.0; [menubarImage lockFocus]; int i; for(i = 0; i < [activeWorkUnits count]; i++) { NSString *projectName = [[activeWorkUnits objectAtIndex:i] objectForKey:@"name"]; NSString *percentage = [[activeWorkUnits objectAtIndex:i] objectForKey:@"percentCompleted"]; if(percentage && [percentage doubleValue] > 0.0 && [percentage doubleValue] <= 100.0) { NSString *progressFilename = [NSString stringWithFormat:@"Progress%.0f", ([percentage doubleValue] / 100.0) * 14.0]; NSString *progressImagePath = [[NSBundle mainBundle] pathForResource:progressFilename ofType:@"tif"]; NSImage *progressIcon = [[[NSImage alloc] initByReferencingFile:progressImagePath] autorelease]; [progressIcon compositeToPoint:NSMakePoint(x, 0.0) operation:NSCompositeSourceOver]; } x -= 18.0; NSString *projectIconPath; if([projectName isEqualToString:@"SETI@home"]) projectIconPath = [[NSBundle mainBundle] pathForResource:@"SAHIcon" ofType:@"tif"]; else if([projectName isEqualToString:@"climateprediction.net"]) projectIconPath = [[NSBundle mainBundle] pathForResource:@"CPIcon" ofType:@"tif"]; else if([projectName isEqualToString:@"Einstein@Home"]) projectIconPath = [[NSBundle mainBundle] pathForResource:@"EAHIcon" ofType:@"tif"]; else if([projectName isEqualToString:@"ProteinPredictorAtHome"]) projectIconPath = [[NSBundle mainBundle] pathForResource:@"PPAHIcon" ofType:@"tif"]; else if([projectName isEqualToString:@"The Lattice Project"]) projectIconPath = [[NSBundle mainBundle] pathForResource:@"LPIcon" ofType:@"tif"]; else projectIconPath = [[NSBundle mainBundle] pathForResource:@"BOINCIcon" ofType:@"tif"]; NSImage *projectIcon = [[[NSImage alloc] initByReferencingFile:projectIconPath] autorelease]; [projectIcon compositeToPoint:NSMakePoint(x, 0.0) operation:NSCompositeSourceOver]; x = 18.0; } [menubarImage unlockFocus]; [statusItem setLength:[menubarImage size].width + 8.0]; [statusItem setImage:menubarImage]; } else if([BOINCController boincIsRunning]) { [statusItem setLength:NSSquareStatusItemLength]; [statusItem setImage:[[[NSImage alloc] initByReferencingFile:[[NSBundle mainBundle] pathForResource:@"BOINCIcon" ofType:@"tif"]] autorelease]]; } else { [statusItem setLength:NSSquareStatusItemLength]; [statusItem setImage:[[[NSImage alloc] initByReferencingFile:[[NSBundle mainBundle] pathForResource:@"BOINCIconOff" ofType:@"tif"]] autorelease]]; } // Handle alternate image if([BOINCController boincIsRunning] && [activeWorkUnits count]) { NSImage *menubarImageW; if([activeWorkUnits count] == 2) menubarImageW = [[NSImage alloc] initWithSize:NSMakeSize(44, 22)]; else menubarImageW = [[NSImage alloc] initWithSize:NSMakeSize(22, 22)]; float x = [menubarImageW size].width - 2.0; [menubarImageW lockFocus]; int i; for(i = 0; i < [activeWorkUnits count]; i++) { NSString *projectName = [[activeWorkUnits objectAtIndex:i] objectForKey:@"name"]; NSString *percentage = [[activeWorkUnits objectAtIndex:i] objectForKey:@"percentCompleted"]; if(percentage && [percentage doubleValue] > 0.0 && [percentage doubleValue] <= 100.0) { NSString *progressFilenameW = [NSString stringWithFormat:@"Progress%.0fW", ([percentage doubleValue] / 100.0) * 14.0]; NSString *progressImageWPath = [[NSBundle mainBundle] pathForResource:progressFilenameW ofType:@"tif"]; NSImage *progressIconW = [[[NSImage alloc] initByReferencingFile:progressImageWPath] autorelease]; [progressIconW compositeToPoint:NSMakePoint(x, 0.0) operation:NSCompositeSourceOver]; } x -= 18.0; NSString *projectIconWPath; if([projectName isEqualToString:@"SETI@home"]) projectIconWPath = [[NSBundle mainBundle] pathForResource:@"SAHIconW" ofType:@"tif"]; else if([projectName isEqualToString:@"climateprediction.net"]) projectIconWPath = [[NSBundle mainBundle] pathForResource:@"CPIconW" ofType:@"tif"]; else if([projectName isEqualToString:@"Einstein@Home"]) projectIconWPath = [[NSBundle mainBundle] pathForResource:@"EAHIconW" ofType:@"tif"]; else if([projectName isEqualToString:@"ProteinPredictorAtHome"]) projectIconWPath = [[NSBundle mainBundle] pathForResource:@"PPAHIconW" ofType:@"tif"]; else if([projectName isEqualToString:@"The Lattice Project"]) projectIconWPath = [[NSBundle mainBundle] pathForResource:@"LPIconW" ofType:@"tif"]; else projectIconWPath = [[NSBundle mainBundle] pathForResource:@"BOINCIconW" ofType:@"tif"]; NSImage *projectIconW = [[[NSImage alloc] initByReferencingFile:projectIconWPath] autorelease]; [projectIconW compositeToPoint:NSMakePoint(x, 0.0) operation:NSCompositeSourceOver]; x = 18.0; } [menubarImageW unlockFocus]; [statusItem setAlternateImage:menubarImageW]; } else { [statusItem setAlternateImage:[[[NSImage alloc] initByReferencingFile:[[NSBundle mainBundle] pathForResource:@"BOINCIconOffW" ofType:@"tif"]] autorelease]]; } } // Handles turning on and off of menu items according to status of boinc - (BOOL)validateMenuItem:(NSMenuItem*)anItem { // If there is a task running, start should be off and vice versa if(anItem == startItem) { if(![[[ClientState clientState] projects] count]) return NO; return [BOINCController boincIsRunning] == nil; } // If there is a task running, stop should be on and vice versa if(anItem == stopItem) return [BOINCController boincIsRunning] != nil ; return YES; } // Called by the menu right before it is displayed // This method updates the menu with current project and appropriate statistics - (void)menuNeedsUpdate:(NSMenu *)aMenu { // Handle first project [currentProjectItem setTitle:[@" " stringByAppendingString:project]]; if(![projectCredit doubleValue]) [projectCreditItem setTitle:@" ---"]; else [projectCreditItem setTitle:[@" " stringByAppendingString:projectCredit]]; if([percentCompleted doubleValue] > 0.0 && [percentCompleted doubleValue] <= 100.0) [percentCompletedItem setTitle:[NSString stringWithFormat:@" %@%%", percentCompleted]]; else [percentCompletedItem setTitle:@" ---"]; // Handle second project if necessary if([menu numberOfItems] == 32) { [currentProjectItem2 setTitle:[@" " stringByAppendingString:project2]]; if(![projectCredit2 doubleValue]) [projectCreditItem2 setTitle:@" ---"]; else [projectCreditItem2 setTitle:[@" " stringByAppendingString:projectCredit2]]; if([percentCompleted2 doubleValue] > 0.0 && [percentCompleted2 doubleValue] <= 100.0) [percentCompletedItem2 setTitle:[NSString stringWithFormat:@" %@%%", percentCompleted2]]; else [percentCompletedItem2 setTitle:@" ---"]; } // Total credit if(![totalCredit doubleValue]) [totalCreditItem setTitle:@" ---"]; [totalCreditItem setTitle:[@" " stringByAppendingString:totalCredit]]; } @end