// // MainController.m // iLyrics // // Created by Kim Wittenburg on 10.06.12. // Copyright (c) 2012 __MyCompanyName__. All rights reserved. // #import "MainController.h" @implementation MainController { NSMutableArray *loadedResults; BOOL lyricsSelected; NSInteger selectedSavePanelButton; NSURL *saveFile; Lyrics *currentLyrics; int selectedRow; iLyrics *ilyrics; } @synthesize iLyricsMenuItem; @synthesize window; @synthesize searchField; @synthesize resultsOutline; @synthesize showPreviewCheckBox; @synthesize previewPopover; @synthesize previewTextArea; @synthesize songLabel; @synthesize artistLabel; @synthesize lyricsArea; -(id)init { loadedResults = [[NSMutableArray alloc] init]; ilyrics = [iLyrics sharediLyrics]; return [super init]; } -(void)awakeFromNib { [self lyricsSelectionChanged:resultsOutline]; } #pragma mark - #pragma mark Outline view Data Source and Delegate -(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { return item == nil ? [loadedResults count] + 1 : 1; } -(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { if (index == [outlineView numberOfRows]-1) { return NSLocalizedString(@"iLyrics.text.loadMoreResults", @""); } return [loadedResults objectAtIndex:index]; } -(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { return NO; } -(id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { if ([item isKindOfClass:[NSString class]]) { return item; } if ([[tableColumn identifier] isEqualToString:@"song"]) { return [item name]; } else if ([[tableColumn identifier] isEqualToString:@"artist"]) { return [item artist]; } else { return [[item loadedByHoster] name]; } } -(NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item { if ([item isKindOfClass:[NSString class]]) { NSPopUpButtonCell *cell = [[NSPopUpButtonCell alloc] init]; NSMenu *menu = [[NSMenu alloc] init]; NSMenuItem *titleItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"iLyrics.text.loadNextResultsFrom", @"") action:@selector(nothingResponsible:soDisable:titleItem:) keyEquivalent:@""]; [menu addItem:titleItem]; for (id hoster in [ilyrics lyricsHosters]) { NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:[hoster name] action:@selector(loadNextResults:) keyEquivalent:@""]; [menuItem setTarget:self]; [menu addItem:menuItem]; } [cell setMenu:menu]; [cell setControlSize:NSMiniControlSize]; return cell; } return nil; } -(NSString *)outlineView:(NSOutlineView *)outlineView toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn item:(id)item mouseLocation:(NSPoint)mouseLocation { //item is an instance of SearchResult if ([item isKindOfClass:[SearchResult class]]) { [self shouldShowPreviewForCellRect:*rect searchResult:item]; } return nil; } #pragma mark - #pragma mark Responding to Lyrics Search - (IBAction)getCurrentiTunesSong:(id)sender { iTunesTrack *track = [[ilyrics iTunes] currentTrack]; NSString *name = [track name]; NSString *artist = [track artist]; if (name == nil) { NSBeginAlertSheet(NSLocalizedString(@"iTunes.messages.noTrackPlaying.title", @""), NSLocalizedString(@"OK", @""), nil, nil, window, nil, nil, nil, nil, NSLocalizedString(@"iTunes.messages.noTrackPlaying.detail", @"")); return; } NSString *searchText = [NSString stringWithFormat:@"%@ - %@", name, artist]; [searchField setStringValue:searchText]; [searchField performClick:sender]; } - (IBAction)getCurrentSpotifySong:(id)sender { SpotifyTrack *track = [[ilyrics spotify] currentTrack]; NSString *name = [track name]; NSString *artist = [track artist]; if (name == nil) { NSBeginAlertSheet(NSLocalizedString(@"Spotify.messages.noTrackPlaying.title", @""), NSLocalizedString(@"OK", @""), nil, nil, window, nil, nil, nil, nil, NSLocalizedString(@"Spotify.messages.noTrackPlaying.detail", @"")); return; } NSString *searchText = [NSString stringWithFormat:@"%@ - %@", name, artist]; [searchField setStringValue:searchText]; [searchField performClick:sender]; } - (IBAction)startNewSearch:(id)sender { [self resetLoadedResults:sender]; if ([[searchField stringValue] length] > 0) { for (id hoster in [ilyrics lyricsHosters]) { [hoster startNewSearchForQuery:[searchField stringValue]]; } //Load first results BOOL networkError = YES; BOOL resultsFound = NO; for (id hoster in [ilyrics lyricsHosters]) { NSArray *results = [hoster nextResults]; if (results) { networkError = NO; if ([results count] > 0) { resultsFound = YES; [loadedResults addObjectsFromArray:results]; [resultsOutline reloadData]; break; } } else { networkError = networkError && YES; } } if (networkError) { [self presentNetworkErrorWithHoster:nil]; } else if (!resultsFound) { [self presentNoResultsErrorWithHoster:nil]; } } [self lyricsSelectionChanged:resultsOutline]; } - (void)loadNextResults:(id)sender{ [self loadNextResultsFromHoster:[ilyrics hosterWithName:[sender title]]]; } - (void) loadNextResultsFromHoster: (id) hoster { if (hoster == nil) { return; } NSArray *nextResults = [hoster nextResults]; if (nextResults == nil) { [self presentNetworkErrorWithHoster:hoster]; return; } if ([nextResults count] == 0) { [self presentNoResultsErrorWithHoster:hoster]; return; } [loadedResults addObjectsFromArray:nextResults]; [resultsOutline reloadData]; } -(void)presentNetworkErrorWithHoster: (id)hoster { NSString *title; NSString *detail; if (hoster) { title = [NSLocalizedString(@"Hoster.messages.networkOrQueryError.title", @"") stringByReplacingOccurrencesOfString:@"%hoster%" withString:[hoster name]]; detail = [NSLocalizedString(@"Hoster.messages.networkOrQueryError.detail", @"") stringByReplacingOccurrencesOfString:@"%hoster%" withString:[hoster name]]; } else { title = NSLocalizedString(@"iLyrics.messages.networkOrQueryError.title", @""); detail = NSLocalizedString(@"iLyrics.messages.networkOrQueryError.detail", @""); } NSRunCriticalAlertPanel(title, detail, NSLocalizedString(@"OK", @""), nil, nil); } -(void)presentNoResultsErrorWithHoster: (id)hoster { NSString *title; NSString *detail; if (hoster) { title = [NSLocalizedString(@"Hoster.messages.noResults.title", @"") stringByReplacingOccurrencesOfString:@"%hoster%" withString:[hoster name]]; detail = [NSLocalizedString(@"Hoster.messages.noResults.detail", @"") stringByReplacingOccurrencesOfString:@"%hoster%" withString:[hoster name]]; } else { title = NSLocalizedString(@"iLyrics.messages.noResults.title", @"") ; detail = NSLocalizedString(@"iLyrics.messages.noResults.detail", @""); } NSRunAlertPanel(title, detail, NSLocalizedString(@"OK", @""), nil, nil); } -(IBAction)resetLoadedResults:(id)sender { for (id hoster in [ilyrics lyricsHosters]) { [hoster resetLoadedResults]; } [loadedResults removeAllObjects]; [resultsOutline reloadData]; [self lyricsSelectionChanged:resultsOutline]; } - (IBAction)lyricsSelectionChanged:(NSOutlineView *)sender { int index = [sender selectedRow]; if (index == [sender numberOfRows]-1) { index = -1; } if (index < 0) { lyricsSelected = NO; currentLyrics = nil; NSString *noSelectionText = NSLocalizedString(@"iLyrics.text.noSelection", @""); [songLabel setStringValue:noSelectionText]; [artistLabel setStringValue:noSelectionText]; [lyricsArea setString:noSelectionText]; [lyricsArea setEditable:NO]; } else { if (selectedRow != index) { lyricsSelected = YES; SearchResult *result = [loadedResults objectAtIndex:index]; Lyrics *lyrics = [[result loadedByHoster] lyricsBySearchResult:result]; currentLyrics = lyrics; if (lyrics == nil) { NSString *title = [NSLocalizedString(@"Hoster.messages.networkOrQueryError.title", @"") stringByReplacingOccurrencesOfString:@"%hoster%" withString:[[result loadedByHoster] name]]; NSString *detail = [NSLocalizedString(@"Hoster.messages.networkOrQueryError.detail", @"") stringByReplacingOccurrencesOfString:@"%hoster%" withString:[[result loadedByHoster] name]]; NSRunCriticalAlertPanel(title, detail, NSLocalizedString(@"OK", @""), nil, nil); NSString *noNetwork = NSLocalizedString(@"Hoster.text.noNetwork", @""); [songLabel setStringValue:noNetwork]; [artistLabel setStringValue:noNetwork]; [lyricsArea setString:noNetwork]; [lyricsArea setEditable:NO]; } [songLabel setStringValue:[lyrics name]]; [artistLabel setStringValue:[lyrics artist]]; [lyricsArea setString:[lyrics lyrics]]; [lyricsArea setEditable:YES]; } } selectedRow = index; [window update]; } -(void) shouldShowPreviewForCellRect: (NSRect) rect searchResult: (SearchResult *) result { if ([showPreviewCheckBox state] == NSOnState) { NSString *lyrics = [result preview]; if (lyrics) { rect.size.width = [resultsOutline frame].size.width; [previewTextArea setString:lyrics==nil?NSLocalizedString(@"Hoster.text.noPreviewAvailable", @""):lyrics]; [previewPopover showRelativeToRect:rect ofView:resultsOutline preferredEdge:NSMaxXEdge]; } } } - (IBAction)sendLyricsToiTunes:(id)sender { NSString *title = NSLocalizedString(@"iTunes.messages.sentToiTunes.title", @""); NSString *detail = NSLocalizedString(@"iTunes.messages.sentToiTunes.detail", @""); NSString *sendToCurrent = NSLocalizedString(@"iTunes.messages.sentToiTunes.sendToCurrent", @""); NSString *sendToMatching = NSLocalizedString(@"iTunes.messages.sentToiTunes.sendToMatching", @""); NSString *cancel = NSLocalizedString(@"Cancel", @""); NSBeginAlertSheet(title, sendToCurrent, cancel, sendToMatching, window, self, nil, @selector(sendLyricsToiTunesSheetDidDismiss:returnCode:contextInfo:), nil, detail); } - (void)sendLyricsToiTunesSheetDidDismiss: (NSWindow *) sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { if (returnCode == NSAlertDefaultReturn) { [self sendLyricsToCurrentiTunesTrack:nil]; } else if (returnCode == NSAlertOtherReturn) { [self sendLyricsToTracksMatchingTrack:nil]; } } - (IBAction)sendLyricsToCurrentiTunesTrack:(id)sender { iTunesTrack *track = [[ilyrics iTunes] currentTrack]; NSString *name = [track name]; if (name == nil) { NSBeginAlertSheet(NSLocalizedString(@"iTunes.messages.noTrackPlaying.title", @""), NSLocalizedString(@"OK", @""), nil, nil, window, nil, nil, nil, nil, NSLocalizedString(@"iTunes.messages.noTrackPlaying.detail", @"")); return; } NSString *oldLyrics = [track lyrics]; if (oldLyrics != nil && [oldLyrics length] > 0) { NSBeginAlertSheet(NSLocalizedString(@"iTunes.messages.replaceLyrics.title", @""), NSLocalizedString(@"Yes", @""), NSLocalizedString(@"No", @""), nil, window, self, @selector(replaceLyricsSheetDidEnd:returnCode:contextInfo:), nil, nil, NSLocalizedString(@"iTunes.messages.replaceLyrics.detail", @"")); } else { [self sendLyricsToiTunesTrack:[[ilyrics iTunes] currentTrack]]; } } - (IBAction)sendLyricsToTracksMatchingTrack:(id)sender { iTunesSource *iTunesSrc = [[[ilyrics iTunes] sources] objectAtIndex:0]; iTunesLibraryPlaylist *library = [[iTunesSrc libraryPlaylists] objectAtIndex:0]; NSString *name = [currentLyrics name]; NSString *artist = [currentLyrics artist]; BOOL foundTrack = NO; for (iTunesFileTrack *track in [library fileTracks]) { if ([[track name] rangeOfString:name options:NSCaseInsensitiveSearch].location != NSNotFound && [[track artist] rangeOfString:artist options:NSCaseInsensitiveSearch].location != NSNotFound) { [self sendLyricsToiTunesTrack:track]; foundTrack = YES; } } if (!foundTrack) { NSString *title = NSLocalizedString(@"iTunes.messages.noMatchingTracks.title", @""); NSString *detail = [[NSLocalizedString(@"iTunes.messages.noMatchingTracks.detail", @"") stringByReplacingOccurrencesOfString:@"%track%" withString:name] stringByReplacingOccurrencesOfString:@"%artist%" withString:artist]; NSString *ok = NSLocalizedString(@"OK", @""); NSBeginAlertSheet(title, ok, nil, nil, window, nil, nil, nil, nil, detail); } } - (void)replaceLyricsSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { if (returnCode == NSAlertDefaultReturn) { iTunesTrack *track = [[ilyrics iTunes] currentTrack]; [self sendLyricsToiTunesTrack:track]; } } - (void)sendLyricsToiTunesTrack:(iTunesTrack *)track { [track setLyrics:[lyricsArea string]]; [GrowlApplicationBridge notifyWithTitle:NSLocalizedString(@"Growl.messages.lyricsSent.title", @"") description:[NSString stringWithFormat:NSLocalizedString(@"Growl.messages.lyricsSent.detail", @""), [track name]] notificationName:@"Lyrics sent to iTunes" iconData:nil priority:0 isSticky:NO clickContext:nil]; } - (IBAction)downloadLyrics:(id)sender { NSSavePanel *savePanel = [NSSavePanel savePanel]; [savePanel setAllowedFileTypes:[NSArray arrayWithObject:@"txt"]]; [savePanel setAllowsOtherFileTypes:YES]; [savePanel setCanSelectHiddenExtension:YES]; [savePanel setExtensionHidden:YES]; [savePanel setNameFieldStringValue:[currentLyrics name]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(savePanelDidClose:) name:NSWindowDidEndSheetNotification object:window]; void (^handler) (NSInteger) = ^(NSInteger result) { selectedSavePanelButton = result; saveFile = [savePanel URL]; }; [savePanel beginSheetModalForWindow:window completionHandler:handler]; } - (IBAction)showLyricsInBrowser:(id)sender { int row = [resultsOutline clickedRow]; if (row < 0) { row = [resultsOutline selectedRow]; } SearchResult *result = [loadedResults objectAtIndex:row]; [[result loadedByHoster] showInBrowser:result]; } -(void) savePanelDidClose: (NSNotification *) notification{ [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidEndSheetNotification object:window]; if (selectedSavePanelButton == NSOKButton) { BOOL success = [[[lyricsArea string] dataUsingEncoding:NSUTF8StringEncoding] writeToURL:saveFile atomically:NO]; if (!success) { NSBeginAlertSheet(NSLocalizedString(@"iLyrics.messages.error.saveLyrics.title", @""), NSLocalizedString(@"OK", @""), nil, nil, window, nil, nil, nil, nil, NSLocalizedString(@"iLyrics.messages.error.saveLyrics.detail", @"")); } else { [GrowlApplicationBridge notifyWithTitle:NSLocalizedString(@"Growl.messages.lyricsSaved.title", @"") description:[NSString stringWithFormat:NSLocalizedString(@"Growl.messages.lyricsSaved.detail", @""), [saveFile path]] notificationName:@"Lyrics saved to File" iconData:nil priority:0 isSticky:NO clickContext:nil]; } } } -(Lyrics *)currentLyrics { return currentLyrics; } #pragma mark - #pragma mark Show the window - (IBAction)showiLyricsWindow:(id)sender { [window makeKeyAndOrderFront:sender]; } #pragma mark Split View Delegate /*-(CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { NSView *rightView = [[splitView subviews] objectAtIndex:1]; int width = [splitView frame].size.width; int maxWidth = width-[rightView frame].size.width; return maxWidth; }*/ /* -(CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex { int outlineMinWith = [[resultsOutline tableColumnWithIdentifier:@"song"] minWidth] + [[resultsOutline tableColumnWithIdentifier:@"artist"] minWidth]; int minWidth = outlineMinWith; return minWidth; } */ #pragma mark window delegate -(BOOL)validateMenuItem:(NSMenuItem *)menuItem { SEL action = [menuItem action]; if (action == @selector(loadNextResults:)) { BOOL hasMore = [[ilyrics hosterWithName:[menuItem title]] hasMoreResults]; return hasMore; } return [self validateUserInterfaceItem:menuItem]; } -(BOOL)validateToolbarItem:(NSToolbarItem *)theItem { return [self validateUserInterfaceItem:theItem]; } -(BOOL)validateUserInterfaceItem:(id)item { SEL action = [item action]; if (action == @selector(downloadLyrics:)) { return lyricsSelected; } if (action == @selector(sendLyricsToiTunes:)) { BOOL enabled = lyricsSelected && [[ilyrics iTunes] isRunning]; return enabled; } if (action == @selector(sendLyricsToCurrentiTunesTrack:)) { if (![[ilyrics iTunes] isRunning]) { return NO; } NSString *name = [[[ilyrics iTunes] currentTrack] name]; BOOL enabled = lyricsSelected && name != nil && [name length]>0; return enabled; } if (action == @selector(sendLyricsToMatchingiTunesTrack:)) { BOOL enabled = lyricsSelected && [[ilyrics iTunes] isRunning]; return enabled; } if (action == @selector(getCurrentiTunesSong:)) { return [[ilyrics iTunes] isRunning]; } if (action == @selector(getCurrentSpotifySong:)) { return [[ilyrics spotify] isRunning]; } if (action == @selector(showPreview:)) { return [resultsOutline clickedRow] >= 0; } if (action == @selector(showLyricsInBrowser:)) { if ([resultsOutline clickedRow] >= 0) { SearchResult *result = [loadedResults objectAtIndex:[resultsOutline clickedRow]]; return [[result loadedByHoster] canShowInBrowser:result];; } else if ([resultsOutline selectedRow] >= 0) { SearchResult *result = [loadedResults objectAtIndex:[resultsOutline selectedRow]]; return [[result loadedByHoster] canShowInBrowser:result];; } else { return NO; } } return [self respondsToSelector:[item action]]; } -(void)windowDidBecomeMain:(NSNotification *)notification { [iLyricsMenuItem setOffStateImage:nil]; [iLyricsMenuItem setState:NSOnState]; } -(void)windowDidResignMain:(NSNotification *)notification { [iLyricsMenuItem setOffStateImage:nil]; [iLyricsMenuItem setState:NSOffState]; } -(void)windowDidMiniaturize:(NSNotification *)notification { NSString *imgPath = [[NSBundle mainBundle] pathForImageResource:@"Diamond"]; NSImage *miniaturizedImage = [[NSImage alloc] initWithContentsOfFile:imgPath]; [iLyricsMenuItem setOffStateImage:miniaturizedImage]; [iLyricsMenuItem setState:NSOffState]; } -(void)saveToDefalts:(NSUserDefaults *)defaults { [defaults setBool:[showPreviewCheckBox state] == NSOnState forKey:@"Show preview"]; } -(void)loadFromDefaults:(NSUserDefaults *)defaults { [showPreviewCheckBox setState:[defaults boolForKey:@"Show preview"]?NSOnState:NSOffState]; } @end