Added a status field to the audioscrobbler preferences. Using alloc'd NSTimers in...
[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 <ITKit/ITCategory-NSMenu.h>
10 #import "StatusWindow.h"
11 #import "StatusWindowController.h"
12 #import "AudioscrobblerController.h"
13 #import "StatusItemHack.h"
14
15 @interface NSMenu (MenuImpl)
16 - (id)_menuImpl;
17 @end
18
19 @interface NSCarbonMenuImpl:NSObject
20 {
21     NSMenu *_menu;
22 }
23
24 + (void)initialize;
25 + (void)setupForNoMenuBar;
26 - (void)dealloc;
27 - (void)setMenu:fp8;
28 - menu;
29 - (void)itemChanged:fp8;
30 - (void)itemAdded:fp8;
31 - (void)itemRemoved:fp8;
32 - (void)performActionWithHighlightingForItemAtIndex:(int)fp8;
33 - (void)performMenuAction:(SEL)fp8 withTarget:fp12;
34 - (void)setupCarbonMenuBar;
35 - (void)setAsMainCarbonMenuBar;
36 - (void)clearAsMainCarbonMenuBar;
37 - (void)popUpMenu:fp8 atLocation:(NSPoint)fp12 width:(float)fp20 forView:fp24 withSelectedItem:(int)fp28 withFont:fp32;
38 - (void)_popUpContextMenu:fp8 withEvent:fp12 forView:fp16 withFont:fp20;
39 - (void)_popUpContextMenu:fp8 withEvent:fp12 forView:fp16;
40 - window;
41 @end
42
43 @implementation NSImage (SmoothAdditions)
44
45 - (NSImage *)imageScaledSmoothlyToSize:(NSSize)scaledSize
46 {
47     NSImage *newImage;
48     NSImageRep *rep = [self bestRepresentationForDevice:nil];
49     
50     newImage = [[NSImage alloc] initWithSize:scaledSize];
51     [newImage lockFocus];
52     {
53         [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
54         [[NSGraphicsContext currentContext] setShouldAntialias:YES];
55         [rep drawInRect:NSMakeRect(3, 3, scaledSize.width - 6, scaledSize.height - 6)];
56     }
57     [newImage unlockFocus];
58     return [newImage autorelease];
59 }
60
61 @end
62
63 @interface MainController(Private)
64 - (ITMTRemote *)loadRemote;
65 - (void)setLatestSongIdentifier:(NSString *)newIdentifier;
66 - (void)applicationLaunched:(NSNotification *)note;
67 - (void)applicationTerminated:(NSNotification *)note;
68
69 - (void)invalidateStatusWindowUpdateTimer;
70 @end
71
72 static MainController *sharedController;
73
74 @implementation MainController
75
76 + (MainController *)sharedController
77 {
78     return sharedController;
79 }
80
81 /*************************************************************************/
82 #pragma mark -
83 #pragma mark INITIALIZATION/DEALLOCATION METHODS
84 /*************************************************************************/
85
86 - (id)init
87 {
88     if ( ( self = [super init] ) ) {
89         sharedController = self;
90         
91                 _statusWindowUpdateTimer = nil;
92                 _audioscrobblerTimer = nil;
93                 
94         remoteArray = [[NSMutableArray alloc] initWithCapacity:1];
95         [[PreferencesController sharedPrefs] setController:self];
96         statusWindowController = [StatusWindowController sharedController];
97         menuController = [[MenuController alloc] init];
98         df = [[NSUserDefaults standardUserDefaults] retain];
99         timerUpdating = NO;
100         blinged = NO;
101     }
102     return self;
103 }
104
105 - (void)applicationDidFinishLaunching:(NSNotification *)note
106 {
107         NSString *iTunesPath = [df stringForKey:@"CustomPlayerPath"];
108         NSDictionary *iTunesInfoPlist;
109         float iTunesVersion;
110         
111     //Turn on debug mode if needed
112         /*if ((GetCurrentKeyModifiers() & (controlKey | rightControlKey)) != 0)
113     if ((GetCurrentKeyModifiers() & (optionKey | rightOptionKey)) != 0)
114     if ((GetCurrentKeyModifiers() & (shiftKey | rightShiftKey)) != 0)*/
115     if ([df boolForKey:@"ITDebugMode"] || ((GetCurrentKeyModifiers() & (controlKey | rightControlKey)) != 0)) {
116         SetITDebugMode(YES);
117                 [[StatusWindowController sharedController] showDebugModeEnabledWindow];
118     }
119
120         //Check if iTunes 4.7 or later is installed     
121         if (!iTunesPath) {
122                 iTunesPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:@"iTunes.app"];
123         }
124         iTunesInfoPlist = [[NSBundle bundleWithPath:iTunesPath] infoDictionary];
125         iTunesVersion = [[iTunesInfoPlist objectForKey:@"CFBundleVersion"] floatValue];
126         ITDebugLog(@"iTunes version found: %f.", iTunesVersion);
127         if (iTunesVersion >= 4.7) {
128                 _needsPolling = NO;
129         } else {
130                 _needsPolling = YES;
131         }
132         
133     if (([df integerForKey:@"appVersion"] < 1200) && ([df integerForKey:@"SongsInAdvance"] > 0)) {
134         [df removePersistentDomainForName:@"com.ithinksw.menutunes"];
135         [df synchronize];
136         [[PreferencesController sharedPrefs] registerDefaults];
137         [[StatusWindowController sharedController] showPreferencesUpdateWindow];
138     }
139     
140     currentRemote = [self loadRemote];
141     [[self currentRemote] begin];
142     
143         [[self currentRemote] currentSongElapsed];
144         
145     //Turn on network stuff if needed
146     networkController = [[NetworkController alloc] init];
147     if ([df boolForKey:@"enableSharing"]) {
148         [self setServerStatus:YES];
149     } else if ([df boolForKey:@"useSharedPlayer"]) {
150         [self checkForRemoteServerAndConnectImmediately:YES];
151     }
152     
153     //Setup for notification of the remote player launching or quitting
154     [[[NSWorkspace sharedWorkspace] notificationCenter]
155             addObserver:self
156             selector:@selector(applicationTerminated:)
157             name:NSWorkspaceDidTerminateApplicationNotification
158             object:nil];
159     
160     [[[NSWorkspace sharedWorkspace] notificationCenter]
161             addObserver:self
162             selector:@selector(applicationLaunched:)
163             name:NSWorkspaceDidLaunchApplicationNotification
164             object:nil];
165     
166     if (![df objectForKey:@"menu"]) {  // If this is nil, defaults have never been registered.
167         [[PreferencesController sharedPrefs] registerDefaults];
168     }
169     
170     if ([df boolForKey:@"ITMTNoStatusItem"]) {
171         statusItem = nil;
172     } else {
173         [StatusItemHack install];
174         statusItem = [[ITStatusItem alloc]
175                 initWithStatusBar:[NSStatusBar systemStatusBar]
176                 withLength:NSSquareStatusItemLength];
177     }
178     
179     /*bling = [[MTBlingController alloc] init];
180     [self blingTime];
181     registerTimer = [[NSTimer scheduledTimerWithTimeInterval:10.0
182                              target:self
183                              selector:@selector(blingTime)
184                              userInfo:nil
185                              repeats:YES] retain];*/
186     
187     NS_DURING
188         if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
189             [self applicationLaunched:nil];
190         } else {
191             if ([df boolForKey:@"LaunchPlayerWithMT"])
192                 [self showPlayer];
193             else
194                 [self applicationTerminated:nil];
195         }
196     NS_HANDLER
197         [self networkError:localException];
198     NS_ENDHANDLER
199     
200     [statusItem setImage:[NSImage imageNamed:@"MenuNormal"]];
201     [statusItem setAlternateImage:[NSImage imageNamed:@"MenuInverted"]];
202
203         if ([df boolForKey:@"audioscrobblerEnabled"]) {
204                 if ([PreferencesController getKeychainItemPasswordForUser:[df stringForKey:@"audioscrobblerUser"]] != nil) {
205                         [[AudioscrobblerController sharedController] attemptHandshake:NO];
206                 }
207         }
208
209     [networkController startRemoteServerSearch];
210     [NSApp deactivate];
211         [self performSelector:@selector(rawr) withObject:nil afterDelay:1.0];
212         
213         bling = [[MTBlingController alloc] init];
214     [self blingTime];
215     registerTimer = [[NSTimer scheduledTimerWithTimeInterval:10.0
216                              target:self
217                              selector:@selector(blingTime)
218                              userInfo:nil
219                              repeats:YES] retain];
220 }
221
222 - (void)rawr
223 {
224         _open = YES;
225 }
226
227 - (ITMTRemote *)loadRemote
228 {
229     NSString *folderPath = [[NSBundle mainBundle] builtInPlugInsPath];
230     ITDebugLog(@"Gathering remotes.");
231     if (folderPath) {
232         NSArray      *bundlePathList = [NSBundle pathsForResourcesOfType:@"remote" inDirectory:folderPath];
233         NSEnumerator *enumerator     = [bundlePathList objectEnumerator];
234         NSString     *bundlePath;
235
236         while ( (bundlePath = [enumerator nextObject]) ) {
237             NSBundle* remoteBundle = [NSBundle bundleWithPath:bundlePath];
238
239             if (remoteBundle) {
240                 Class remoteClass = [remoteBundle principalClass];
241
242                 if ([remoteClass conformsToProtocol:@protocol(ITMTRemote)] &&
243                     [(NSObject *)remoteClass isKindOfClass:[NSObject class]]) {
244                     id remote = [remoteClass remote];
245                     ITDebugLog(@"Adding remote at path %@", bundlePath);
246                     [remoteArray addObject:remote];
247                 }
248             }
249         }
250
251 //      if ( [remoteArray count] > 0 ) {  // UNCOMMENT WHEN WE HAVE > 1 PLUGIN
252 //          if ( [remoteArray count] > 1 ) {
253 //              [remoteArray sortUsingSelector:@selector(sortAlpha:)];
254 //          }
255 //          [self loadModuleAccessUI]; //Comment out this line to disable remote visibility
256 //      }
257     }
258 //  NSLog(@"%@", [remoteArray objectAtIndex:0]);  //DEBUG
259     return [remoteArray objectAtIndex:0];
260 }
261
262 /*************************************************************************/
263 #pragma mark -
264 #pragma mark INSTANCE METHODS
265 /*************************************************************************/
266
267 /*- (void)startTimerInNewThread
268 {
269     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
270     NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
271     refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
272                              target:self
273                              selector:@selector(timerUpdate)
274                              userInfo:nil
275                              repeats:YES] retain];
276     [runLoop run];
277     ITDebugLog(@"Timer started.");
278     [pool release];
279 }*/
280
281 - (void)setBlingTime:(NSDate*)date
282 {
283     NSMutableDictionary *globalPrefs;
284     [df synchronize];
285     globalPrefs = [[df persistentDomainForName:@".GlobalPreferences"] mutableCopy];
286     if (date) {
287         [globalPrefs setObject:date forKey:@"ITMTTrialStart"];
288         [globalPrefs setObject:[NSNumber numberWithInt:MT_CURRENT_VERSION] forKey:@"ITMTTrialVers"];
289     } else {
290         [globalPrefs removeObjectForKey:@"ITMTTrialStart"];
291         [globalPrefs removeObjectForKey:@"ITMTTrialVers"];
292     }
293     [df setPersistentDomain:globalPrefs forName:@".GlobalPreferences"];
294     [df synchronize];
295     [globalPrefs release];
296 }
297
298 - (NSDate*)getBlingTime
299 {
300     [df synchronize];
301     return [[df persistentDomainForName:@".GlobalPreferences"] objectForKey:@"ITMTTrialStart"];
302 }
303
304 - (void)blingTime
305 {
306     NSDate *now = [NSDate date];
307     if (![self blingBling]) {
308         if ( (! [self getBlingTime] ) || ([now timeIntervalSinceDate:[self getBlingTime]] < 0) ) {
309             [self setBlingTime:now];
310         } else if ([[[df persistentDomainForName:@".GlobalPreferences"] objectForKey:@"ITMTTrialVers"] intValue] < MT_CURRENT_VERSION) {
311             if ([now timeIntervalSinceDate:[self getBlingTime]] >= 345600) {
312                 [self setBlingTime:[now addTimeInterval:-259200]];
313             } else {
314                 NSMutableDictionary *globalPrefs;
315                 [df synchronize];
316                 globalPrefs = [[df persistentDomainForName:@".GlobalPreferences"] mutableCopy];
317                 [globalPrefs setObject:[NSNumber numberWithInt:MT_CURRENT_VERSION] forKey:@"ITMTTrialVers"];
318                 [df setPersistentDomain:globalPrefs forName:@".GlobalPreferences"];
319                 [df synchronize];
320                 [globalPrefs release];
321             }
322         }
323         
324         if ( ([now timeIntervalSinceDate:[self getBlingTime]] >= 604800) && (blinged != YES) ) {
325             blinged = YES;
326             [statusItem setEnabled:NO];
327                         [[ITHotKeyCenter sharedCenter] setEnabled:NO];
328             if ([refreshTimer isValid]) {
329                 [refreshTimer invalidate];
330                                 [refreshTimer release];
331                                 refreshTimer = nil;
332             }
333             [statusWindowController showRegistrationQueryWindow];
334         }
335     } else {
336         if (blinged) {
337             [statusItem setEnabled:YES];
338             [[ITHotKeyCenter sharedCenter] setEnabled:YES];
339             if (_needsPolling && ![refreshTimer isValid]) {
340                 [refreshTimer release];
341                 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
342                              target:self
343                              selector:@selector(timerUpdate)
344                              userInfo:nil
345                              repeats:YES] retain];
346             }
347             blinged = NO;
348         }
349         [self setBlingTime:nil];
350     }
351 }
352
353 - (void)blingNow
354 {
355     [bling showPanel];
356 }
357
358 - (BOOL)blingBling
359 {
360     if ( ! ([bling checkDone] == 2475) ) {
361         return NO;
362     } else {
363         return YES;
364     }
365 }
366
367 - (BOOL)songIsPlaying
368 {
369     NSString *identifier = nil;
370     NS_DURING
371         identifier = [[self currentRemote] playerStateUniqueIdentifier];
372     NS_HANDLER
373         [self networkError:localException];
374     NS_ENDHANDLER
375     return ( ! ([identifier isEqualToString:@"0-0"]) );
376 }
377
378 - (BOOL)radioIsPlaying
379 {
380     ITMTRemotePlayerPlaylistClass class = nil;
381     NS_DURING
382         class = [[self currentRemote] currentPlaylistClass];
383     NS_HANDLER
384         [self networkError:localException];
385     NS_ENDHANDLER
386     return (class  == ITMTRemotePlayerRadioPlaylist );
387 }
388
389 - (BOOL)songChanged
390 {
391     NSString *identifier = nil;
392     NS_DURING
393         identifier = [[self currentRemote] playerStateUniqueIdentifier];
394     NS_HANDLER
395         [self networkError:localException];
396     NS_ENDHANDLER
397     return ( ! [identifier isEqualToString:_latestSongIdentifier] );
398 }
399
400 - (NSString *)latestSongIdentifier
401 {
402     return _latestSongIdentifier;
403 }
404
405 - (void)setLatestSongIdentifier:(NSString *)newIdentifier
406 {
407     ITDebugLog(@"Setting latest song identifier:");
408     ITDebugLog(@"   - Identifier: %@", newIdentifier);
409     [_latestSongIdentifier autorelease];
410     _latestSongIdentifier = [newIdentifier retain];
411 }
412
413 - (void)timerUpdate
414 {
415         NSString *identifier = nil;
416         NS_DURING
417                 identifier = [[self currentRemote] playerStateUniqueIdentifier];
418         NS_HANDLER
419                 [self networkError:localException];
420         NS_ENDHANDLER
421         if (refreshTimer && identifier == nil) {
422                 if ([statusItem isEnabled]) {
423                         [statusItem setToolTip:@"iTunes not responding."];
424                 }
425                 [statusItem setEnabled:NO];
426                 return;
427         } else if (![statusItem isEnabled]) {
428                 [statusItem setEnabled:YES];
429                 [statusItem setToolTip:_toolTip];
430                 return;
431         }
432         
433         if ( [self songChanged] && (timerUpdating != YES) && (playerRunningState == ITMTRemotePlayerRunning) ) {
434         ITDebugLog(@"The song changed. '%@'", _latestSongIdentifier);
435         if ([df boolForKey:@"runScripts"]) {
436             NSArray *scripts = [[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
437             NSEnumerator *scriptsEnum = [scripts objectEnumerator];
438             NSString *nextScript;
439             ITDebugLog(@"Running AppleScripts for song change.");
440             while ( (nextScript = [scriptsEnum nextObject]) ) {
441                 NSDictionary *error;
442                 NSAppleScript *currentScript = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] stringByAppendingPathComponent:nextScript]] error:&error];
443                 ITDebugLog(@"Running script: %@", nextScript);
444                 if (!currentScript || ![currentScript executeAndReturnError:nil]) {
445                     ITDebugLog(@"Error running script %@.", nextScript);
446                 }
447                 [currentScript release];
448             }
449         }
450         
451         timerUpdating = YES;
452         [statusItem setEnabled:NO];
453                 
454         NS_DURING
455             latestPlaylistClass = [[self currentRemote] currentPlaylistClass];
456                         
457                         if ([menuController rebuildSubmenus]) {
458                                 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
459                                         [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
460                                 }
461                                 [self setLatestSongIdentifier:identifier];
462                                 //Create the tooltip for the status item
463                                 if ( [df boolForKey:@"showToolTip"] ) {
464                                         NSString *artist = [[self currentRemote] currentSongArtist];
465                                         NSString *title = [[self currentRemote] currentSongTitle];
466                                         ITDebugLog(@"Creating status item tooltip.");
467                                         if (artist) {
468                                                 _toolTip = [NSString stringWithFormat:@"%@ - %@", artist, title];
469                                         } else if (title) {
470                                                 _toolTip = title;
471                                         } else {
472                                                 _toolTip = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
473                                         }
474                                         [statusItem setToolTip:_toolTip];
475                                 } else {
476                                         [statusItem setToolTip:nil];
477                                 }
478                         }
479                         
480                         if ([df boolForKey:@"audioscrobblerEnabled"]) {
481                                 int length = [[self currentRemote] currentSongDuration];
482                                 if (length > 30) {
483                                         _audioscrobblerInterval = ((length / 2 < 240) ? length / 2 : 240);
484                                         [_audioscrobblerTimer invalidate];
485                                         [_audioscrobblerTimer release];
486                                         _audioscrobblerTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:_audioscrobblerInterval] interval:nil target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:NO];
487                                         [[NSRunLoop currentRunLoop] addTimer:_audioscrobblerTimer forMode:NSDefaultRunLoopMode];
488                                 }
489                         } else {
490                                 _audioscrobblerTimer = nil;
491                         }
492         NS_HANDLER
493             [self networkError:localException];
494         NS_ENDHANDLER
495         timerUpdating = NO;
496         [statusItem setEnabled:YES];
497     }
498         
499     if ([networkController isConnectedToServer]) {
500         [statusItem setMenu:([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) ? [menuController menu] : [menuController menuForNoPlayer]];
501     }
502 }
503
504 - (void)menuClicked
505 {
506     ITDebugLog(@"Menu clicked.");
507         
508         if (([[self currentRemote] playerStateUniqueIdentifier] == nil) && playerRunningState == ITMTRemotePlayerRunning) {
509                 if (refreshTimer) {
510                         if ([statusItem isEnabled]) {
511                                 [statusItem setToolTip:NSLocalizedString(@"iTunesNotResponding", @"iTunes is not responding.")];
512                         }
513                         [statusItem setEnabled:NO];
514                 } else {
515                         NSMenu *menu = [[NSMenu alloc] init];
516                         [menu addItemWithTitle:NSLocalizedString(@"iTunesNotResponding", @"iTunes is not responding.") action:nil keyEquivalent:@""];
517                         [statusItem setMenu:[menu autorelease]];
518                 }
519                 return;
520         } else if (![statusItem isEnabled]) {
521                 [statusItem setEnabled:YES];
522                 [statusItem setToolTip:_toolTip];
523                 return;
524         }
525         
526     if ([networkController isConnectedToServer]) {
527         //Used the cached version
528         return;
529     }
530     
531     NS_DURING
532         if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
533             [statusItem setMenu:[menuController menu]];
534         } else {
535             [statusItem setMenu:[menuController menuForNoPlayer]];
536         }
537     NS_HANDLER
538         [self networkError:localException];
539     NS_ENDHANDLER
540 }
541
542 - (void)trackChanged:(NSNotification *)note
543 {
544         //If we're running the timer, shut it off since we don't need it!
545         /*if (refreshTimer && [refreshTimer isValid]) {
546                 ITDebugLog(@"Invalidating refresh timer.");
547                 [refreshTimer invalidate];
548                 [refreshTimer release];
549                 refreshTimer = nil;
550         }*/
551         
552         if (![self songChanged]) {
553                 return;
554         }
555         NSString *identifier = [[self currentRemote] playerStateUniqueIdentifier];
556         if ( [df boolForKey:@"showSongInfoOnChange"] ) {
557                 [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
558         }
559         [_lastTrackInfo release];
560         _lastTrackInfo = [[note userInfo] retain];
561         
562         [self setLatestSongIdentifier:identifier];
563         ITDebugLog(@"The song changed. '%@'", _latestSongIdentifier);
564         if ([df boolForKey:@"runScripts"]) {
565                 NSArray *scripts = [[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
566                 NSEnumerator *scriptsEnum = [scripts objectEnumerator];
567                 NSString *nextScript;
568                 ITDebugLog(@"Running AppleScripts for song change.");
569                 while ( (nextScript = [scriptsEnum nextObject]) ) {
570                         NSDictionary *error;
571                         NSAppleScript *currentScript = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] stringByAppendingPathComponent:nextScript]] error:&error];
572                         ITDebugLog(@"Running script: %@", nextScript);
573                         if (!currentScript || ![currentScript executeAndReturnError:nil]) {
574                                 ITDebugLog(@"Error running script %@.", nextScript);
575                         }
576                         [currentScript release];
577                 }
578         }
579         
580         [statusItem setEnabled:NO];
581         
582         NS_DURING
583                 latestPlaylistClass = [[self currentRemote] currentPlaylistClass];
584                 
585                 if ([menuController rebuildSubmenus]) {
586                         /*if ( [df boolForKey:@"showSongInfoOnChange"] ) {
587                                 [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
588                         }*/
589                         [self setLatestSongIdentifier:identifier];
590                         //Create the tooltip for the status item
591                         if ( [df boolForKey:@"showToolTip"] ) {
592                                 ITDebugLog(@"Creating status item tooltip.");
593                                 NSString *artist = [_lastTrackInfo objectForKey:@"Artist"], *title = [_lastTrackInfo objectForKey:@"Name"];
594                                 if (artist) {
595                                         _toolTip = [NSString stringWithFormat:@"%@ - %@", artist, title];
596                                 } else if (title) {
597                                         _toolTip = title;
598                                 } else {
599                                         _toolTip = NSLocalizedString(@"noSongPlaying", @"No song is playing.");;
600                                 }
601                                 [statusItem setToolTip:_toolTip];
602                         } else {
603                                 [statusItem setToolTip:nil];
604                         }
605                 }
606                 
607                 if ([df boolForKey:@"audioscrobblerEnabled"]) {
608                         int length = [[self currentRemote] currentSongDuration];
609                         if (length > 30) {
610                                 _audioscrobblerInterval = ((length / 2 < 240) ? length / 2 : 240);
611                                 [_audioscrobblerTimer invalidate];
612                                 [_audioscrobblerTimer release];
613                                 _audioscrobblerTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:_audioscrobblerInterval] interval:1.0 target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:NO];
614                                 [[NSRunLoop currentRunLoop] addTimer:_audioscrobblerTimer forMode:NSDefaultRunLoopMode];
615                         }
616                 } else {
617                         _audioscrobblerTimer = nil;
618                 }
619         NS_HANDLER
620                 [self networkError:localException];
621         NS_ENDHANDLER
622         timerUpdating = NO;
623         [statusItem setEnabled:YES];
624         
625         if ([networkController isConnectedToServer]) {
626         [statusItem setMenu:([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) ? [menuController menu] : [menuController menuForNoPlayer]];
627     }
628 }
629
630 - (void)submitAudioscrobblerTrack:(NSTimer *)timer
631 {
632         ITDebugLog(@"Audioscrobbler: Attempting to submit current track");
633         [timer invalidate];
634         if ([df boolForKey:@"audioscrobblerEnabled"]) {
635                 NS_DURING
636                         int elapsed = [[self currentRemote] currentSongPlayed];
637                         if ((abs(elapsed - _audioscrobblerInterval) < 5) && ([[self currentRemote] playerPlayingState] == ITMTRemotePlayerPlaying)) {
638                                 NSString *title = [[self currentRemote] currentSongTitle], *artist = [[self currentRemote] currentSongArtist];
639                                 if (title && artist) {
640                                         ITDebugLog(@"Audioscrobbler: Submitting current track");
641                                         [[AudioscrobblerController sharedController] submitTrack:title
642                                                                                                                                         artist:artist
643                                                                                                                                         album:[[self currentRemote] currentSongAlbum]
644                                                                                                                                         length:[[self currentRemote] currentSongDuration]];
645                                 }
646                         } else if (_audioscrobblerInterval - elapsed > 0) {
647                                 ITDebugLog(@"Audioscrobbler: Creating a new timer that will run in %i seconds", _audioscrobblerInterval - elapsed);
648                                 _audioscrobblerInterval -= elapsed;
649                                 [_audioscrobblerTimer release];
650                                 _audioscrobblerTimer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:_audioscrobblerInterval] interval:nil target:self selector:@selector(submitAudioscrobblerTrack:) userInfo:nil repeats:NO];
651                                 [[NSRunLoop currentRunLoop] addTimer:_audioscrobblerTimer forMode:NSDefaultRunLoopMode];
652                         }
653                 NS_HANDLER
654                         [self networkError:localException];
655                 NS_ENDHANDLER
656         }
657 }
658
659 //
660 //
661 // Menu Selectors
662 //
663 //
664
665 - (void)playPause
666 {
667     NS_DURING
668         ITMTRemotePlayerPlayingState state = [[self currentRemote] playerPlayingState];
669         ITDebugLog(@"Play/Pause toggled");
670         if (state == ITMTRemotePlayerPlaying) {
671             [[self currentRemote] pause];
672         } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
673             [[self currentRemote] pause];
674             [[self currentRemote] play];
675         } else {
676             [[self currentRemote] play];
677         }
678     NS_HANDLER
679         [self networkError:localException];
680     NS_ENDHANDLER
681     
682         if (refreshTimer) {
683                 [self timerUpdate];
684         }
685 }
686
687 - (void)nextSong
688 {
689     ITDebugLog(@"Going to next song.");
690     NS_DURING
691         [[self currentRemote] goToNextSong];
692     NS_HANDLER
693         [self networkError:localException];
694     NS_ENDHANDLER
695     if (refreshTimer) {
696                 [self timerUpdate];
697         }
698 }
699
700 - (void)prevSong
701 {
702     ITDebugLog(@"Going to previous song.");
703     NS_DURING
704         [[self currentRemote] goToPreviousSong];
705     NS_HANDLER
706         [self networkError:localException];
707     NS_ENDHANDLER
708     if (refreshTimer) {
709                 [self timerUpdate];
710         }
711 }
712
713 - (void)fastForward
714 {
715     ITDebugLog(@"Fast forwarding.");
716     NS_DURING
717         [[self currentRemote] forward];
718     NS_HANDLER
719         [self networkError:localException];
720     NS_ENDHANDLER
721     if (refreshTimer) {
722                 [self timerUpdate];
723         }
724 }
725
726 - (void)rewind
727 {
728     ITDebugLog(@"Rewinding.");
729     NS_DURING
730         [[self currentRemote] rewind];
731     NS_HANDLER
732         [self networkError:localException];
733     NS_ENDHANDLER
734     if (refreshTimer) {
735                 [self timerUpdate];
736         }
737 }
738
739 - (void)selectPlaylistAtIndex:(int)index
740 {
741     ITDebugLog(@"Selecting playlist %i", index);
742     NS_DURING
743         [[self currentRemote] switchToPlaylistAtIndex:(index % 1000) ofSourceAtIndex:(index / 1000)];
744         //[[self currentRemote] switchToPlaylistAtIndex:index];
745     NS_HANDLER
746         [self networkError:localException];
747     NS_ENDHANDLER
748     if (refreshTimer) {
749                 [self timerUpdate];
750         }
751 }
752
753 - (void)selectSongAtIndex:(int)index
754 {
755     ITDebugLog(@"Selecting song %i", index);
756     NS_DURING
757         [[self currentRemote] switchToSongAtIndex:index];
758     NS_HANDLER
759         [self networkError:localException];
760     NS_ENDHANDLER
761     if (refreshTimer) {
762                 [self timerUpdate];
763         }
764 }
765
766 - (void)selectSongRating:(int)rating
767 {
768     ITDebugLog(@"Selecting song rating %i", rating);
769     NS_DURING
770         [[self currentRemote] setCurrentSongRating:(float)rating / 100.0];
771     NS_HANDLER
772         [self networkError:localException];
773     NS_ENDHANDLER
774     if (refreshTimer) {
775                 [self timerUpdate];
776         }
777 }
778
779 - (void)selectEQPresetAtIndex:(int)index
780 {
781     ITDebugLog(@"Selecting EQ preset %i", index);
782     NS_DURING
783         if (index == -1) {
784             [[self currentRemote] setEqualizerEnabled:![[self currentRemote] equalizerEnabled]];
785         } else {
786             [[self currentRemote] switchToEQAtIndex:index];
787         }
788     NS_HANDLER
789         [self networkError:localException];
790     NS_ENDHANDLER
791     if (refreshTimer) {
792                 [self timerUpdate];
793         }
794 }
795
796 - (void)makePlaylistWithTerm:(NSString *)term ofType:(int)type
797 {
798     ITDebugLog(@"Making playlist with term %@, type %i", term, type);
799     NS_DURING
800         [[self currentRemote] makePlaylistWithTerm:term ofType:type];
801     NS_HANDLER
802         [self networkError:localException];
803     NS_ENDHANDLER
804     ITDebugLog(@"Done making playlist");
805 }
806
807 - (void)showPlayer
808 {
809     ITDebugLog(@"Beginning show player.");
810     //if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
811         ITDebugLog(@"Showing player interface.");
812         NS_DURING
813             [[self currentRemote] showPrimaryInterface];
814         NS_HANDLER
815             [self networkError:localException];
816         NS_ENDHANDLER
817     /*} else {
818         ITDebugLog(@"Launching player.");
819         NS_DURING
820             NSString *path;
821             if ( (path = [df stringForKey:@"CustomPlayerPath"]) ) {
822             } else {
823                 pathITDebugLog(@"Showing player interface."); = [[self currentRemote] playerFullName];
824             }
825             if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
826                 ITDebugLog(@"Error Launching Player");
827             }
828         NS_HANDLER
829             [self networkError:localException];
830         NS_ENDHANDLER
831     }*/
832     ITDebugLog(@"Finished show player.");
833 }
834
835 - (void)showPreferences
836 {
837     ITDebugLog(@"Show preferences.");
838     [[PreferencesController sharedPrefs] showPrefsWindow:self];
839 }
840
841 - (void)showPreferencesAndClose
842 {
843     ITDebugLog(@"Show preferences.");
844     [[PreferencesController sharedPrefs] showPrefsWindow:self];
845     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
846     [[StatusWindow sharedWindow] vanish:self];
847     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
848 }
849
850 - (void)showTestWindow
851 {
852     [self showCurrentTrackInfo];
853 }
854
855 - (void)quitMenuTunes
856 {
857     ITDebugLog(@"Quitting MenuTunes.");
858     [NSApp terminate:self];
859 }
860
861 //
862 //
863
864 - (MenuController *)menuController
865 {
866     return menuController;
867 }
868
869 - (void)closePreferences
870 {
871     ITDebugLog(@"Preferences closed.");
872     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
873         [self setupHotKeys];
874     }
875 }
876
877 - (ITMTRemote *)currentRemote
878 {
879     if ([networkController isConnectedToServer] && ![[networkController networkObject] isValid]) {
880         [self networkError:nil];
881         return nil;
882     }
883     return currentRemote;
884 }
885
886 //
887 //
888 // Hot key setup
889 //
890 //
891
892 - (void)clearHotKeys
893 {
894     NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
895     ITHotKey *nextHotKey;
896     ITDebugLog(@"Clearing hot keys.");
897     while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
898         [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
899     }
900     ITDebugLog(@"Done clearing hot keys.");
901 }
902
903 - (void)setupHotKeys
904 {
905     ITHotKey *hotKey;
906     ITDebugLog(@"Setting up hot keys.");
907     
908     if (playerRunningState == ITMTRemotePlayerNotRunning && ![[NetworkController sharedController] isConnectedToServer]) {
909         return;
910     }
911     
912     if ([df objectForKey:@"PlayPause"] != nil) {
913         ITDebugLog(@"Setting up play pause hot key.");
914         hotKey = [[ITHotKey alloc] init];
915         [hotKey setName:@"PlayPause"];
916         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
917         [hotKey setTarget:self];
918         [hotKey setAction:@selector(playPause)];
919         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
920     }
921     
922     if ([df objectForKey:@"NextTrack"] != nil) {
923         ITDebugLog(@"Setting up next track hot key.");
924         hotKey = [[ITHotKey alloc] init];
925         [hotKey setName:@"NextTrack"];
926         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
927         [hotKey setTarget:self];
928         [hotKey setAction:@selector(nextSong)];
929         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
930     }
931     
932     if ([df objectForKey:@"PrevTrack"] != nil) {
933         ITDebugLog(@"Setting up previous track hot key.");
934         hotKey = [[ITHotKey alloc] init];
935         [hotKey setName:@"PrevTrack"];
936         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
937         [hotKey setTarget:self];
938         [hotKey setAction:@selector(prevSong)];
939         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
940     }
941     
942     if ([df objectForKey:@"FastForward"] != nil) {
943         ITDebugLog(@"Setting up fast forward hot key.");
944         hotKey = [[ITHotKey alloc] init];
945         [hotKey setName:@"FastForward"];
946         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"FastForward"]]];
947         [hotKey setTarget:self];
948         [hotKey setAction:@selector(fastForward)];
949         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
950     }
951     
952     if ([df objectForKey:@"Rewind"] != nil) {
953         ITDebugLog(@"Setting up rewind hot key.");
954         hotKey = [[ITHotKey alloc] init];
955         [hotKey setName:@"Rewind"];
956         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"Rewind"]]];
957         [hotKey setTarget:self];
958         [hotKey setAction:@selector(rewind)];
959         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
960     }
961     
962     if ([df objectForKey:@"ShowPlayer"] != nil) {
963         ITDebugLog(@"Setting up show player hot key.");
964         hotKey = [[ITHotKey alloc] init];
965         [hotKey setName:@"ShowPlayer"];
966         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
967         [hotKey setTarget:self];
968         [hotKey setAction:@selector(showPlayer)];
969         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
970     }
971     
972     if ([df objectForKey:@"TrackInfo"] != nil) {
973         ITDebugLog(@"Setting up track info hot key.");
974         hotKey = [[ITHotKey alloc] init];
975         [hotKey setName:@"TrackInfo"];
976         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
977         [hotKey setTarget:self];
978         [hotKey setAction:@selector(showCurrentTrackInfoHotKey)];
979         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
980     }
981     
982     if ([df objectForKey:@"UpcomingSongs"] != nil) {
983         ITDebugLog(@"Setting up upcoming songs hot key.");
984         hotKey = [[ITHotKey alloc] init];
985         [hotKey setName:@"UpcomingSongs"];
986         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
987         [hotKey setTarget:self];
988         [hotKey setAction:@selector(showUpcomingSongs)];
989         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
990     }
991     
992     if ([df objectForKey:@"ToggleLoop"] != nil) {
993         ITDebugLog(@"Setting up toggle loop hot key.");
994         hotKey = [[ITHotKey alloc] init];
995         [hotKey setName:@"ToggleLoop"];
996         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
997         [hotKey setTarget:self];
998         [hotKey setAction:@selector(toggleLoop)];
999         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1000     }
1001     
1002     if ([df objectForKey:@"ToggleShuffle"] != nil) {
1003         ITDebugLog(@"Setting up toggle shuffle hot key.");
1004         hotKey = [[ITHotKey alloc] init];
1005         [hotKey setName:@"ToggleShuffle"];
1006         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
1007         [hotKey setTarget:self];
1008         [hotKey setAction:@selector(toggleShuffle)];
1009         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1010     }
1011     
1012     if ([df objectForKey:@"IncrementVolume"] != nil) {
1013         ITDebugLog(@"Setting up increment volume hot key.");
1014         hotKey = [[ITHotKey alloc] init];
1015         [hotKey setName:@"IncrementVolume"];
1016         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
1017         [hotKey setTarget:self];
1018         [hotKey setAction:@selector(incrementVolume)];
1019         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1020     }
1021     
1022     if ([df objectForKey:@"DecrementVolume"] != nil) {
1023         ITDebugLog(@"Setting up decrement volume hot key.");
1024         hotKey = [[ITHotKey alloc] init];
1025         [hotKey setName:@"DecrementVolume"];
1026         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
1027         [hotKey setTarget:self];
1028         [hotKey setAction:@selector(decrementVolume)];
1029         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1030     }
1031     
1032     if ([df objectForKey:@"IncrementRating"] != nil) {
1033         ITDebugLog(@"Setting up increment rating hot key.");
1034         hotKey = [[ITHotKey alloc] init];
1035         [hotKey setName:@"IncrementRating"];
1036         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
1037         [hotKey setTarget:self];
1038         [hotKey setAction:@selector(incrementRating)];
1039         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1040     }
1041     
1042     if ([df objectForKey:@"DecrementRating"] != nil) {
1043         ITDebugLog(@"Setting up decrement rating hot key.");
1044         hotKey = [[ITHotKey alloc] init];
1045         [hotKey setName:@"DecrementRating"];
1046         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
1047         [hotKey setTarget:self];
1048         [hotKey setAction:@selector(decrementRating)];
1049         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1050     }
1051     
1052         if ([df objectForKey:@"ToggleShufflability"] != nil) {
1053         ITDebugLog(@"Setting up toggle song shufflability hot key.");
1054         hotKey = [[ITHotKey alloc] init];
1055         [hotKey setName:@"ToggleShufflability"];
1056         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShufflability"]]];
1057         [hotKey setTarget:self];
1058         [hotKey setAction:@selector(toggleSongShufflable)];
1059         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1060     }
1061         
1062     if ([df objectForKey:@"PopupMenu"] != nil) {
1063         ITDebugLog(@"Setting up popup menu hot key.");
1064         hotKey = [[ITHotKey alloc] init];
1065         [hotKey setName:@"PopupMenu"];
1066         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PopupMenu"]]];
1067         [hotKey setTarget:self];
1068         [hotKey setAction:@selector(popupMenu)];
1069         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1070     }
1071     
1072     int i;
1073     for (i = 0; i <= 5; i++) {
1074         NSString *curName = [NSString stringWithFormat:@"SetRating%i", i];
1075         if ([df objectForKey:curName] != nil) {
1076             ITDebugLog(@"Setting up set rating %i hot key.", i);
1077             hotKey = [[ITHotKey alloc] init];
1078             [hotKey setName:curName];
1079             [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:curName]]];
1080             [hotKey setTarget:self];
1081             [hotKey setAction:@selector(setRating:)];
1082             [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1083         }
1084     }
1085     ITDebugLog(@"Finished setting up hot keys.");
1086 }
1087
1088 - (void)showCurrentTrackInfoHotKey
1089 {
1090         //If we're already visible and the setting says so, vanish instead of displaying again.
1091         if ([df boolForKey:@"ToggleTrackInfoWithHotKey"] && [statusWindowController currentStatusWindowType] == StatusWindowTrackInfoType && [[StatusWindow sharedWindow] visibilityState] == ITWindowVisibleState) {
1092                 ITDebugLog(@"Track window is already visible, hiding track window.");
1093                 [self invalidateStatusWindowUpdateTimer];
1094                 [[StatusWindow sharedWindow] vanish:nil];
1095                 return;
1096         }
1097         [self showCurrentTrackInfo];
1098 }
1099
1100 - (void)showCurrentTrackInfo
1101 {
1102     ITMTRemotePlayerSource  source      = 0;
1103     NSString               *title       = nil;
1104     NSString               *album       = nil;
1105     NSString               *artist      = nil;
1106     NSString               *composer    = nil;
1107     NSString               *time        = nil;
1108     NSString               *track       = nil;
1109     NSImage                *art         = nil;
1110     int                     rating      = -1;
1111     int                     playCount   = -1;
1112         
1113     ITDebugLog(@"Showing track info status window.");
1114     
1115     NS_DURING
1116         source      = [[self currentRemote] currentSource];
1117         title       = [[self currentRemote] currentSongTitle];
1118     NS_HANDLER
1119         [self networkError:localException];
1120     NS_ENDHANDLER
1121     
1122     if ( title ) {
1123         if ( [df boolForKey:@"showAlbumArtwork"] ) {
1124                         NSSize oldSize, newSize;
1125                         NS_DURING
1126                                 art = [[self currentRemote] currentSongAlbumArt];
1127                                 oldSize = [art size];
1128                                 if (oldSize.width > oldSize.height) {
1129                                         newSize = NSMakeSize(110,oldSize.height * (110.0f / oldSize.width));
1130                                 }
1131                                 else newSize = NSMakeSize(oldSize.width * (110.0f / oldSize.height),110);
1132                                 art = [[[[NSImage alloc] initWithData:[art TIFFRepresentation]] autorelease] imageScaledSmoothlyToSize:newSize];
1133                         NS_HANDLER
1134                                 [self networkError:localException];
1135                         NS_ENDHANDLER
1136         }
1137         
1138         if ( [df boolForKey:@"showAlbum"] ) {
1139             NS_DURING
1140                 album = [[self currentRemote] currentSongAlbum];
1141             NS_HANDLER
1142                 [self networkError:localException];
1143             NS_ENDHANDLER
1144         }
1145
1146         if ( [df boolForKey:@"showArtist"] ) {
1147             NS_DURING
1148                 artist = [[self currentRemote] currentSongArtist];
1149             NS_HANDLER
1150                 [self networkError:localException];
1151             NS_ENDHANDLER
1152         }
1153
1154         if ( [df boolForKey:@"showComposer"] ) {
1155             NS_DURING
1156                 composer = [[self currentRemote] currentSongComposer];
1157             NS_HANDLER
1158                 [self networkError:localException];
1159             NS_ENDHANDLER
1160         }
1161
1162         if ( [df boolForKey:@"showTime"] ) {
1163             NS_DURING
1164                 time = [NSString stringWithFormat:@"%@: %@ / %@",
1165                 NSLocalizedString(@"time", @"Time"),
1166                 [[self currentRemote] currentSongElapsed],
1167                 [[self currentRemote] currentSongLength]];
1168             NS_HANDLER
1169                 [self networkError:localException];
1170             NS_ENDHANDLER
1171                         _timeUpdateCount = 0;
1172                         [self invalidateStatusWindowUpdateTimer];
1173                         _statusWindowUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTime:) userInfo:nil repeats:YES];
1174         }
1175
1176         if ( [df boolForKey:@"showTrackNumber"] ) {
1177             int trackNo    = 0;
1178             int trackCount = 0;
1179             
1180             NS_DURING
1181                 trackNo    = [[self currentRemote] currentSongTrack];
1182                 trackCount = [[self currentRemote] currentAlbumTrackCount];
1183             NS_HANDLER
1184                 [self networkError:localException];
1185             NS_ENDHANDLER
1186             
1187             if ( (trackNo > 0) || (trackCount > 0) ) {
1188                 track = [NSString stringWithFormat:@"%@: %i %@ %i",
1189                     @"Track", trackNo, @"of", trackCount];
1190             }
1191         }
1192
1193         if ( [df boolForKey:@"showTrackRating"] ) {
1194             float currentRating = 0;
1195             
1196             NS_DURING
1197                 currentRating = [[self currentRemote] currentSongRating];
1198             NS_HANDLER
1199                 [self networkError:localException];
1200             NS_ENDHANDLER
1201             
1202             if (currentRating >= 0.0) {
1203                 rating = ( currentRating * 5 );
1204             }
1205         }
1206         
1207         if ( [df boolForKey:@"showPlayCount"] && ![self radioIsPlaying] && [[self currentRemote] currentSource] == ITMTRemoteLibrarySource ) {
1208             NS_DURING
1209                 playCount = [[self currentRemote] currentSongPlayCount];
1210             NS_HANDLER
1211                 [self networkError:localException];
1212             NS_ENDHANDLER
1213         }
1214     } else {
1215         title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
1216     }
1217     ITDebugLog(@"Showing current track info status window.");
1218     [statusWindowController showSongInfoWindowWithSource:source
1219                                                    title:title
1220                                                    album:album
1221                                                   artist:artist
1222                                                 composer:composer
1223                                                     time:time
1224                                                    track:track
1225                                                   rating:rating
1226                                                playCount:playCount
1227                                                    image:art];
1228 }
1229
1230 - (void)updateTime:(NSTimer *)timer
1231 {
1232         StatusWindow *sw = (StatusWindow *)[StatusWindow sharedWindow];
1233         _timeUpdateCount++;
1234         if ([sw visibilityState] != ITWindowHiddenState) {
1235                 NSString *time = nil, *length;
1236                 NS_DURING
1237                         length = [[self currentRemote] currentSongLength];
1238                         if (length) {
1239                                 time = [NSString stringWithFormat:@"%@: %@ / %@",
1240                                                         NSLocalizedString(@"time", @"Time"),
1241                                                         [[self currentRemote] currentSongElapsed],
1242                                                         length];
1243                                 [[StatusWindowController sharedController] updateTime:time];
1244                         }
1245                 NS_HANDLER
1246                         [self networkError:localException];
1247                 NS_ENDHANDLER
1248         } else {
1249                 [self invalidateStatusWindowUpdateTimer];
1250         }
1251 }
1252
1253 - (void)invalidateStatusWindowUpdateTimer
1254 {
1255         if (_statusWindowUpdateTimer) {
1256                 [_statusWindowUpdateTimer invalidate];
1257                 _statusWindowUpdateTimer = nil;
1258         }
1259 }
1260
1261 - (void)showUpcomingSongs
1262 {
1263     int numSongs = 0;
1264     NS_DURING
1265         numSongs = [[self currentRemote] numberOfSongsInPlaylistAtIndex:[[self currentRemote] currentPlaylistIndex]];
1266     NS_HANDLER
1267         [self networkError:localException];
1268     NS_ENDHANDLER
1269     
1270         [self invalidateStatusWindowUpdateTimer];
1271         
1272     ITDebugLog(@"Showing upcoming songs status window.");
1273     NS_DURING
1274         if (numSongs > 0) {
1275             int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
1276             NSMutableArray *songList = [NSMutableArray arrayWithCapacity:numSongsInAdvance];
1277             int curTrack = [[self currentRemote] currentSongIndex];
1278             int i;
1279     
1280             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance && i <= numSongs; i++) {
1281                 if ([[self currentRemote] songEnabledAtIndex:i]) {
1282                     [songList addObject:[[self currentRemote] songTitleAtIndex:i]];
1283                 } else {
1284                                         numSongsInAdvance++;
1285                                 }
1286             }
1287             
1288             if ([songList count] == 0) {
1289                 [songList addObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")];
1290             }
1291             
1292             [statusWindowController showUpcomingSongsWindowWithTitles:songList];
1293         } else {
1294             [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
1295         }
1296     NS_HANDLER
1297         [self networkError:localException];
1298     NS_ENDHANDLER
1299 }
1300
1301 - (void)popupMenu
1302 {
1303     if (!_popped) {
1304         _popped = YES;
1305         [self menuClicked];
1306         NSMenu *menu = [statusItem menu];
1307         [(NSCarbonMenuImpl *)[menu _menuImpl] popUpMenu:menu atLocation:[NSEvent mouseLocation] width:1 forView:nil withSelectedItem:-30 withFont:[NSFont menuFontOfSize:32]];
1308         _popped = NO;
1309     }
1310 }
1311
1312 - (void)incrementVolume
1313 {
1314     NS_DURING
1315         float volume  = [[self currentRemote] volume];
1316         float dispVol = volume;
1317         ITDebugLog(@"Incrementing volume.");
1318         volume  += 0.110;
1319         dispVol += 0.100;
1320         
1321         if (volume > 1.0) {
1322             volume  = 1.0;
1323             dispVol = 1.0;
1324         }
1325     
1326         ITDebugLog(@"Setting volume to %f", volume);
1327         [[self currentRemote] setVolume:volume];
1328     
1329         // Show volume status window
1330                 [self invalidateStatusWindowUpdateTimer];
1331         [statusWindowController showVolumeWindowWithLevel:dispVol];
1332     NS_HANDLER
1333         [self networkError:localException];
1334     NS_ENDHANDLER
1335 }
1336
1337 - (void)decrementVolume
1338 {
1339     NS_DURING
1340         float volume  = [[self currentRemote] volume];
1341         float dispVol = volume;
1342         ITDebugLog(@"Decrementing volume.");
1343         volume  -= 0.090;
1344         dispVol -= 0.100;
1345     
1346         if (volume < 0.0) {
1347             volume  = 0.0;
1348             dispVol = 0.0;
1349         }
1350         
1351         ITDebugLog(@"Setting volume to %f", volume);
1352         [[self currentRemote] setVolume:volume];
1353         
1354         //Show volume status window
1355                 [self invalidateStatusWindowUpdateTimer];
1356         [statusWindowController showVolumeWindowWithLevel:dispVol];
1357     NS_HANDLER
1358         [self networkError:localException];
1359     NS_ENDHANDLER
1360 }
1361
1362 - (void)incrementRating
1363 {
1364     NS_DURING
1365         float rating = [[self currentRemote] currentSongRating];
1366         ITDebugLog(@"Incrementing rating.");
1367         
1368         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1369             ITDebugLog(@"No song playing, rating change aborted.");
1370             return;
1371         }
1372         
1373         rating += 0.2;
1374         if (rating > 1.0) {
1375             rating = 1.0;
1376         }
1377         ITDebugLog(@"Setting rating to %f", rating);
1378         [[self currentRemote] setCurrentSongRating:rating];
1379         
1380         //Show rating status window
1381                 [self invalidateStatusWindowUpdateTimer];
1382         [statusWindowController showRatingWindowWithRating:rating];
1383     NS_HANDLER
1384         [self networkError:localException];
1385     NS_ENDHANDLER
1386 }
1387
1388 - (void)decrementRating
1389 {
1390     NS_DURING
1391         float rating = [[self currentRemote] currentSongRating];
1392         ITDebugLog(@"Decrementing rating.");
1393         
1394         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1395             ITDebugLog(@"No song playing, rating change aborted.");
1396             return;
1397         }
1398         
1399         rating -= 0.2;
1400         if (rating < 0.0) {
1401             rating = 0.0;
1402         }
1403         ITDebugLog(@"Setting rating to %f", rating);
1404         [[self currentRemote] setCurrentSongRating:rating];
1405         
1406         //Show rating status window
1407                 [self invalidateStatusWindowUpdateTimer];
1408         [statusWindowController showRatingWindowWithRating:rating];
1409     NS_HANDLER
1410         [self networkError:localException];
1411     NS_ENDHANDLER
1412 }
1413
1414 - (void)setRating:(ITHotKey *)sender
1415 {
1416         if ([self songIsPlaying]) {
1417                 int stars = [[sender name] characterAtIndex:9] - 48;
1418                 [self selectSongRating:stars * 20];
1419                 [statusWindowController showRatingWindowWithRating:(float)stars / 5.0];
1420         }
1421 }
1422
1423 - (void)toggleLoop
1424 {
1425     NS_DURING
1426         ITMTRemotePlayerRepeatMode repeatMode = [[self currentRemote] repeatMode];
1427         ITDebugLog(@"Toggling repeat mode.");
1428         switch (repeatMode) {
1429             case ITMTRemotePlayerRepeatOff:
1430                 repeatMode = ITMTRemotePlayerRepeatAll;
1431             break;
1432             case ITMTRemotePlayerRepeatAll:
1433                 repeatMode = ITMTRemotePlayerRepeatOne;
1434             break;
1435             case ITMTRemotePlayerRepeatOne:
1436                 repeatMode = ITMTRemotePlayerRepeatOff;
1437             break;
1438         }
1439         ITDebugLog(@"Setting repeat mode to %i", repeatMode);
1440         [[self currentRemote] setRepeatMode:repeatMode];
1441         
1442         //Show loop status window
1443                 [self invalidateStatusWindowUpdateTimer];
1444         [statusWindowController showRepeatWindowWithMode:repeatMode];
1445     NS_HANDLER
1446         [self networkError:localException];
1447     NS_ENDHANDLER
1448 }
1449
1450 - (void)toggleShuffle
1451 {
1452     NS_DURING
1453         BOOL newShuffleEnabled = ( ! [[self currentRemote] shuffleEnabled] );
1454         ITDebugLog(@"Toggling shuffle mode.");
1455         [[self currentRemote] setShuffleEnabled:newShuffleEnabled];
1456         //Show shuffle status window
1457         ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
1458                 [self invalidateStatusWindowUpdateTimer];
1459         [statusWindowController showShuffleWindow:newShuffleEnabled];
1460     NS_HANDLER
1461         [self networkError:localException];
1462     NS_ENDHANDLER
1463 }
1464
1465 - (void)toggleSongShufflable
1466 {
1467         if ([self songIsPlaying]) {
1468                 NS_DURING
1469                         BOOL flag = ![[self currentRemote] currentSongShufflable];
1470                         ITDebugLog(@"Toggling shufflability.");
1471                         [[self currentRemote] setCurrentSongShufflable:flag];
1472                         //Show song shufflability status window
1473                         [self invalidateStatusWindowUpdateTimer];
1474                         [statusWindowController showSongShufflabilityWindow:flag];
1475                 NS_HANDLER
1476                         [self networkError:localException];
1477                 NS_ENDHANDLER
1478         }
1479 }
1480
1481 - (void)registerNowOK
1482 {
1483     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1484     [[StatusWindow sharedWindow] vanish:self];
1485     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1486
1487     [self blingNow];
1488 }
1489
1490 - (void)registerNowCancel
1491 {
1492     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1493     [[StatusWindow sharedWindow] vanish:self];
1494     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1495
1496     [NSApp terminate:self];
1497 }
1498
1499 /*************************************************************************/
1500 #pragma mark -
1501 #pragma mark NETWORK HANDLERS
1502 /*************************************************************************/
1503
1504 - (void)setServerStatus:(BOOL)newStatus
1505 {
1506     if (newStatus) {
1507         //Turn on
1508         [networkController setServerStatus:YES];
1509     } else {
1510         //Tear down
1511         [networkController setServerStatus:NO];
1512     }
1513 }
1514
1515 - (int)connectToServer
1516 {
1517     int result;
1518     ITDebugLog(@"Attempting to connect to shared remote.");
1519     result = [networkController connectToHost:[df stringForKey:@"sharedPlayerHost"]];
1520     //Connect
1521     if (result == 1) {
1522         [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1523         currentRemote = [[[networkController networkObject] remote] retain];
1524         
1525         [self setupHotKeys];
1526         //playerRunningState = ITMTRemotePlayerRunning;
1527         playerRunningState = [[self currentRemote] playerRunningState];
1528                 if (_needsPolling) {
1529                         if (refreshTimer) {
1530                                 [refreshTimer invalidate];
1531                         }
1532                 }
1533                 
1534                 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1535                                                                  target:self
1536                                                                  selector:@selector(timerUpdate)
1537                                                                  userInfo:nil
1538                                                                  repeats:YES] retain];
1539                 
1540         [self timerUpdate];
1541         ITDebugLog(@"Connection successful.");
1542         return 1;
1543     } else if (result == 0) {
1544         ITDebugLog(@"Connection failed.");
1545         currentRemote = [remoteArray objectAtIndex:0];
1546         return 0;
1547     } else {
1548         //Do something about the password being invalid
1549         ITDebugLog(@"Connection failed.");
1550         currentRemote = [remoteArray objectAtIndex:0];
1551         return -1;
1552     }
1553 }
1554
1555 - (BOOL)disconnectFromServer
1556 {
1557     ITDebugLog(@"Disconnecting from shared remote.");
1558     //Disconnect
1559     [currentRemote release];
1560     currentRemote = [remoteArray objectAtIndex:0];
1561     [networkController disconnect];
1562     
1563     if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
1564                 refreshTimer = nil;
1565         [self applicationLaunched:nil];
1566     } else {
1567         [self applicationTerminated:nil];
1568     }
1569         
1570     if (refreshTimer) {
1571                 [self timerUpdate];
1572         };
1573     return YES;
1574 }
1575
1576 - (void)checkForRemoteServer
1577 {
1578     [self checkForRemoteServerAndConnectImmediately:NO];
1579 }
1580
1581 - (void)checkForRemoteServerAndConnectImmediately:(BOOL)connectImmediately
1582 {
1583     ITDebugLog(@"Checking for remote server.");
1584     if (!_checkingForServer) {
1585         if (!_serverCheckLock) {
1586             _serverCheckLock = [[NSLock alloc] init];
1587         }
1588         [_serverCheckLock lock];
1589         _checkingForServer = YES;
1590         [_serverCheckLock unlock];
1591         [NSThread detachNewThreadSelector:@selector(runRemoteServerCheck:) toTarget:self withObject:[NSNumber numberWithBool:connectImmediately]];
1592     }
1593 }
1594
1595 - (void)runRemoteServerCheck:(id)sender
1596 {
1597     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1598     ITDebugLog(@"Remote server check running.");
1599     if ([networkController checkForServerAtHost:[df stringForKey:@"sharedPlayerHost"]]) {
1600         ITDebugLog(@"Remote server found.");
1601         if ([sender boolValue]) {
1602             [self performSelectorOnMainThread:@selector(connectToServer) withObject:nil waitUntilDone:NO];
1603         } else {
1604             [self performSelectorOnMainThread:@selector(remoteServerFound:) withObject:nil waitUntilDone:NO];
1605         }
1606     } else {
1607         ITDebugLog(@"Remote server not found.");
1608         [self performSelectorOnMainThread:@selector(remoteServerNotFound:) withObject:nil waitUntilDone:NO];
1609     }
1610     [_serverCheckLock lock];
1611     _checkingForServer = NO;
1612     [_serverCheckLock unlock];
1613     [pool release];
1614 }
1615
1616 - (void)remoteServerFound:(id)sender
1617 {
1618     if (![networkController isServerOn] && ![networkController isConnectedToServer]) {
1619                 [self invalidateStatusWindowUpdateTimer];
1620         [[StatusWindowController sharedController] showReconnectQueryWindow];
1621     }
1622 }
1623
1624 - (void)remoteServerNotFound:(id)sender
1625 {
1626     if (![[NetworkController sharedController] isConnectedToServer]) {
1627         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1628     }
1629 }
1630
1631 - (void)networkError:(NSException *)exception
1632 {
1633     ITDebugLog(@"Remote exception thrown: %@: %@", [exception name], [exception reason]);
1634     if ( ((exception == nil) || [[exception name] isEqualToString:NSPortTimeoutException]) && [networkController isConnectedToServer]) {
1635         //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);
1636                 [self invalidateStatusWindowUpdateTimer];
1637         [[StatusWindowController sharedController] showNetworkErrorQueryWindow];
1638         if ([self disconnectFromServer]) {
1639             [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1640             [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1641         } else {
1642             ITDebugLog(@"CRITICAL ERROR, DISCONNECTING!");
1643         }
1644     }
1645 }
1646
1647 - (void)reconnect
1648 {
1649     /*if ([self connectToServer] == 0) {
1650         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1651     }*/
1652     [self checkForRemoteServerAndConnectImmediately:YES];
1653     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1654     [[StatusWindow sharedWindow] vanish:self];
1655     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1656 }
1657
1658 - (void)cancelReconnect
1659 {
1660     [(StatusWindow *)[StatusWindow sharedWindow] setLocked:NO];
1661     [[StatusWindow sharedWindow] vanish:self];
1662     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1663 }
1664
1665 /*************************************************************************/
1666 #pragma mark -
1667 #pragma mark WORKSPACE NOTIFICATION HANDLERS
1668 /*************************************************************************/
1669
1670 - (void)applicationLaunched:(NSNotification *)note
1671 {
1672     NS_DURING
1673         if (!note || ([[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer])) {
1674             ITDebugLog(@"Remote application launched.");
1675             playerRunningState = ITMTRemotePlayerRunning;
1676             [[self currentRemote] begin];
1677             [self setLatestSongIdentifier:@""];
1678             [self timerUpdate];
1679                         if (_needsPolling) {
1680                                 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1681                                                                         target:self
1682                                                                         selector:@selector(timerUpdate)
1683                                                                         userInfo:nil
1684                                                                         repeats:YES] retain];
1685                         }
1686             //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
1687                         if (![df boolForKey:@"UsePollingOnly"]) {
1688                                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(trackChanged:) name:@"ITMTTrackChanged" object:nil];
1689                         }
1690             [self setupHotKeys];
1691         }
1692     NS_HANDLER
1693         [self networkError:localException];
1694     NS_ENDHANDLER
1695 }
1696
1697  - (void)applicationTerminated:(NSNotification *)note
1698  {
1699     NS_DURING
1700         if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer]) {
1701             ITDebugLog(@"Remote application terminated.");
1702             playerRunningState = ITMTRemotePlayerNotRunning;
1703             [[self currentRemote] halt];
1704             [refreshTimer invalidate];
1705             [refreshTimer release];
1706             refreshTimer = nil;
1707                         [[NSNotificationCenter defaultCenter] removeObserver:self name:@"ITMTTrackChanged" object:nil];
1708                         [statusItem setEnabled:YES];
1709                         [statusItem setToolTip:@"iTunes not running."];
1710             [self clearHotKeys];
1711
1712                         
1713             if ([df objectForKey:@"ShowPlayer"] != nil) {
1714                 ITHotKey *hotKey;
1715                 ITDebugLog(@"Setting up show player hot key.");
1716                 hotKey = [[ITHotKey alloc] init];
1717                 [hotKey setName:@"ShowPlayer"];
1718                 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
1719                 [hotKey setTarget:self];
1720                 [hotKey setAction:@selector(showPlayer)];
1721                 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1722             }
1723         }
1724     NS_HANDLER
1725         [self networkError:localException];
1726     NS_ENDHANDLER
1727  }
1728
1729
1730 /*************************************************************************/
1731 #pragma mark -
1732 #pragma mark NSApplication DELEGATE METHODS
1733 /*************************************************************************/
1734
1735 - (void)applicationWillTerminate:(NSNotification *)note
1736 {
1737         [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
1738     [networkController stopRemoteServerSearch];
1739     [self clearHotKeys];
1740     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
1741 }
1742
1743 - (void)applicationDidBecomeActive:(NSNotification *)note
1744 {
1745         //This appears to not work in 10.4
1746         if (_open && !blinged && ![[ITAboutWindowController sharedController] isVisible] && ![NSApp mainWindow] && ([[StatusWindow sharedWindow] exitMode] == ITTransientStatusWindowExitAfterDelay)) {
1747                 [[MainController sharedController] showPreferences];
1748         }
1749 }
1750
1751 /*************************************************************************/
1752 #pragma mark -
1753 #pragma mark DEALLOCATION METHOD
1754 /*************************************************************************/
1755
1756 - (void)dealloc
1757 {
1758     [self applicationTerminated:nil];
1759     [bling release];
1760     [statusItem release];
1761     [statusWindowController release];
1762     [menuController release];
1763     [networkController release];
1764     [_serverCheckLock release];
1765     [super dealloc];
1766 }
1767
1768 @end