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