/* MenuBarView.m Implementation of the MenuBarView class for the EtoileMenuServer application. Copyright (C) 2005 Saso Kiselkov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #import "MenuBarView.h" #import #import #import #import #import #import #import #import #import "MenuBarHeight.h" #import "EtoileSystemBarEntry.h" #import "BundleExtensionLoader.h" @interface MenuBarView (Private) - (void) loadSystemBarEntries; @end @implementation MenuBarView (Private) /** * Loads system bar entries and stuffs them into the system bar menu. System * bar entries are objects which conform to the EtoileSystemBarEntry protocol. * * The system bar is the menu which is brought up when the user hits * the Etoile logo on the left edge of the menubar. The entries in * there are menu items provided by system bar entries. */ - (void) loadSystemBarEntries { NSMutableDictionary * groups; NSArray * bundles; NSEnumerator * e; NSBundle * bundle; NSString * group; NSMutableArray * ungrouped; NSSortDescriptor * sortDesc; // load all the system bar entry menus bundles = [[BundleExtensionLoader shared] extensionsForBundleType: @"sysbarentry" principalClassProtocol: @protocol(EtoileSystemBarEntry) bundleSubdirectory: @"EtoileMenuServer" inDomains: 0 domainDetectionByKey: @"SystemBarEntries"]; groups = [NSMutableDictionary dictionary]; ungrouped = [NSMutableArray array]; // now load all system bar entries and put them into the `groups' // dictionary where they are each placed in an array keyed to their // group name e = [bundles objectEnumerator]; while ((bundle = [e nextObject]) != nil) { Class principalClass = [bundle principalClass]; id entry = [principalClass new]; if (entry != nil) { NSString * group = [entry menuGroup]; if (group != nil) { NSMutableArray * groupArray = [groups objectForKey: group]; if (groupArray == nil) { groupArray = [NSMutableArray array]; [groups setObject: groupArray forKey: group]; } [groupArray addObject: entry]; } else { [ungrouped addObject: entry]; } } else { NSLog(_(@"Failed to load system bar entry %@"), [bundle bundlePath]); } } sortDesc = [[[NSSortDescriptor alloc] initWithKey: @"menuItem.title" ascending: YES selector: @selector (caseInsensitiveCompare:)] autorelease]; // now sort the groups, and start adding the menu items into the // system bar for the individual entries e = [[[groups allKeys] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)] objectEnumerator]; while ((group = [e nextObject]) != nil) { // also sort the entries within a group based on the title of // their menu item NSMutableArray * groupEntries = [groups objectForKey: group]; NSEnumerator * ee; id entry; [groupEntries sortUsingDescriptors: [NSArray arrayWithObject: sortDesc]]; ee = [groupEntries objectEnumerator]; while ((entry = [ee nextObject]) != nil) { [systemMenu addItem: [entry menuItem]]; } [systemMenu addItem: [NSMenuItem separatorItem]]; } if ([ungrouped count] > 0) { id entry; e = [ungrouped objectEnumerator]; while ((entry = [e nextObject]) != nil) { [systemMenu addItem: [entry menuItem]]; } [systemMenu addItem: [NSMenuItem separatorItem]]; } } @end @implementation MenuBarView static NSImage * filler = nil, * leftEdge = nil, * rightEdge = nil, * etoileLogo = nil, * etoileLogoH = nil; + (void) initialize { if (self == [MenuBarView class]) { // load all menubar images ASSIGN(filler, [NSImage imageNamed: @"MenuBarFiller"]); ASSIGN(leftEdge, [NSImage imageNamed: @"MenuBarLeftEdge"]); ASSIGN(rightEdge, [NSImage imageNamed: @"MenuBarRightEdge"]); ASSIGN(etoileLogo, [NSImage imageNamed: @"EtoileLogo"]); ASSIGN(etoileLogoH, [NSImage imageNamed: @"EtoileLogoH"]); } } - (void) dealloc { TEST_RELEASE(systemMenu); [super dealloc]; } - initWithFrame: (NSRect) frame { if ((self = [super initWithFrame: frame]) != nil) { NSWindow * systemMenuWindow; NSRect frame; // create the system bar menu (the menu shown when the user pushes // the Etoile logo on the menubar) systemMenu = [[NSMenu alloc] initWithTitle: @"Étoilé"]; [self loadSystemBarEntries]; // last, add the Log Out entry [systemMenu addItemWithTitle: @"Log Out" action: @selector(logOut:) keyEquivalent: nil]; [systemMenu sizeToFit]; // and now adjust the menubar's window's frame to appear in the // upper left corner just under the menubar systemMenuWindow = [systemMenu window]; frame = [systemMenuWindow frame]; frame.origin.x = 0; frame.origin.y = NSHeight([[NSScreen mainScreen] frame]) - NSHeight(frame) - MenuBarHeight; [systemMenuWindow setFrameOrigin: frame.origin]; } return self; } - (void) drawRect: (NSRect) r { float offset; NSSize size; NSPoint p; // draw the background filler tiles size = [filler size]; for (offset = NSMinX(r); offset < NSMaxX(r); offset += size.width) { [filler compositeToPoint: NSMakePoint(offset, 0) operation: NSCompositeCopy]; } // now the edges size = [leftEdge size]; if (NSMinX(r) <= size.width) { [leftEdge compositeToPoint: NSZeroPoint operation: NSCompositeCopy]; } size = [rightEdge size]; p = NSMakePoint(NSMaxX([self frame]) - size.width, 0); [rightEdge compositeToPoint: p operation: NSCompositeCopy]; // and the Etoile logo if (systemLogoPushedIn) { [etoileLogoH compositeToPoint: NSZeroPoint operation: NSCompositeSourceOver]; } else { [etoileLogo compositeToPoint: NSZeroPoint operation: NSCompositeSourceOver]; } } - (BOOL) acceptsFirstMouse: (NSEvent *) ev { return YES; } - (void) mouseDown: (NSEvent *) ev { // invoke the system bar menu if the user hit the Etoile logo if ([ev locationInWindow].x < [etoileLogo size].width) { systemLogoPushedIn = YES; [self setNeedsDisplay: YES]; [NSEvent startPeriodicEventsAfterDelay: 0.1 withPeriod: 0.01]; [systemMenu display]; [[systemMenu menuRepresentation] trackWithEvent: ev]; [systemMenu close]; [NSEvent stopPeriodicEvents]; systemLogoPushedIn = NO; [self setNeedsDisplay: YES]; } } @end