Removed the disabling of the hot key center when I disabled the status
[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                 }
392                 [statusItem setEnabled:NO];
393                 return;
394         } else if (![statusItem isEnabled]) {
395                 [statusItem setEnabled:YES];
396                 [statusItem setToolTip:_toolTip];
397                 return;
398         }
399         
400         if ( [self songChanged] && (timerUpdating != YES) && (playerRunningState == ITMTRemotePlayerRunning) ) {
401         ITDebugLog(@"The song changed. '%@'", _latestSongIdentifier);
402         if ([df boolForKey:@"runScripts"]) {
403             NSArray *scripts = [[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
404             NSEnumerator *scriptsEnum = [scripts objectEnumerator];
405             NSString *nextScript;
406             ITDebugLog(@"Running AppleScripts for song change.");
407             while ( (nextScript = [scriptsEnum nextObject]) ) {
408                 NSDictionary *error;
409                 NSAppleScript *currentScript = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] stringByAppendingPathComponent:nextScript]] error:&error];
410                 ITDebugLog(@"Running script: %@", nextScript);
411                 if (!currentScript || ![currentScript executeAndReturnError:nil]) {
412                     ITDebugLog(@"Error running script %@.", nextScript);
413                 }
414                 [currentScript release];
415             }
416         }
417         
418         timerUpdating = YES;
419         [statusItem setEnabled:NO];
420                 
421         NS_DURING
422             latestPlaylistClass = [[self currentRemote] currentPlaylistClass];
423                         
424                         if ([menuController rebuildSubmenus]) {
425                                 if ( [df boolForKey:@"showSongInfoOnChange"] ) {
426                                         [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
427                                 }
428                                 [self setLatestSongIdentifier:identifier];
429                                 //Create the tooltip for the status item
430                                 if ( [df boolForKey:@"showToolTip"] ) {
431                                         NSString *artist = [[self currentRemote] currentSongArtist];
432                                         NSString *title = [[self currentRemote] currentSongTitle];
433                                         ITDebugLog(@"Creating status item tooltip.");
434                                         if (artist) {
435                                                 _toolTip = [NSString stringWithFormat:@"%@ - %@", artist, title];
436                                         } else if (title) {
437                                                 _toolTip = title;
438                                         } else {
439                                                 _toolTip = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
440                                         }
441                                         [statusItem setToolTip:_toolTip];
442                                 } else {
443                                         [statusItem setToolTip:nil];
444                                 }
445                         }
446         NS_HANDLER
447             [self networkError:localException];
448         NS_ENDHANDLER
449         timerUpdating = NO;
450         [statusItem setEnabled:YES];
451     }
452         
453     if ([networkController isConnectedToServer]) {
454         [statusItem setMenu:([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) ? [menuController menu] : [menuController menuForNoPlayer]];
455     }
456 }
457
458 - (void)menuClicked
459 {
460     ITDebugLog(@"Menu clicked.");
461         
462         if (([[self currentRemote] playerStateUniqueIdentifier] == nil) && playerRunningState == ITMTRemotePlayerRunning) {
463                 if (refreshTimer) {
464                         if ([statusItem isEnabled]) {
465                                 [statusItem setToolTip:NSLocalizedString(@"iTunesNotResponding", @"iTunes is not responding.")];
466                         }
467                         [statusItem setEnabled:NO];
468                 } else {
469                         NSMenu *menu = [[NSMenu alloc] init];
470                         [menu addItemWithTitle:NSLocalizedString(@"iTunesNotResponding", @"iTunes is not responding.") action:nil keyEquivalent:@""];
471                         [statusItem setMenu:[menu autorelease]];
472                 }
473                 return;
474         } else if (![statusItem isEnabled]) {
475                 [statusItem setEnabled:YES];
476                 [statusItem setToolTip:_toolTip];
477                 return;
478         }
479         
480     if ([networkController isConnectedToServer]) {
481         //Used the cached version
482         return;
483     }
484     
485     NS_DURING
486         if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
487             [statusItem setMenu:[menuController menu]];
488         } else {
489             [statusItem setMenu:[menuController menuForNoPlayer]];
490         }
491     NS_HANDLER
492         [self networkError:localException];
493     NS_ENDHANDLER
494 }
495
496 - (void)trackChanged:(NSNotification *)note
497 {
498         //If we're running the timer, shut it off since we don't need it!
499         if (refreshTimer && [refreshTimer isValid]) {
500                 ITDebugLog(@"Invalidating refresh timer.");
501                 [refreshTimer invalidate];
502                 [refreshTimer release];
503                 refreshTimer = nil;
504         }
505         
506         if (![self songChanged]) {
507                 return;
508         }
509         NSString *identifier = [[self currentRemote] playerStateUniqueIdentifier];
510         if ( [df boolForKey:@"showSongInfoOnChange"] ) {
511                 [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
512         }
513         [_lastTrackInfo release];
514         _lastTrackInfo = [[note userInfo] retain];
515         
516         [self setLatestSongIdentifier:identifier];
517         ITDebugLog(@"The song changed. '%@'", _latestSongIdentifier);
518         if ([df boolForKey:@"runScripts"]) {
519                 NSArray *scripts = [[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"]];
520                 NSEnumerator *scriptsEnum = [scripts objectEnumerator];
521                 NSString *nextScript;
522                 ITDebugLog(@"Running AppleScripts for song change.");
523                 while ( (nextScript = [scriptsEnum nextObject]) ) {
524                         NSDictionary *error;
525                         NSAppleScript *currentScript = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/MenuTunes/Scripts"] stringByAppendingPathComponent:nextScript]] error:&error];
526                         ITDebugLog(@"Running script: %@", nextScript);
527                         if (!currentScript || ![currentScript executeAndReturnError:nil]) {
528                                 ITDebugLog(@"Error running script %@.", nextScript);
529                         }
530                         [currentScript release];
531                 }
532         }
533         
534         [statusItem setEnabled:NO];
535         
536         NS_DURING
537                 latestPlaylistClass = [[self currentRemote] currentPlaylistClass];
538                 
539                 if ([menuController rebuildSubmenus]) {
540                         /*if ( [df boolForKey:@"showSongInfoOnChange"] ) {
541                                 [self performSelector:@selector(showCurrentTrackInfo) withObject:nil afterDelay:0.0];
542                         }*/
543                         [self setLatestSongIdentifier:identifier];
544                         //Create the tooltip for the status item
545                         if ( [df boolForKey:@"showToolTip"] ) {
546                                 ITDebugLog(@"Creating status item tooltip.");
547                                 NSString *artist = [_lastTrackInfo objectForKey:@"Artist"], *title = [_lastTrackInfo objectForKey:@"Name"];
548                                 if (artist) {
549                                         _toolTip = [NSString stringWithFormat:@"%@ - %@", artist, title];
550                                 } else if (title) {
551                                         _toolTip = title;
552                                 } else {
553                                         _toolTip = NSLocalizedString(@"noSongPlaying", @"No song is playing.");;
554                                 }
555                                 [statusItem setToolTip:_toolTip];
556                         } else {
557                                 [statusItem setToolTip:nil];
558                         }
559                 }
560         NS_HANDLER
561                 [self networkError:localException];
562         NS_ENDHANDLER
563         timerUpdating = NO;
564         [statusItem setEnabled:YES];
565         
566         if ([networkController isConnectedToServer]) {
567         [statusItem setMenu:([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) ? [menuController menu] : [menuController menuForNoPlayer]];
568     }
569 }
570
571 //
572 //
573 // Menu Selectors
574 //
575 //
576
577 - (void)playPause
578 {
579     NS_DURING
580         ITMTRemotePlayerPlayingState state = [[self currentRemote] playerPlayingState];
581         ITDebugLog(@"Play/Pause toggled");
582         if (state == ITMTRemotePlayerPlaying) {
583             [[self currentRemote] pause];
584         } else if ((state == ITMTRemotePlayerForwarding) || (state == ITMTRemotePlayerRewinding)) {
585             [[self currentRemote] pause];
586             [[self currentRemote] play];
587         } else {
588             [[self currentRemote] play];
589         }
590     NS_HANDLER
591         [self networkError:localException];
592     NS_ENDHANDLER
593     
594         if (refreshTimer) {
595                 [self timerUpdate];
596         }
597 }
598
599 - (void)nextSong
600 {
601     ITDebugLog(@"Going to next song.");
602     NS_DURING
603         [[self currentRemote] goToNextSong];
604     NS_HANDLER
605         [self networkError:localException];
606     NS_ENDHANDLER
607     if (refreshTimer) {
608                 [self timerUpdate];
609         }
610 }
611
612 - (void)prevSong
613 {
614     ITDebugLog(@"Going to previous song.");
615     NS_DURING
616         [[self currentRemote] goToPreviousSong];
617     NS_HANDLER
618         [self networkError:localException];
619     NS_ENDHANDLER
620     if (refreshTimer) {
621                 [self timerUpdate];
622         }
623 }
624
625 - (void)fastForward
626 {
627     ITDebugLog(@"Fast forwarding.");
628     NS_DURING
629         [[self currentRemote] forward];
630     NS_HANDLER
631         [self networkError:localException];
632     NS_ENDHANDLER
633     if (refreshTimer) {
634                 [self timerUpdate];
635         }
636 }
637
638 - (void)rewind
639 {
640     ITDebugLog(@"Rewinding.");
641     NS_DURING
642         [[self currentRemote] rewind];
643     NS_HANDLER
644         [self networkError:localException];
645     NS_ENDHANDLER
646     if (refreshTimer) {
647                 [self timerUpdate];
648         }
649 }
650
651 - (void)selectPlaylistAtIndex:(int)index
652 {
653     ITDebugLog(@"Selecting playlist %i", index);
654     NS_DURING
655         [[self currentRemote] switchToPlaylistAtIndex:(index % 1000) ofSourceAtIndex:(index / 1000)];
656         //[[self currentRemote] switchToPlaylistAtIndex:index];
657     NS_HANDLER
658         [self networkError:localException];
659     NS_ENDHANDLER
660     if (refreshTimer) {
661                 [self timerUpdate];
662         }
663 }
664
665 - (void)selectSongAtIndex:(int)index
666 {
667     ITDebugLog(@"Selecting song %i", index);
668     NS_DURING
669         [[self currentRemote] switchToSongAtIndex:index];
670     NS_HANDLER
671         [self networkError:localException];
672     NS_ENDHANDLER
673     if (refreshTimer) {
674                 [self timerUpdate];
675         }
676 }
677
678 - (void)selectSongRating:(int)rating
679 {
680     ITDebugLog(@"Selecting song rating %i", rating);
681     NS_DURING
682         [[self currentRemote] setCurrentSongRating:(float)rating / 100.0];
683     NS_HANDLER
684         [self networkError:localException];
685     NS_ENDHANDLER
686     if (refreshTimer) {
687                 [self timerUpdate];
688         }
689 }
690
691 - (void)selectEQPresetAtIndex:(int)index
692 {
693     ITDebugLog(@"Selecting EQ preset %i", index);
694     NS_DURING
695         if (index == -1) {
696             [[self currentRemote] setEqualizerEnabled:![[self currentRemote] equalizerEnabled]];
697         } else {
698             [[self currentRemote] switchToEQAtIndex:index];
699         }
700     NS_HANDLER
701         [self networkError:localException];
702     NS_ENDHANDLER
703     if (refreshTimer) {
704                 [self timerUpdate];
705         }
706 }
707
708 - (void)makePlaylistWithTerm:(NSString *)term ofType:(int)type
709 {
710     ITDebugLog(@"Making playlist with term %@, type %i", term, type);
711     NS_DURING
712         [[self currentRemote] makePlaylistWithTerm:term ofType:type];
713     NS_HANDLER
714         [self networkError:localException];
715     NS_ENDHANDLER
716     ITDebugLog(@"Done making playlist");
717 }
718
719 - (void)showPlayer
720 {
721     ITDebugLog(@"Beginning show player.");
722     //if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
723         ITDebugLog(@"Showing player interface.");
724         NS_DURING
725             [[self currentRemote] showPrimaryInterface];
726         NS_HANDLER
727             [self networkError:localException];
728         NS_ENDHANDLER
729     /*} else {
730         ITDebugLog(@"Launching player.");
731         NS_DURING
732             NSString *path;
733             if ( (path = [df stringForKey:@"CustomPlayerPath"]) ) {
734             } else {
735                 pathITDebugLog(@"Showing player interface."); = [[self currentRemote] playerFullName];
736             }
737             if (![[NSWorkspace sharedWorkspace] launchApplication:path]) {
738                 ITDebugLog(@"Error Launching Player");
739             }
740         NS_HANDLER
741             [self networkError:localException];
742         NS_ENDHANDLER
743     }*/
744     ITDebugLog(@"Finished show player.");
745 }
746
747 - (void)showPreferences
748 {
749     ITDebugLog(@"Show preferences.");
750     [[PreferencesController sharedPrefs] showPrefsWindow:self];
751 }
752
753 - (void)showPreferencesAndClose
754 {
755     ITDebugLog(@"Show preferences.");
756     [[PreferencesController sharedPrefs] showPrefsWindow:self];
757     [[StatusWindow sharedWindow] setLocked:NO];
758     [[StatusWindow sharedWindow] vanish:self];
759     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
760 }
761
762 - (void)showTestWindow
763 {
764     [self showCurrentTrackInfo];
765 }
766
767 - (void)quitMenuTunes
768 {
769     ITDebugLog(@"Quitting MenuTunes.");
770     [NSApp terminate:self];
771 }
772
773 //
774 //
775
776 - (MenuController *)menuController
777 {
778     return menuController;
779 }
780
781 - (void)closePreferences
782 {
783     ITDebugLog(@"Preferences closed.");
784     if ( ( playerRunningState == ITMTRemotePlayerRunning) ) {
785         [self setupHotKeys];
786     }
787 }
788
789 - (ITMTRemote *)currentRemote
790 {
791     if ([networkController isConnectedToServer] && ![[networkController networkObject] isValid]) {
792         [self networkError:nil];
793         return nil;
794     }
795     return currentRemote;
796 }
797
798 //
799 //
800 // Hot key setup
801 //
802 //
803
804 - (void)clearHotKeys
805 {
806     NSEnumerator *hotKeyEnumerator = [[[ITHotKeyCenter sharedCenter] allHotKeys] objectEnumerator];
807     ITHotKey *nextHotKey;
808     ITDebugLog(@"Clearing hot keys.");
809     while ( (nextHotKey = [hotKeyEnumerator nextObject]) ) {
810         [[ITHotKeyCenter sharedCenter] unregisterHotKey:nextHotKey];
811     }
812     ITDebugLog(@"Done clearing hot keys.");
813 }
814
815 - (void)setupHotKeys
816 {
817     ITHotKey *hotKey;
818     ITDebugLog(@"Setting up hot keys.");
819     
820     if (playerRunningState == ITMTRemotePlayerNotRunning && ![[NetworkController sharedController] isConnectedToServer]) {
821         return;
822     }
823     
824     if ([df objectForKey:@"PlayPause"] != nil) {
825         ITDebugLog(@"Setting up play pause hot key.");
826         hotKey = [[ITHotKey alloc] init];
827         [hotKey setName:@"PlayPause"];
828         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PlayPause"]]];
829         [hotKey setTarget:self];
830         [hotKey setAction:@selector(playPause)];
831         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
832     }
833     
834     if ([df objectForKey:@"NextTrack"] != nil) {
835         ITDebugLog(@"Setting up next track hot key.");
836         hotKey = [[ITHotKey alloc] init];
837         [hotKey setName:@"NextTrack"];
838         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"NextTrack"]]];
839         [hotKey setTarget:self];
840         [hotKey setAction:@selector(nextSong)];
841         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
842     }
843     
844     if ([df objectForKey:@"PrevTrack"] != nil) {
845         ITDebugLog(@"Setting up previous track hot key.");
846         hotKey = [[ITHotKey alloc] init];
847         [hotKey setName:@"PrevTrack"];
848         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PrevTrack"]]];
849         [hotKey setTarget:self];
850         [hotKey setAction:@selector(prevSong)];
851         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
852     }
853     
854     if ([df objectForKey:@"FastForward"] != nil) {
855         ITDebugLog(@"Setting up fast forward hot key.");
856         hotKey = [[ITHotKey alloc] init];
857         [hotKey setName:@"FastForward"];
858         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"FastForward"]]];
859         [hotKey setTarget:self];
860         [hotKey setAction:@selector(fastForward)];
861         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
862     }
863     
864     if ([df objectForKey:@"Rewind"] != nil) {
865         ITDebugLog(@"Setting up rewind hot key.");
866         hotKey = [[ITHotKey alloc] init];
867         [hotKey setName:@"Rewind"];
868         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"Rewind"]]];
869         [hotKey setTarget:self];
870         [hotKey setAction:@selector(rewind)];
871         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
872     }
873     
874     if ([df objectForKey:@"ShowPlayer"] != nil) {
875         ITDebugLog(@"Setting up show player hot key.");
876         hotKey = [[ITHotKey alloc] init];
877         [hotKey setName:@"ShowPlayer"];
878         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
879         [hotKey setTarget:self];
880         [hotKey setAction:@selector(showPlayer)];
881         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
882     }
883     
884     if ([df objectForKey:@"TrackInfo"] != nil) {
885         ITDebugLog(@"Setting up track info hot key.");
886         hotKey = [[ITHotKey alloc] init];
887         [hotKey setName:@"TrackInfo"];
888         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"TrackInfo"]]];
889         [hotKey setTarget:self];
890         [hotKey setAction:@selector(showCurrentTrackInfo)];
891         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
892     }
893     
894     if ([df objectForKey:@"UpcomingSongs"] != nil) {
895         ITDebugLog(@"Setting up upcoming songs hot key.");
896         hotKey = [[ITHotKey alloc] init];
897         [hotKey setName:@"UpcomingSongs"];
898         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"UpcomingSongs"]]];
899         [hotKey setTarget:self];
900         [hotKey setAction:@selector(showUpcomingSongs)];
901         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
902     }
903     
904     if ([df objectForKey:@"ToggleLoop"] != nil) {
905         ITDebugLog(@"Setting up toggle loop hot key.");
906         hotKey = [[ITHotKey alloc] init];
907         [hotKey setName:@"ToggleLoop"];
908         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleLoop"]]];
909         [hotKey setTarget:self];
910         [hotKey setAction:@selector(toggleLoop)];
911         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
912     }
913     
914     if ([df objectForKey:@"ToggleShuffle"] != nil) {
915         ITDebugLog(@"Setting up toggle shuffle hot key.");
916         hotKey = [[ITHotKey alloc] init];
917         [hotKey setName:@"ToggleShuffle"];
918         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ToggleShuffle"]]];
919         [hotKey setTarget:self];
920         [hotKey setAction:@selector(toggleShuffle)];
921         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
922     }
923     
924     if ([df objectForKey:@"IncrementVolume"] != nil) {
925         ITDebugLog(@"Setting up increment volume hot key.");
926         hotKey = [[ITHotKey alloc] init];
927         [hotKey setName:@"IncrementVolume"];
928         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementVolume"]]];
929         [hotKey setTarget:self];
930         [hotKey setAction:@selector(incrementVolume)];
931         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
932     }
933     
934     if ([df objectForKey:@"DecrementVolume"] != nil) {
935         ITDebugLog(@"Setting up decrement volume hot key.");
936         hotKey = [[ITHotKey alloc] init];
937         [hotKey setName:@"DecrementVolume"];
938         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementVolume"]]];
939         [hotKey setTarget:self];
940         [hotKey setAction:@selector(decrementVolume)];
941         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
942     }
943     
944     if ([df objectForKey:@"IncrementRating"] != nil) {
945         ITDebugLog(@"Setting up increment rating hot key.");
946         hotKey = [[ITHotKey alloc] init];
947         [hotKey setName:@"IncrementRating"];
948         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"IncrementRating"]]];
949         [hotKey setTarget:self];
950         [hotKey setAction:@selector(incrementRating)];
951         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
952     }
953     
954     if ([df objectForKey:@"DecrementRating"] != nil) {
955         ITDebugLog(@"Setting up decrement rating hot key.");
956         hotKey = [[ITHotKey alloc] init];
957         [hotKey setName:@"DecrementRating"];
958         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"DecrementRating"]]];
959         [hotKey setTarget:self];
960         [hotKey setAction:@selector(decrementRating)];
961         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
962     }
963     
964     if ([df objectForKey:@"PopupMenu"] != nil) {
965         ITDebugLog(@"Setting up popup menu hot key.");
966         hotKey = [[ITHotKey alloc] init];
967         [hotKey setName:@"PopupMenu"];
968         [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"PopupMenu"]]];
969         [hotKey setTarget:self];
970         [hotKey setAction:@selector(popupMenu)];
971         [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
972     }
973     
974     int i;
975     for (i = 0; i <= 5; i++) {
976         NSString *curName = [NSString stringWithFormat:@"SetRating%i", i];
977         if ([df objectForKey:curName] != nil) {
978             ITDebugLog(@"Setting up set rating %i hot key.", i);
979             hotKey = [[ITHotKey alloc] init];
980             [hotKey setName:curName];
981             [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:curName]]];
982             [hotKey setTarget:self];
983             [hotKey setAction:@selector(setRating:)];
984             [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
985         }
986     }
987     ITDebugLog(@"Finished setting up hot keys.");
988 }
989
990 - (void)showCurrentTrackInfo
991 {
992     ITMTRemotePlayerSource  source      = 0;
993     NSString               *title       = nil;
994     NSString               *album       = nil;
995     NSString               *artist      = nil;
996     NSString               *composer    = nil;
997     NSString               *time        = nil;
998     NSString               *track       = nil;
999     NSImage                *art         = nil;
1000     int                     rating      = -1;
1001     int                     playCount   = -1;
1002     
1003     ITDebugLog(@"Showing track info status window.");
1004     
1005     NS_DURING
1006         source      = [[self currentRemote] currentSource];
1007         title       = [[self currentRemote] currentSongTitle];
1008     NS_HANDLER
1009         [self networkError:localException];
1010     NS_ENDHANDLER
1011     
1012     if ( title ) {
1013         if ( [df boolForKey:@"showAlbumArtwork"] ) {
1014             NSSize oldSize, newSize;
1015              NS_DURING
1016                  art = [[self currentRemote] currentSongAlbumArt];
1017                  oldSize = [art size];
1018                  if (oldSize.width > oldSize.height) newSize = NSMakeSize(110,oldSize.height * (110.0f / oldSize.width));
1019                  else newSize = NSMakeSize(oldSize.width * (110.0f / oldSize.height),110);
1020                 art = [[[[NSImage alloc] initWithData:[art TIFFRepresentation]] autorelease] imageScaledSmoothlyToSize:newSize];
1021             NS_HANDLER
1022                 [self networkError:localException];
1023             NS_ENDHANDLER
1024         }
1025         
1026         if ( [df boolForKey:@"showAlbum"] ) {
1027             NS_DURING
1028                 album = [[self currentRemote] currentSongAlbum];
1029             NS_HANDLER
1030                 [self networkError:localException];
1031             NS_ENDHANDLER
1032         }
1033
1034         if ( [df boolForKey:@"showArtist"] ) {
1035             NS_DURING
1036                 artist = [[self currentRemote] currentSongArtist];
1037             NS_HANDLER
1038                 [self networkError:localException];
1039             NS_ENDHANDLER
1040         }
1041
1042         if ( [df boolForKey:@"showComposer"] ) {
1043             NS_DURING
1044                 composer = [[self currentRemote] currentSongComposer];
1045             NS_HANDLER
1046                 [self networkError:localException];
1047             NS_ENDHANDLER
1048         }
1049
1050         if ( [df boolForKey:@"showTime"] ) {
1051             NS_DURING
1052                 time = [NSString stringWithFormat:@"%@: %@ / %@",
1053                 NSLocalizedString(@"time", @"Time"),
1054                 [[self currentRemote] currentSongElapsed],
1055                 [[self currentRemote] currentSongLength]];
1056             NS_HANDLER
1057                 [self networkError:localException];
1058             NS_ENDHANDLER
1059         }
1060
1061         if ( [df boolForKey:@"showTrackNumber"] ) {
1062             int trackNo    = 0;
1063             int trackCount = 0;
1064             
1065             NS_DURING
1066                 trackNo    = [[self currentRemote] currentSongTrack];
1067                 trackCount = [[self currentRemote] currentAlbumTrackCount];
1068             NS_HANDLER
1069                 [self networkError:localException];
1070             NS_ENDHANDLER
1071             
1072             if ( (trackNo > 0) || (trackCount > 0) ) {
1073                 track = [NSString stringWithFormat:@"%@: %i %@ %i",
1074                     @"Track", trackNo, @"of", trackCount];
1075             }
1076         }
1077
1078         if ( [df boolForKey:@"showTrackRating"] ) {
1079             float currentRating = 0;
1080             
1081             NS_DURING
1082                 currentRating = [[self currentRemote] currentSongRating];
1083             NS_HANDLER
1084                 [self networkError:localException];
1085             NS_ENDHANDLER
1086             
1087             if (currentRating >= 0.0) {
1088                 rating = ( currentRating * 5 );
1089             }
1090         }
1091         
1092         if ( [df boolForKey:@"showPlayCount"] && ![self radioIsPlaying] && [[self currentRemote] currentSource] == ITMTRemoteLibrarySource ) {
1093             NS_DURING
1094                 playCount = [[self currentRemote] currentSongPlayCount];
1095             NS_HANDLER
1096                 [self networkError:localException];
1097             NS_ENDHANDLER
1098         }
1099     } else {
1100         title = NSLocalizedString(@"noSongPlaying", @"No song is playing.");
1101     }
1102     ITDebugLog(@"Showing current track info status window.");
1103     [statusWindowController showSongInfoWindowWithSource:source
1104                                                    title:title
1105                                                    album:album
1106                                                   artist:artist
1107                                                 composer:composer
1108                                                     time:time
1109                                                    track:track
1110                                                   rating:rating
1111                                                playCount:playCount
1112                                                    image:art];
1113 }
1114
1115 - (void)showUpcomingSongs
1116 {
1117     int numSongs = 0;
1118     NS_DURING
1119         numSongs = [[self currentRemote] numberOfSongsInPlaylistAtIndex:[[self currentRemote] currentPlaylistIndex]];
1120     NS_HANDLER
1121         [self networkError:localException];
1122     NS_ENDHANDLER
1123     
1124     ITDebugLog(@"Showing upcoming songs status window.");
1125     NS_DURING
1126         if (numSongs > 0) {
1127             int numSongsInAdvance = [df integerForKey:@"SongsInAdvance"];
1128             NSMutableArray *songList = [NSMutableArray arrayWithCapacity:numSongsInAdvance];
1129             int curTrack = [[self currentRemote] currentSongIndex];
1130             int i;
1131     
1132             for (i = curTrack + 1; i <= curTrack + numSongsInAdvance; i++) {
1133                 if (i <= numSongs) {
1134                     [songList addObject:[[self currentRemote] songTitleAtIndex:i]];
1135                 }
1136             }
1137             
1138             if ([songList count] == 0) {
1139                 [songList addObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")];
1140             }
1141             
1142             [statusWindowController showUpcomingSongsWindowWithTitles:songList];
1143         } else {
1144             [statusWindowController showUpcomingSongsWindowWithTitles:[NSArray arrayWithObject:NSLocalizedString(@"noUpcomingSongs", @"No upcoming songs.")]];
1145         }
1146     NS_HANDLER
1147         [self networkError:localException];
1148     NS_ENDHANDLER
1149 }
1150
1151 - (void)popupMenu
1152 {
1153     if (!_popped) {
1154         _popped = YES;
1155         [self menuClicked];
1156         NSMenu *menu = [statusItem menu];
1157         [(NSCarbonMenuImpl *)[menu _menuImpl] popUpMenu:menu atLocation:[NSEvent mouseLocation] width:1 forView:nil withSelectedItem:-30 withFont:[NSFont menuFontOfSize:32]];
1158         _popped = NO;
1159     }
1160 }
1161
1162 - (void)incrementVolume
1163 {
1164     NS_DURING
1165         float volume  = [[self currentRemote] volume];
1166         float dispVol = volume;
1167         ITDebugLog(@"Incrementing volume.");
1168         volume  += 0.110;
1169         dispVol += 0.100;
1170         
1171         if (volume > 1.0) {
1172             volume  = 1.0;
1173             dispVol = 1.0;
1174         }
1175     
1176         ITDebugLog(@"Setting volume to %f", volume);
1177         [[self currentRemote] setVolume:volume];
1178     
1179         // Show volume status window
1180         [statusWindowController showVolumeWindowWithLevel:dispVol];
1181     NS_HANDLER
1182         [self networkError:localException];
1183     NS_ENDHANDLER
1184 }
1185
1186 - (void)decrementVolume
1187 {
1188     NS_DURING
1189         float volume  = [[self currentRemote] volume];
1190         float dispVol = volume;
1191         ITDebugLog(@"Decrementing volume.");
1192         volume  -= 0.090;
1193         dispVol -= 0.100;
1194     
1195         if (volume < 0.0) {
1196             volume  = 0.0;
1197             dispVol = 0.0;
1198         }
1199         
1200         ITDebugLog(@"Setting volume to %f", volume);
1201         [[self currentRemote] setVolume:volume];
1202         
1203         //Show volume status window
1204         [statusWindowController showVolumeWindowWithLevel:dispVol];
1205     NS_HANDLER
1206         [self networkError:localException];
1207     NS_ENDHANDLER
1208 }
1209
1210 - (void)incrementRating
1211 {
1212     NS_DURING
1213         float rating = [[self currentRemote] currentSongRating];
1214         ITDebugLog(@"Incrementing rating.");
1215         
1216         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1217             ITDebugLog(@"No song playing, rating change aborted.");
1218             return;
1219         }
1220         
1221         rating += 0.2;
1222         if (rating > 1.0) {
1223             rating = 1.0;
1224         }
1225         ITDebugLog(@"Setting rating to %f", rating);
1226         [[self currentRemote] setCurrentSongRating:rating];
1227         
1228         //Show rating status window
1229         [statusWindowController showRatingWindowWithRating:rating];
1230     NS_HANDLER
1231         [self networkError:localException];
1232     NS_ENDHANDLER
1233 }
1234
1235 - (void)decrementRating
1236 {
1237     NS_DURING
1238         float rating = [[self currentRemote] currentSongRating];
1239         ITDebugLog(@"Decrementing rating.");
1240         
1241         if ([[self currentRemote] currentPlaylistIndex] == 0) {
1242             ITDebugLog(@"No song playing, rating change aborted.");
1243             return;
1244         }
1245         
1246         rating -= 0.2;
1247         if (rating < 0.0) {
1248             rating = 0.0;
1249         }
1250         ITDebugLog(@"Setting rating to %f", rating);
1251         [[self currentRemote] setCurrentSongRating:rating];
1252         
1253         //Show rating status window
1254         [statusWindowController showRatingWindowWithRating:rating];
1255     NS_HANDLER
1256         [self networkError:localException];
1257     NS_ENDHANDLER
1258 }
1259
1260 - (void)setRating:(ITHotKey *)sender
1261 {
1262     int stars = [[sender name] characterAtIndex:9] - 48;
1263     [self selectSongRating:stars * 20];
1264     [statusWindowController showRatingWindowWithRating:(float)stars / 5.0];
1265 }
1266
1267 - (void)toggleLoop
1268 {
1269     NS_DURING
1270         ITMTRemotePlayerRepeatMode repeatMode = [[self currentRemote] repeatMode];
1271         ITDebugLog(@"Toggling repeat mode.");
1272         switch (repeatMode) {
1273             case ITMTRemotePlayerRepeatOff:
1274                 repeatMode = ITMTRemotePlayerRepeatAll;
1275             break;
1276             case ITMTRemotePlayerRepeatAll:
1277                 repeatMode = ITMTRemotePlayerRepeatOne;
1278             break;
1279             case ITMTRemotePlayerRepeatOne:
1280                 repeatMode = ITMTRemotePlayerRepeatOff;
1281             break;
1282         }
1283         ITDebugLog(@"Setting repeat mode to %i", repeatMode);
1284         [[self currentRemote] setRepeatMode:repeatMode];
1285         
1286         //Show loop status window
1287         [statusWindowController showRepeatWindowWithMode:repeatMode];
1288     NS_HANDLER
1289         [self networkError:localException];
1290     NS_ENDHANDLER
1291 }
1292
1293 - (void)toggleShuffle
1294 {
1295     NS_DURING
1296         BOOL newShuffleEnabled = ( ! [[self currentRemote] shuffleEnabled] );
1297         ITDebugLog(@"Toggling shuffle mode.");
1298         [[self currentRemote] setShuffleEnabled:newShuffleEnabled];
1299         //Show shuffle status window
1300         ITDebugLog(@"Setting shuffle mode to %i", newShuffleEnabled);
1301         [statusWindowController showShuffleWindow:newShuffleEnabled];
1302     NS_HANDLER
1303         [self networkError:localException];
1304     NS_ENDHANDLER
1305 }
1306
1307 - (void)registerNowOK
1308 {
1309     [[StatusWindow sharedWindow] setLocked:NO];
1310     [[StatusWindow sharedWindow] vanish:self];
1311     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1312
1313     [self blingNow];
1314 }
1315
1316 - (void)registerNowCancel
1317 {
1318     [[StatusWindow sharedWindow] setLocked:NO];
1319     [[StatusWindow sharedWindow] vanish:self];
1320     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1321
1322     [NSApp terminate:self];
1323 }
1324
1325 /*************************************************************************/
1326 #pragma mark -
1327 #pragma mark NETWORK HANDLERS
1328 /*************************************************************************/
1329
1330 - (void)setServerStatus:(BOOL)newStatus
1331 {
1332     if (newStatus) {
1333         //Turn on
1334         [networkController setServerStatus:YES];
1335     } else {
1336         //Tear down
1337         [networkController setServerStatus:NO];
1338     }
1339 }
1340
1341 - (int)connectToServer
1342 {
1343     int result;
1344     ITDebugLog(@"Attempting to connect to shared remote.");
1345     result = [networkController connectToHost:[df stringForKey:@"sharedPlayerHost"]];
1346     //Connect
1347     if (result == 1) {
1348         [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1349         currentRemote = [[[networkController networkObject] remote] retain];
1350         
1351         [self setupHotKeys];
1352         //playerRunningState = ITMTRemotePlayerRunning;
1353         playerRunningState = [[self currentRemote] playerRunningState];
1354                 if (refreshTimer) {
1355                         [refreshTimer invalidate];
1356                 }
1357         refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1358                                 target:self
1359                                 selector:@selector(timerUpdate)
1360                                 userInfo:nil
1361                                 repeats:YES] retain];
1362         [self timerUpdate];
1363         ITDebugLog(@"Connection successful.");
1364         return 1;
1365     } else if (result == 0) {
1366         ITDebugLog(@"Connection failed.");
1367         currentRemote = [remoteArray objectAtIndex:0];
1368         return 0;
1369     } else {
1370         //Do something about the password being invalid
1371         ITDebugLog(@"Connection failed.");
1372         currentRemote = [remoteArray objectAtIndex:0];
1373         return -1;
1374     }
1375 }
1376
1377 - (BOOL)disconnectFromServer
1378 {
1379     ITDebugLog(@"Disconnecting from shared remote.");
1380     //Disconnect
1381     [currentRemote release];
1382     currentRemote = [remoteArray objectAtIndex:0];
1383     [networkController disconnect];
1384     
1385     if ([[self currentRemote] playerRunningState] == ITMTRemotePlayerRunning) {
1386         [self applicationLaunched:nil];
1387     } else {
1388         [self applicationTerminated:nil];
1389     }
1390     if (refreshTimer) {
1391                 [self timerUpdate];
1392         };
1393     return YES;
1394 }
1395
1396 - (void)checkForRemoteServer
1397 {
1398     [self checkForRemoteServerAndConnectImmediately:NO];
1399 }
1400
1401 - (void)checkForRemoteServerAndConnectImmediately:(BOOL)connectImmediately
1402 {
1403     ITDebugLog(@"Checking for remote server.");
1404     if (!_checkingForServer) {
1405         if (!_serverCheckLock) {
1406             _serverCheckLock = [[NSLock alloc] init];
1407         }
1408         [_serverCheckLock lock];
1409         _checkingForServer = YES;
1410         [_serverCheckLock unlock];
1411         [NSThread detachNewThreadSelector:@selector(runRemoteServerCheck:) toTarget:self withObject:[NSNumber numberWithBool:connectImmediately]];
1412     }
1413 }
1414
1415 - (void)runRemoteServerCheck:(id)sender
1416 {
1417     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1418     ITDebugLog(@"Remote server check running.");
1419     if ([networkController checkForServerAtHost:[df stringForKey:@"sharedPlayerHost"]]) {
1420         ITDebugLog(@"Remote server found.");
1421         if ([sender boolValue]) {
1422             [self performSelectorOnMainThread:@selector(connectToServer) withObject:nil waitUntilDone:NO];
1423         } else {
1424             [self performSelectorOnMainThread:@selector(remoteServerFound:) withObject:nil waitUntilDone:NO];
1425         }
1426     } else {
1427         ITDebugLog(@"Remote server not found.");
1428         [self performSelectorOnMainThread:@selector(remoteServerNotFound:) withObject:nil waitUntilDone:NO];
1429     }
1430     [_serverCheckLock lock];
1431     _checkingForServer = NO;
1432     [_serverCheckLock unlock];
1433     [pool release];
1434 }
1435
1436 - (void)remoteServerFound:(id)sender
1437 {
1438     if (![networkController isServerOn] && ![networkController isConnectedToServer]) {
1439         [[StatusWindowController sharedController] showReconnectQueryWindow];
1440     }
1441 }
1442
1443 - (void)remoteServerNotFound:(id)sender
1444 {
1445     if (![[NetworkController sharedController] isConnectedToServer]) {
1446         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1447     }
1448 }
1449
1450 - (void)networkError:(NSException *)exception
1451 {
1452     ITDebugLog(@"Remote exception thrown: %@: %@", [exception name], [exception reason]);
1453     if ( ((exception == nil) || [[exception name] isEqualToString:NSPortTimeoutException]) && [networkController isConnectedToServer]) {
1454         //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);
1455         [[StatusWindowController sharedController] showNetworkErrorQueryWindow];
1456         if ([self disconnectFromServer]) {
1457             [[PreferencesController sharedPrefs] resetRemotePlayerTextFields];
1458             [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1459         } else {
1460             ITDebugLog(@"CRITICAL ERROR, DISCONNECTING!");
1461         }
1462     }
1463 }
1464
1465 - (void)reconnect
1466 {
1467     /*if ([self connectToServer] == 0) {
1468         [NSTimer scheduledTimerWithTimeInterval:90.0 target:self selector:@selector(checkForRemoteServer) userInfo:nil repeats:NO];
1469     }*/
1470     [self checkForRemoteServerAndConnectImmediately:YES];
1471     [[StatusWindow sharedWindow] setLocked:NO];
1472     [[StatusWindow sharedWindow] vanish:self];
1473     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1474 }
1475
1476 - (void)cancelReconnect
1477 {
1478     [[StatusWindow sharedWindow] setLocked:NO];
1479     [[StatusWindow sharedWindow] vanish:self];
1480     [[StatusWindow sharedWindow] setIgnoresMouseEvents:YES];
1481 }
1482
1483 /*************************************************************************/
1484 #pragma mark -
1485 #pragma mark WORKSPACE NOTIFICATION HANDLERS
1486 /*************************************************************************/
1487
1488 - (void)applicationLaunched:(NSNotification *)note
1489 {
1490     NS_DURING
1491         if (!note || ([[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer])) {
1492             ITDebugLog(@"Remote application launched.");
1493             playerRunningState = ITMTRemotePlayerRunning;
1494             [[self currentRemote] begin];
1495             [self setLatestSongIdentifier:@""];
1496             [self timerUpdate];
1497             refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:([networkController isConnectedToServer] ? 10.0 : 0.5)
1498                                 target:self
1499                                 selector:@selector(timerUpdate)
1500                                 userInfo:nil
1501                                 repeats:YES] retain];
1502             //[NSThread detachNewThreadSelector:@selector(startTimerInNewThread) toTarget:self withObject:nil];
1503                         if (![df boolForKey:@"UsePollingOnly"]) {
1504                                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(trackChanged:) name:@"ITMTTrackChanged" object:nil];
1505                         }
1506             [self setupHotKeys];
1507         }
1508     NS_HANDLER
1509         [self networkError:localException];
1510     NS_ENDHANDLER
1511 }
1512
1513  - (void)applicationTerminated:(NSNotification *)note
1514  {
1515     NS_DURING
1516         if (!note || [[[note userInfo] objectForKey:@"NSApplicationName"] isEqualToString:[[self currentRemote] playerFullName]] && ![[NetworkController sharedController] isConnectedToServer]) {
1517             ITDebugLog(@"Remote application terminated.");
1518             playerRunningState = ITMTRemotePlayerNotRunning;
1519             [[self currentRemote] halt];
1520             [refreshTimer invalidate];
1521             [refreshTimer release];
1522             refreshTimer = nil;
1523                         [[NSNotificationCenter defaultCenter] removeObserver:self];
1524                         [statusItem setEnabled:YES];
1525                         [statusItem setToolTip:@"iTunes not running."];
1526             [self clearHotKeys];
1527
1528                         
1529             if ([df objectForKey:@"ShowPlayer"] != nil) {
1530                 ITHotKey *hotKey;
1531                 ITDebugLog(@"Setting up show player hot key.");
1532                 hotKey = [[ITHotKey alloc] init];
1533                 [hotKey setName:@"ShowPlayer"];
1534                 [hotKey setKeyCombo:[ITKeyCombo keyComboWithPlistRepresentation:[df objectForKey:@"ShowPlayer"]]];
1535                 [hotKey setTarget:self];
1536                 [hotKey setAction:@selector(showPlayer)];
1537                 [[ITHotKeyCenter sharedCenter] registerHotKey:[hotKey autorelease]];
1538             }
1539         }
1540     NS_HANDLER
1541         [self networkError:localException];
1542     NS_ENDHANDLER
1543  }
1544
1545
1546 /*************************************************************************/
1547 #pragma mark -
1548 #pragma mark NSApplication DELEGATE METHODS
1549 /*************************************************************************/
1550
1551 - (void)applicationWillTerminate:(NSNotification *)note
1552 {
1553         [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
1554     [networkController stopRemoteServerSearch];
1555     [self clearHotKeys];
1556     [[NSStatusBar systemStatusBar] removeStatusItem:statusItem];
1557 }
1558
1559
1560 /*************************************************************************/
1561 #pragma mark -
1562 #pragma mark DEALLOCATION METHOD
1563 /*************************************************************************/
1564
1565 - (void)dealloc
1566 {
1567     [self applicationTerminated:nil];
1568     [bling release];
1569     [statusItem release];
1570     [statusWindowController release];
1571     [menuController release];
1572     [networkController release];
1573     [_serverCheckLock release];
1574     [super dealloc];
1575 }
1576
1577 @end