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