Fixed some warnings. Upped the server check to 90 seconds, from 45.
[MenuTunes.git] / MainController.m
1 #import "MainController.h"
2 #import "MenuController.h"
3 #import "PreferencesController.h"
4 #import "NetworkController.h"
5 #import "NetworkObject.h"
6 #import <ITKit/ITHotKeyCenter.h>
7 #import <ITKit/ITHotKey.h>
8 #import <ITKit/ITKeyCombo.h>
9 #import "StatusWindow.h"
10 #import "StatusWindowController.h"
11 #import "StatusItemHack.h"
12
13 @implementation NSImage (SmoothAdditions)
14
15 - (NSImage *)imageScaledSmoothlyToSize:(NSSize)scaledSize
16 {
17     NSImage *newImage;
18     NSImageRep *rep = [self bestRepresentationForDevice:nil];
19     
20     newImage = [[NSImage alloc] initWithSize:scaledSize];
21     [newImage lockFocus];
22     {
23         [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
24         [[NSGraphicsContext currentContext] setShouldAntialias:YES];
25         [rep drawInRect:NSMakeRect(3, 3, scaledSize.width - 6, scaledSize.height - 6)];
26     }
27     [newImage unlockFocus];
28     return [newImage autorelease];
29 }
30
31 @end
32
33 @interface MainController(Private)
34 - (ITMTRemote *)loadRemote;
35 - (void)setLatestSongIdentifier:(NSString *)newIdentifier;
36 - (void)checkForRemoteServer;
37 - (void)applicationLaunched:(NSNotification *)note;
38 - (void)applicationTerminated:(NSNotification *)note;
39 @end
40
41 static MainController *sharedController;
42
43 @implementation MainController
44
45 + (MainController *)sharedController
46 {
47     return sharedController;
48 }
49
50 /*************************************************************************/
51 #pragma mark -
52 #pragma mark INITIALIZATION/DEALLOCATION METHODS
53 /*************************************************************************/
54
55 - (id)init
56 {
57     if ( ( self = [super init] ) ) {
58         sharedController = self;
59         
60         remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
61         [[PreferencesController sharedPrefs] setController:self];
62         statusWindowController = [StatusWindowController sharedController];
63         menuController = [[MenuController alloc] init];
64         df = [[NSUserDefaults standardUserDefaults] retain];
65         timerUpdating = NO;
66         blinged = NO;
67     }
68     return self;
69 }
70
71 - (void)applicationDidFinishLaunching:(NSNotification *)note
72 {
73     //Turn on debug mode if needed
74     if ([df boolForKey:@"ITDebugMode"]) {
75         SetITDebugMode(YES);
76     }
77     
78     if (([df integerForKey:@"appVersion"] < 1200) && ([df integerForKey:@"SongsInAdvance"] > 0)) {
79         [df removePersistentDomainForName:@"com.ithinksw.menutunes"];
80         [df synchronize];
81         [[PreferencesController sharedPrefs] registerDefaults];
82         [[StatusWindowController sharedController] showPreferencesUpdateWindow];
83     }
84     
85     currentRemote = [self loadRemote];
86     [[self currentRemote] begin];
87     
88     //Turn on network stuff if needed
89     networkController = [[NetworkController alloc] init];
90     if ([df boolForKey:@"enableSharing"]) {
91         [self setServerStatus:YES];
92     } else if ([df boolForKey:@"useSharedPlayer"]) {
93         [self checkForRemoteServer];
94     }
95     
96     //Setup for notification of the remote player launching or quitting
97     [[[NSWorkspace sharedWorkspace] notificationCenter]
98             addObserver:self
99             selector:@selector(applicationTerminated:)
100             name:NSWorkspaceDidTerminateApplicationNotification
101             object:nil];
102     
103     [[[NSWorkspace sharedWorkspace] notificationCenter]
104             addObserver:self
105             selector:@selector(applicationLaunched:)
106             name:NSWorkspaceDidLaunchApplicationNotification
107             object:nil];
108     
109     if (![df objectForKey:@"menu"]) {  // If this is nil, defaults have never been registered.
110         [[PreferencesController sharedPrefs] registerDefaults];
111     }
112     
113     if ([df boolForKey:@"ITMTNoStatusItem"]) {
114         statusItem = nil;
115     } else {
116         [StatusItemHack install];
117         statusItem = [[ITStatusItem alloc]
118                 initWithStatusBar:[NSStatusBar systemStatusBar]
119                 withLength:NSSquareStatusItemLength];
120     }
121     
122     bling = [[MTBlingController alloc] init];
123     [self blingTime];
124     registerTimer = [[NSTimer scheduledTimerWithTimeInterval:10.0
125                              target:self
126                              selector:@selector(blingTime)
127                              userInfo:nil
128                              repeats:YES] retain];
129     
130     NS_DURING
131         if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
132             [self applicationLaunched:nil];
133         } else {
134             if ([df boolForKey:@"LaunchPlayerWithMT"])
135                 [self showPlayer];
136             else
137                 [self applicationTerminated:nil];
138         }
139     NS_HANDLER
140         [self networkError:localException];
141     NS_ENDHANDLER
142     
143     [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
144     [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
145
146     [networkController startRemoteServerSearch];
147     [NSApp deactivate];
148 }
149
150 - (ITMTRemote *)loadRemote
151 {
152     NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
153     ITDebugLog(@"Gathering remotes.");
154     if (folderPath) {
155         NSArray      *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
156         NSEnumerator *enumerator     = [bundlePathList objectEnumerator];
157         NSString     *bundlePath;
158
159         while ( (bundlePath = [enumerator nextObject]) ) {
160             NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
161
162             if (remoteBundle) {
163                 Class remoteClass = [remoteBundle principalClass];
164
165                 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
166                     [(NSObject *)remoteClass isKindOfClass:[NSObject class]]) {
167                     id remote = [remoteClass remote];
168                     ITDebugLog(@"Adding remote at path %@", bundlePath);
169                     [remoteArray addObject:remote];
170                 }
171             }
172         }
173
174 //      if ( [remoteArray count] > 0 ) {  // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
175 //          if ( [remoteArray count] > 1 ) {
176 //              [remoteArray sortUsingSelector:@selector(sortAlpha:)];
177 //          }
178 //          [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
179 //      }
180     }
181 //  NSLog(@"%@", [remoteArray objectAtIndex:0]);  //DEBUG
182     return [remoteArray objectAtIndex:0];
183 }
184
185 /*************************************************************************/
186 #pragma mark -
187 #pragma mark INSTANCE METHODS
188 /*************************************************************************/
189
190 /*- (void)startTimerInNewThread
191 {
192     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
193     NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
194     refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
195                              target:self
196                              selector:@selector(timerUpdate)
197                              userInfo:nil
198                              repeats:YES] retain];
199     [runLoop run];
200     ITDebugLog(@"Timer started.");
201     [pool release];
202 }*/
203
204 - (void)setBlingTime:(NSDate*)date
205 {
206     NSMutableDictionary *globalPrefs;
207     [df synchronize];
208     globalPrefs = [[df persistentDomainForName:@".GlobalPreferences"] mutableCopy];
209     if (date) {
210         [globalPrefs setObject:date forKey:@"ITMTTrialStart"];
211         [globalPrefs setObject:[NSNumber numberWithInt:MT_CURRENT_VERSION] forKey:@"ITMTTrialVers"];
212     } else {
213         [globalPrefs removeObjectForKey:@"ITMTTrialStart"];
214         [globalPrefs removeObjectForKey:@"ITMTTrialVers"];
215     }
216     [df setPersistentDomain:globalPrefs forName:@".GlobalPreferences"];
217     [df synchronize];
218     [globalPrefs release];
219 }
220
221 - (NSDate*)getBlingTime
222 {
223     [df synchronize];
224     return [[df persistentDomainForName:@".GlobalPreferences"] objectForKey:@"ITMTTrialStart"];
225 }
226
227 - (void)blingTime
228 {
229     NSDate *now = [NSDate date];
230     if (![self blingBling]) {
231         if ( (! [self getBlingTime] ) || ([now timeIntervalSinceDate:[self getBlingTime]] < 0) ) {
232             [self setBlingTime:now];
233         } else if ([[[df persistentDomainForName:@".GlobalPreferences"] objectForKey:@"ITMTTrialVers"] intValue] < MT_CURRENT_VERSION) {
234             if ([now timeIntervalSinceDate:[self getBlingTime]] >= 345600) {
235                 [self setBlingTime:[now addTimeInterval:-259200]];
236             } else {
237                 NSMutableDictionary *globalPrefs;
238                 [df synchronize];
239                 globalPrefs = [[df persistentDomainForName:@".GlobalPreferences"] mutableCopy];
240                 [globalPrefs setObject:[NSNumber numberWithInt:MT_CURRENT_VERSION] forKey:@"ITMTTrialVers"];
241                 [df setPersistentDomain:globalPrefs forName:@".GlobalPreferences"];
242                 [df synchronize];
243                 [globalPrefs release];
244             }
245         }
246         
247         if ( ([now timeIntervalSinceDate:[self getBlingTime]] >= 604800) && (blinged != YES) ) {
248             blinged = YES;
249             [statusItem setEnabled:NO];
250             [self clearHotKeys];
251             if ([refreshTimer isValid]) {
252                 [refreshTimer invalidate];
253             }
254             [statusWindowController showRegistrationQueryWindow];
255         }
256     } else {
257         if (blinged) {
258             [statusItem setEnabled:YES];
259             [self setupHotKeys];
260             if (![refreshTimer isValid]) {
261                 [refreshTimer release];
262                 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
263                              target:self
264                              selector:@selector(timerUpdate)
265                              userInfo:nil
266                              repeats:YES] retain];
267             }
268             blinged = NO;
269         }
270         [self setBlingTime:nil];
271     }
272 }
273
274 - (void)blingNow
275 {
276     [bling showPanel];
277 }
278
279 - (BOOL)blingBling
280 {
281     if ( ! ([bling checkDone] == 2475) ) {
282         return NO;
283     } else {
284         return YES;
285     }
286 }
287
288 - (BOOL)songIsPlaying
289 {
290     NSString *identifier = nil;
291     NS_DURING
292         identifier = [[self currentRemote] playerStateUniqueIdentifier];
293     NS_HANDLER
294         [self networkError:localException];
295     NS_ENDHANDLER
296     return ( ! ([identifier isEqualToString:@"0-0"]) );
297 }
298
299 - (BOOL)radioIsPlaying
300 {
301     ITMTRemotePlayerPlaylistClass class = nil;
302     NS_DURING
303         class = [[self currentRemote] currentPlaylistClass];
304     NS_HANDLER
305         [self networkError:localException];
306     NS_ENDHANDLER
307     return (class  == ITMTRemotePlayerRadioPlaylist );
308 }
309
310 - (BOOL)songChanged
311 {
312     NSString *identifier = nil;
313     NS_DURING
314         identifier = [[self currentRemote] playerStateUniqueIdentifier];
315     NS_HANDLER
316         [self networkError:localException];
317     NS_ENDHANDLER
318     return ( ! [identifier isEqualToString:_latestSongIdentifier] );
319 }
320
321 - (NSString *)latestSongIdentifier
322 {
323     return _latestSongIdentifier;
324 }
325
326 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
327 {
328     ITDebugLog(@"Setting latest song identifier:");
329     ITDebugLog(@"   - Identifier: %@", newIdentifier);
330     [_latestSongIdentifier autorelease];
331     _latestSongIdentifier = [newIdentifier retain];
332 }
333
334 - (void)timerUpdate
335 {
336     if ( [self songChanged] && (timerUpdating != YES) && (playerRunningState == ITMTRemotePlayerRunning) ) {
337         ITDebugLog(@"The song changed.");
338         
339         if ([df boolForKey:@"runScripts"]) {
340             NSArray *scripts = [[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
341             NSEnumerator *scriptsEnum = [scripts objectEnumerator];
342             NSString *nextScript;
343             ITDebugLog(@"Running AppleScripts for song change.");
344             while ( (nextScript = [scriptsEnum nextObject]) ) {
345                 NSDictionary *error;
346                 NSAppleScript *currentScript = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] stringByAppendingPathComponent:nextScript]] error:&error];
347                 ITDebugLog(@"Running script: %@", nextScript);
348                 if (!currentScript || ![currentScript executeAndReturnError:nil]) {
349                     ITDebugLog(@"Error running script %@.", nextScript);
350                 }
351                 [currentScript release];
352             }
353         }
354         
355         timerUpdating = YES;
356         [statusItem setEnabled:NO];
357         
358         NS_DURING
359             latestPlaylistClass = [[self currentRemote] currentPlaylistClass];
360             [menuController rebuildSubmenus];
361     
362             if ( [df boolForKey:@"showSongInfoOnChange"] ) {
363                 [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
364             }
365             
366             [self setLatestSongIdentifier:[[self currentRemote] playerStateUniqueIdentifier]];
367             
368             //Create the tooltip for the status item
369             if ( [df boolForKey:@"showToolTip"] ) {
370                 NSString *artist = [[self currentRemote] currentSongArtist];
371                 NSString *title = [[self currentRemote] currentSongTitle];
372                 NSString *toolTip;
373                 ITDebugLog(@"Creating status item tooltip.");
374                 if (artist) {
375                     toolTip = [NSString stringWithFormat:@"%@ - %@", artist, title];
376                 } else if (title) {
377                     toolTip = title;
378                 } else {
379                     toolTip = @"No Song Playing";
380                 }
381                 [statusItem setToolTip:toolTip];
382             } else {
383                 [statusItem setToolTip:nil];
384             }
385         NS_HANDLER
386             [self networkError:localException];
387         NS_ENDHANDLER
388         
389         timerUpdating = NO;
390         [statusItem setEnabled:YES];
391     }
392     
393     if ([networkController isConnectedToServer]) {
394         [statusItem setMenu:([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) ? [menuController menu] : [menuController menuForNoPlayer]];
395     }
396 }
397
398 - (void)menuClicked
399 {
400     ITDebugLog(@"Menu clicked.");
401     if ([networkController isConnectedToServer]) {
402         //Used the cached version
403         return;
404     }
405     
406     NS_DURING
407         if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
408             [statusItem setMenu:[menuController menu]];
409         } else {
410             [statusItem setMenu:[menuController menuForNoPlayer]];
411         }
412     NS_HANDLER
413         [self networkError:localException];
414     NS_ENDHANDLER
415 }
416
417 //
418 //
419 // Menu Selectors
420 //
421 //
422
423 - (void)playPause
424 {
425     NS_DURING
426         ITMTRemotePlayerPlayingState state = [[self currentRemote] playerPlayingState];
427         ITDebugLog(@"Play/Pause toggled");
428         if (state == ITMTRemotePlayerPlaying) {
429             [[self currentRemote] pause];
430         } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
431             [[self currentRemote] pause];
432             [[self currentRemote] play];
433         } else {
434             [[self currentRemote] play];
435         }
436     NS_HANDLER
437         [self networkError:localException];
438     NS_ENDHANDLER
439     
440     [self timerUpdate];
441 }
442
443 - (void)nextSong
444 {
445     ITDebugLog(@"Going to next song.");
446     NS_DURING
447         [[self currentRemote] goToNextSong];
448     NS_HANDLER
449         [self networkError:localException];
450     NS_ENDHANDLER
451     [self timerUpdate];
452 }
453
454 - (void)prevSong
455 {
456     ITDebugLog(@"Going to previous song.");
457     NS_DURING
458         [[self currentRemote] goToPreviousSong];
459     NS_HANDLER
460         [self networkError:localException];
461     NS_ENDHANDLER
462     [self timerUpdate];
463 }
464
465 - (void)fastForward
466 {
467     ITDebugLog(@"Fast forwarding.");
468     NS_DURING
469         [[self currentRemote] forward];
470     NS_HANDLER
471         [self networkError:localException];
472     NS_ENDHANDLER
473     [self timerUpdate];
474 }
475
476 - (void)rewind
477 {
478     ITDebugLog(@"Rewinding.");
479     NS_DURING
480         [[self currentRemote] rewind];
481     NS_HANDLER
482         [self networkError:localException];
483     NS_ENDHANDLER
484     [self timerUpdate];
485 }
486
487 - (void)selectPlaylistAtIndex:(int)index
488 {
489     ITDebugLog(@"Selecting playlist %i", index);
490     NS_DURING
491         [[self currentRemote] switchToPlaylistAtIndex:(index % 1000) ofSourceAtIndex:(index / 1000)];
492         //[[self currentRemote] switchToPlaylistAtIndex:index];
493     NS_HANDLER
494         [self networkError:localException];
495     NS_ENDHANDLER
496     [self timerUpdate];
497 }
498
499 - (void)selectSongAtIndex:(int)index
500 {
501     ITDebugLog(@"Selecting song %i", index);
502     NS_DURING
503         [[self currentRemote] switchToSongAtIndex:index];
504     NS_HANDLER
505         [self networkError:localException];
506     NS_ENDHANDLER
507     [self timerUpdate];
508 }
509
510 - (void)selectSongRating:(int)rating
511 {
512     ITDebugLog(@"Selecting song rating %i", rating);
513     NS_DURING
514         [[self currentRemote] setCurrentSongRating:(float)rating / 100.0];
515     NS_HANDLER
516         [self networkError:localException];
517     NS_ENDHANDLER
518     [self timerUpdate];
519 }
520
521 - (void)selectEQPresetAtIndex:(int)index
522 {
523     ITDebugLog(@"Selecting EQ preset %i", index);
524     NS_DURING
525         [[self currentRemote] switchToEQAtIndex:index];
526     NS_HANDLER
527         [self networkError:localException];
528     NS_ENDHANDLER
529     [self timerUpdate];
530 }
531
532 - (void)showPlayer
533 {
534     ITDebugLog(@"Beginning show player.");
535     //if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
536         ITDebugLog(@"Showing player interface.");
537         NS_DURING
538             [[self currentRemote] showPrimaryInterface];
539         NS_HANDLER
540             [self networkError:localException];
541         NS_ENDHANDLER
542     /*} else {
543         ITDebugLog(@"Launching player.");
544         NS_DURING
545             NSString *path;
546             if ( (path = [df stringForKey:@"CustomPlayerPath"]) ) {
547             } else {
548                 pathITDebugLog(@"Showing player interface."); = [[self currentRemote] playerFullName];
549             }
550             if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
551                 ITDebugLog(@"Error Launching Player");
552             }
553         NS_HANDLER
554             [self networkError:localException];
555         NS_ENDHANDLER
556     }*/
557     ITDebugLog(@"Finished show player.");
558 }
559
560 - (void)showPreferences
561 {
562     ITDebugLog(@"Show preferences.");
563     [[PreferencesController sharedPrefs] showPrefsWindow:self];
564 }
565
566 - (void)showPreferencesAndClose
567 {
568     ITDebugLog(@"Show preferences.");
569     [[PreferencesController sharedPrefs] showPrefsWindow:self];
570     [[StatusWindow sharedWindow] setLocked:NO];
571     [[StatusWindow sharedWindow] vanish:self];
572     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
573 }
574
575 - (void)showTestWindow
576 {
577     [self showCurrentTrackInfo];
578 }
579
580 - (void)quitMenuTunes
581 {
582     ITDebugLog(@"Quitting MenuTunes.");
583     [NSApp terminate:self];
584 }
585
586 //
587 //
588
589 - (MenuController *)menuController
590 {
591     return menuController;
592 }
593
594 - (void)closePreferences
595 {
596     ITDebugLog(@"Preferences closed.");
597     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
598         [self setupHotKeys];
599     }
600 }
601
602 - (ITMTRemote *)currentRemote
603 {
604     if ([networkController isConnectedToServer] && ![[networkController networkObject] isValid]) {
605         [self networkError:nil];
606         return nil;
607     }
608     return currentRemote;
609 }
610
611 //
612 //
613 // Hot key setup
614 //
615 //
616
617 - (void)clearHotKeys
618 {
619     NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
620     ITHotKey *nextHotKey;
621     ITDebugLog(@"Clearing hot keys.");
622     while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
623         [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
624     }
625     ITDebugLog(@"Done clearing hot keys.");
626 }
627
628 - (void)setupHotKeys
629 {
630     ITHotKey *hotKey;
631     ITDebugLog(@"Setting up hot keys.");
632     
633     if (playerRunningState == ITMTRemotePlayerNotRunning && ![[NetworkController sharedController] isConnectedToServer]) {
634         return;
635     }
636     
637     if ([df objectForKey:@"PlayPause"] != nil) {
638         ITDebugLog(@"Setting up play pause hot key.");
639         hotKey = [[ITHotKey alloc] init];
640         [hotKey setName:@"PlayPause"];
641         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
642         [hotKey setTarget:self];
643         [hotKey setAction:@selector(playPause)];
644         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
645     }
646     
647     if ([df objectForKey:@"NextTrack"] != nil) {
648         ITDebugLog(@"Setting up next track hot key.");
649         hotKey = [[ITHotKey alloc] init];
650         [hotKey setName:@"NextTrack"];
651         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
652         [hotKey setTarget:self];
653         [hotKey setAction:@selector(nextSong)];
654         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
655     }
656     
657     if ([df objectForKey:@"PrevTrack"] != nil) {
658         ITDebugLog(@"Setting up previous track hot key.");
659         hotKey = [[ITHotKey alloc] init];
660         [hotKey setName:@"PrevTrack"];
661         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
662         [hotKey setTarget:self];
663         [hotKey setAction:@selector(prevSong)];
664         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
665     }
666     
667     if ([df objectForKey:@"ShowPlayer"] != nil) {
668         ITDebugLog(@"Setting up show player hot key.");
669         hotKey = [[ITHotKey alloc] init];
670         [hotKey setName:@"ShowPlayer"];
671         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
672         [hotKey setTarget:self];
673         [hotKey setAction:@selector(showPlayer)];
674         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
675     }
676     
677     if ([df objectForKey:@"TrackInfo"] != nil) {
678         ITDebugLog(@"Setting up track info hot key.");
679         hotKey = [[ITHotKey alloc] init];
680         [hotKey setName:@"TrackInfo"];
681         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
682         [hotKey setTarget:self];
683         [hotKey setAction:@selector(showCurrentTrackInfo)];
684         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
685     }
686     
687     if ([df objectForKey:@"UpcomingSongs"] != nil) {
688         ITDebugLog(@"Setting up upcoming songs hot key.");
689         hotKey = [[ITHotKey alloc] init];
690         [hotKey setName:@"UpcomingSongs"];
691         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
692         [hotKey setTarget:self];
693         [hotKey setAction:@selector(showUpcomingSongs)];
694         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
695     }
696     
697     if ([df objectForKey:@"ToggleLoop"] != nil) {
698         ITDebugLog(@"Setting up toggle loop hot key.");
699         hotKey = [[ITHotKey alloc] init];
700         [hotKey setName:@"ToggleLoop"];
701         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
702         [hotKey setTarget:self];
703         [hotKey setAction:@selector(toggleLoop)];
704         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
705     }
706     
707     if ([df objectForKey:@"ToggleShuffle"] != nil) {
708         ITDebugLog(@"Setting up toggle shuffle hot key.");
709         hotKey = [[ITHotKey alloc] init];
710         [hotKey setName:@"ToggleShuffle"];
711         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
712         [hotKey setTarget:self];
713         [hotKey setAction:@selector(toggleShuffle)];
714         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
715     }
716     
717     if ([df objectForKey:@"IncrementVolume"] != nil) {
718         ITDebugLog(@"Setting up increment volume hot key.");
719         hotKey = [[ITHotKey alloc] init];
720         [hotKey setName:@"IncrementVolume"];
721         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
722         [hotKey setTarget:self];
723         [hotKey setAction:@selector(incrementVolume)];
724         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
725     }
726     
727     if ([df objectForKey:@"DecrementVolume"] != nil) {
728         ITDebugLog(@"Setting up decrement volume hot key.");
729         hotKey = [[ITHotKey alloc] init];
730         [hotKey setName:@"DecrementVolume"];
731         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
732         [hotKey setTarget:self];
733         [hotKey setAction:@selector(decrementVolume)];
734         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
735     }
736     
737     if ([df objectForKey:@"IncrementRating"] != nil) {
738         ITDebugLog(@"Setting up increment rating hot key.");
739         hotKey = [[ITHotKey alloc] init];
740         [hotKey setName:@"IncrementRating"];
741         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
742         [hotKey setTarget:self];
743         [hotKey setAction:@selector(incrementRating)];
744         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
745     }
746     
747     if ([df objectForKey:@"DecrementRating"] != nil) {
748         ITDebugLog(@"Setting up decrement rating hot key.");
749         hotKey = [[ITHotKey alloc] init];
750         [hotKey setName:@"DecrementRating"];
751         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
752         [hotKey setTarget:self];
753         [hotKey setAction:@selector(decrementRating)];
754         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
755     }
756     ITDebugLog(@"Finished setting up hot keys.");
757 }
758
759 - (void)showCurrentTrackInfo
760 {
761     ITMTRemotePlayerSource  source      = 0;
762     NSString               *title       = nil;
763     NSString               *album       = nil;
764     NSString               *artist      = nil;
765     NSString               *composer    = nil;
766     NSString               *time        = nil;
767     NSString               *track       = nil;
768     NSImage                *art         = nil;
769     int                     rating      = -1;
770     
771     ITDebugLog(@"Showing track info status window.");
772     
773     NS_DURING
774         source      = [[self currentRemote] currentSource];
775         title       = [[self currentRemote] currentSongTitle];
776     NS_HANDLER
777         [self networkError:localException];
778     NS_ENDHANDLER
779     
780     if ( title ) {
781
782         if ( [df boolForKey:@"showAlbum"] ) {
783             NS_DURING
784                 album = [[self currentRemote] currentSongAlbum];
785             NS_HANDLER
786                 [self networkError:localException];
787             NS_ENDHANDLER
788         }
789
790         if ( [df boolForKey:@"showArtist"] ) {
791             NS_DURING
792                 artist = [[self currentRemote] currentSongArtist];
793             NS_HANDLER
794                 [self networkError:localException];
795             NS_ENDHANDLER
796         }
797
798         if ( [df boolForKey:@"showComposer"] ) {
799             NS_DURING
800                 composer = [[self currentRemote] currentSongComposer];
801             NS_HANDLER
802                 [self networkError:localException];
803             NS_ENDHANDLER
804         }
805
806         if ( [df boolForKey:@"showTime"] ) {
807             NS_DURING
808                 time = [NSString stringWithFormat:@"%@: %@ / %@",
809                 @"Time",
810                 [[self currentRemote] currentSongElapsed],
811                 [[self currentRemote] currentSongLength]];
812             NS_HANDLER
813                 [self networkError:localException];
814             NS_ENDHANDLER
815         }
816
817         if ( [df boolForKey:@"showTrackNumber"] ) {
818             int trackNo    = 0;
819             int trackCount = 0;
820             
821             NS_DURING
822                 trackNo    = [[self currentRemote] currentSongTrack];
823                 trackCount = [[self currentRemote] currentAlbumTrackCount];
824             NS_HANDLER
825                 [self networkError:localException];
826             NS_ENDHANDLER
827             
828             if ( (trackNo > 0) || (trackCount > 0) ) {
829                 track = [NSString stringWithFormat:@"%@: %i %@ %i",
830                     @"Track", trackNo, @"of", trackCount];
831             }
832         }
833
834         if ( [df boolForKey:@"showTrackRating"] ) {
835             float currentRating = 0;
836             
837             NS_DURING
838                 currentRating = [[self currentRemote] currentSongRating];
839             NS_HANDLER
840                 [self networkError:localException];
841             NS_ENDHANDLER
842             
843             if (currentRating >= 0.0) {
844                 rating = ( currentRating * 5 );
845             }
846         }
847         
848         if ( [df boolForKey:@"showAlbumArtwork"] ) {
849             NSSize oldSize, newSize;
850              NS_DURING
851                  art = [[self currentRemote] currentSongAlbumArt];
852                  oldSize = [art size];
853                  if (oldSize.width > oldSize.height) newSize = NSMakeSize(110,oldSize.height * (110.0f / oldSize.width));
854                  else newSize = NSMakeSize(oldSize.width * (110.0f / oldSize.height),110);
855                 art = [[[[NSImage alloc] initWithData:[art TIFFRepresentation]] autorelease] imageScaledSmoothlyToSize:newSize];
856             NS_HANDLER
857                 [self networkError:localException];
858             NS_ENDHANDLER
859         }
860         
861     } else {
862         title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
863     }
864     ITDebugLog(@"Showing current track info status window.");
865     [statusWindowController showSongInfoWindowWithSource:source
866                                                    title:title
867                                                    album:album
868                                                   artist:artist
869                                                 composer:composer
870                                                     time:time
871                                                    track:track
872                                                   rating:rating
873                                                    image:art];
874 }
875
876 - (void)showUpcomingSongs
877 {
878     int numSongs = 0;
879     NS_DURING
880         numSongs = [[self currentRemote] numberOfSongsInPlaylistAtIndex:[[self currentRemote] currentPlaylistIndex]];
881     NS_HANDLER
882         [self networkError:localException];
883     NS_ENDHANDLER
884     
885     ITDebugLog(@"Showing upcoming songs status window.");
886     NS_DURING
887         if (numSongs > 0) {
888             int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
889             NSMutableArray *songList = [NSMutableArray arrayWithCapacity:numSongsInAdvance];
890             int curTrack = [[self currentRemote] currentSongIndex];
891             int i;
892     
893             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
894                 if (i <= numSongs) {
895                     [songList addObject:[[self currentRemote] songTitleAtIndex:i]];
896                 }
897             }
898             
899             if ([songList count] == 0) {
900                 [songList addObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")];
901             }
902             
903             [statusWindowController showUpcomingSongsWindowWithTitles:songList];
904         } else {
905             [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
906         }
907     NS_HANDLER
908         [self networkError:localException];
909     NS_ENDHANDLER
910 }
911
912 - (void)incrementVolume
913 {
914     NS_DURING
915         float volume  = [[self currentRemote] volume];
916         float dispVol = volume;
917         ITDebugLog(@"Incrementing volume.");
918         volume  += 0.110;
919         dispVol += 0.100;
920         
921         if (volume > 1.0) {
922             volume  = 1.0;
923             dispVol = 1.0;
924         }
925     
926         ITDebugLog(@"Setting volume to %f", volume);
927         [[self currentRemote] setVolume:volume];
928     
929         // Show volume status window
930         [statusWindowController showVolumeWindowWithLevel:dispVol];
931     NS_HANDLER
932         [self networkError:localException];
933     NS_ENDHANDLER
934 }
935
936 - (void)decrementVolume
937 {
938     NS_DURING
939         float volume  = [[self currentRemote] volume];
940         float dispVol = volume;
941         ITDebugLog(@"Decrementing volume.");
942         volume  -= 0.090;
943         dispVol -= 0.100;
944     
945         if (volume < 0.0) {
946             volume  = 0.0;
947             dispVol = 0.0;
948         }
949         
950         ITDebugLog(@"Setting volume to %f", volume);
951         [[self currentRemote] setVolume:volume];
952         
953         //Show volume status window
954         [statusWindowController showVolumeWindowWithLevel:dispVol];
955     NS_HANDLER
956         [self networkError:localException];
957     NS_ENDHANDLER
958 }
959
960 - (void)incrementRating
961 {
962     NS_DURING
963         float rating = [[self currentRemote] currentSongRating];
964         ITDebugLog(@"Incrementing rating.");
965         
966         if ([[self currentRemote] currentPlaylistIndex] == 0) {
967             ITDebugLog(@"No song playing, rating change aborted.");
968             return;
969         }
970         
971         rating += 0.2;
972         if (rating > 1.0) {
973             rating = 1.0;
974         }
975         ITDebugLog(@"Setting rating to %f", rating);
976         [[self currentRemote] setCurrentSongRating:rating];
977         
978         //Show rating status window
979         [statusWindowController showRatingWindowWithRating:rating];
980     NS_HANDLER
981         [self networkError:localException];
982     NS_ENDHANDLER
983 }
984
985 - (void)decrementRating
986 {
987     NS_DURING
988         float rating = [[self currentRemote] currentSongRating];
989         ITDebugLog(@"Decrementing rating.");
990         
991         if ([[self currentRemote] currentPlaylistIndex] == 0) {
992             ITDebugLog(@"No song playing, rating change aborted.");
993             return;
994         }
995         
996         rating -= 0.2;
997         if (rating < 0.0) {
998             rating = 0.0;
999         }
1000         ITDebugLog(@"Setting rating to %f", rating);
1001         [[self currentRemote] setCurrentSongRating:rating];
1002         
1003         //Show rating status window
1004         [statusWindowController showRatingWindowWithRating:rating];
1005     NS_HANDLER
1006         [self networkError:localException];
1007     NS_ENDHANDLER
1008 }
1009
1010 - (void)toggleLoop
1011 {
1012     NS_DURING
1013         ITMTRemotePlayerRepeatMode repeatMode = [[self currentRemote] repeatMode];
1014         ITDebugLog(@"Toggling repeat mode.");
1015         switch (repeatMode) {
1016             case ITMTRemotePlayerRepeatOff:
1017                 repeatMode = ITMTRemotePlayerRepeatAll;
1018             break;
1019             case ITMTRemotePlayerRepeatAll:
1020                 repeatMode = ITMTRemotePlayerRepeatOne;
1021             break;
1022             case ITMTRemotePlayerRepeatOne:
1023                 repeatMode = ITMTRemotePlayerRepeatOff;
1024             break;
1025         }
1026         ITDebugLog(@"Setting repeat mode to %i", repeatMode);
1027         [[self currentRemote] setRepeatMode:repeatMode];
1028         
1029         //Show loop status window
1030         [statusWindowController showRepeatWindowWithMode:repeatMode];
1031     NS_HANDLER
1032         [self networkError:localException];
1033     NS_ENDHANDLER
1034 }
1035
1036 - (void)toggleShuffle
1037 {
1038     NS_DURING
1039         BOOL newShuffleEnabled = ( ! [[self currentRemote] shuffleEnabled] );
1040         ITDebugLog(@"Toggling shuffle mode.");
1041         [[self currentRemote] setShuffleEnabled:newShuffleEnabled];
1042         //Show shuffle status window
1043         ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
1044         [statusWindowController showShuffleWindow:newShuffleEnabled];
1045     NS_HANDLER
1046         [self networkError:localException];
1047     NS_ENDHANDLER
1048 }
1049
1050 - (void)registerNowOK
1051 {
1052     [[StatusWindow sharedWindow] setLocked:NO];
1053     [[StatusWindow sharedWindow] vanish:self];
1054     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1055
1056     [self blingNow];
1057 }
1058
1059 - (void)registerNowCancel
1060 {
1061     [[StatusWindow sharedWindow] setLocked:NO];
1062     [[StatusWindow sharedWindow] vanish:self];
1063     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1064
1065     [NSApp terminate:self];
1066 }
1067
1068 /*************************************************************************/
1069 #pragma mark -
1070 #pragma mark NETWORK HANDLERS
1071 /*************************************************************************/
1072
1073 - (void)setServerStatus:(BOOL)newStatus
1074 {
1075     if (newStatus) {
1076         //Turn on
1077         [networkController setServerStatus:YES];
1078     } else {
1079         //Tear down
1080         [networkController setServerStatus:NO];
1081     }
1082 }
1083
1084 - (int)connectToServer
1085 {
1086     int result;
1087     ITDebugLog(@"Attempting to connect to shared remote.");
1088     result = [networkController connectToHost:[df stringForKey:@"sharedPlayerHost"]];
1089     //Connect
1090     if (result == 1) {
1091         [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1092         currentRemote = [[[networkController networkObject] remote] retain];
1093         
1094         [self setupHotKeys];
1095         //playerRunningState = ITMTRemotePlayerRunning;
1096         playerRunningState = [[self currentRemote] playerRunningState];
1097         
1098         [refreshTimer invalidate];
1099         refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1100                                 target:self
1101                                 selector:@selector(timerUpdate)
1102                                 userInfo:nil
1103                                 repeats:YES] retain];
1104         [self timerUpdate];
1105         ITDebugLog(@"Connection successful.");
1106         return 1;
1107     } else if (result == 0) {
1108         ITDebugLog(@"Connection failed.");
1109         currentRemote = [remoteArray objectAtIndex:0];
1110         return 0;
1111     } else {
1112         //Do something about the password being invalid
1113         ITDebugLog(@"Connection failed.");
1114         currentRemote = [remoteArray objectAtIndex:0];
1115         return -1;
1116     }
1117 }
1118
1119 - (BOOL)disconnectFromServer
1120 {
1121     ITDebugLog(@"Disconnecting from shared remote.");
1122     //Disconnect
1123     [currentRemote release];
1124     currentRemote = [remoteArray objectAtIndex:0];
1125     [networkController disconnect];
1126     
1127     if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
1128         [self applicationLaunched:nil];
1129     } else {
1130         [self applicationTerminated:nil];
1131     }
1132     [self timerUpdate];
1133     return YES;
1134 }
1135
1136 - (void)checkForRemoteServer
1137 {
1138     ITDebugLog(@"Checking for remote server.");
1139     [NSThread detachNewThreadSelector:@selector(runRemoteServerCheck:) toTarget:self withObject:nil];
1140 }
1141
1142 - (void)runRemoteServerCheck:(id)sender
1143 {
1144     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1145     ITDebugLog(@"Remote server check running.");
1146     if ([networkController checkForServerAtHost:[df stringForKey:@"sharedPlayerHost"]]) {
1147         ITDebugLog(@"Remote server found.");
1148         [self performSelectorOnMainThread:@selector(remoteServerFound:) withObject:nil waitUntilDone:NO];
1149     } else {
1150         ITDebugLog(@"Remote server not found.");
1151         [self performSelectorOnMainThread:@selector(remoteServerNotFound:) withObject:nil waitUntilDone:NO];
1152     }
1153     [pool release];
1154 }
1155
1156 - (void)remoteServerFound:(id)sender
1157 {
1158     if (![networkController isServerOn] && ![networkController isConnectedToServer]) {
1159         [[StatusWindowController sharedController] showReconnectQueryWindow];
1160     }
1161 }
1162
1163 - (void)remoteServerNotFound:(id)sender
1164 {
1165     [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1166 }
1167
1168 - (void)networkError:(NSException *)exception
1169 {
1170     ITDebugLog(@"Remote exception thrown: %@: %@", [exception name], [exception reason]);
1171     if ( ((exception == nil) || [[exception name] isEqualToString:NSPortTimeoutException]) && [networkController isConnectedToServer]) {
1172         NSRunCriticalAlertPanel(@"Remote MenuTunes Disconnected", @"The MenuTunes server you were connected to stopped responding or quit. MenuTunes will revert back to the local player.", @"OK", nil, nil);
1173         if ([self disconnectFromServer]) {
1174             [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1175             [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:YES];
1176         } else {
1177             ITDebugLog(@"CRITICAL ERROR, DISCONNECTING!");
1178         }
1179     }
1180 }
1181
1182 - (void)reconnect
1183 {
1184     if ([self connectToServer] == 0) {
1185         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:YES];
1186     }
1187     [[StatusWindow sharedWindow] setLocked:NO];
1188     [[StatusWindow sharedWindow] vanish:self];
1189     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1190 }
1191
1192 - (void)cancelReconnect
1193 {
1194     [[StatusWindow sharedWindow] setLocked:NO];
1195     [[StatusWindow sharedWindow] vanish:self];
1196     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1197 }
1198
1199 /*************************************************************************/
1200 #pragma mark -
1201 #pragma mark WORKSPACE NOTIFICATION HANDLERS
1202 /*************************************************************************/
1203
1204 - (void)applicationLaunched:(NSNotification *)note
1205 {
1206     NS_DURING
1207         if (!note || ([[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer])) {
1208             ITDebugLog(@"Remote application launched.");
1209             playerRunningState = ITMTRemotePlayerRunning;
1210             [[self currentRemote] begin];
1211             [self setLatestSongIdentifier:@""];
1212             [self timerUpdate];
1213             refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1214                                 target:self
1215                                 selector:@selector(timerUpdate)
1216                                 userInfo:nil
1217                                 repeats:YES] retain];
1218             //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
1219             [self setupHotKeys];
1220         }
1221     NS_HANDLER
1222         [self networkError:localException];
1223     NS_ENDHANDLER
1224 }
1225
1226  - (void)applicationTerminated:(NSNotification *)note
1227  {
1228     NS_DURING
1229         if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer]) {
1230             ITDebugLog(@"Remote application terminated.");
1231             playerRunningState = ITMTRemotePlayerNotRunning;
1232             [[self currentRemote] halt];
1233             [refreshTimer invalidate];
1234             [refreshTimer release];
1235             refreshTimer = nil;
1236             [self clearHotKeys];
1237             
1238             if ([df objectForKey:@"ShowPlayer"] != nil) {
1239                 ITHotKey *hotKey;
1240                 ITDebugLog(@"Setting up show player hot key.");
1241                 hotKey = [[ITHotKey alloc] init];
1242                 [hotKey setName:@"ShowPlayer"];
1243                 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
1244                 [hotKey setTarget:self];
1245                 [hotKey setAction:@selector(showPlayer)];
1246                 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1247             }
1248         }
1249     NS_HANDLER
1250         [self networkError:localException];
1251     NS_ENDHANDLER
1252  }
1253
1254
1255 /*************************************************************************/
1256 #pragma mark -
1257 #pragma mark NSApplication DELEGATE METHODS
1258 /*************************************************************************/
1259
1260 - (void)applicationWillTerminate:(NSNotification *)note
1261 {
1262     [networkController stopRemoteServerSearch];
1263     [self clearHotKeys];
1264     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
1265 }
1266
1267
1268 /*************************************************************************/
1269 #pragma mark -
1270 #pragma mark DEALLOCATION METHOD
1271 /*************************************************************************/
1272
1273 - (void)dealloc
1274 {
1275     [self applicationTerminated:nil];
1276     [bling release];
1277     [statusItem release];
1278     [statusWindowController release];
1279     [menuController release];
1280     [networkController release];
1281     [super dealloc];
1282 }
1283
1284 @end