Archived
1
This repository has been archived on 2022-08-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
ilyrics/iLyrics/MainController.m
Kim Wittenburg 6a132a51d0 -Fixed a search bug: Returned false results when having a & in query
-Added Functionality: Send Lyrics to matching iTunes Tracks
2012-06-26 11:17:05 +02:00

479 lines
20 KiB
Objective-C

//
// 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<LyricsHoster> 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<LyricsHoster> hoster in [ilyrics lyricsHosters]) {
[hoster startNewSearchForQuery:[searchField stringValue]];
}
//Load first results
BOOL networkError = YES;
BOOL resultsFound = NO;
for (id<LyricsHoster> 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<LyricsHoster>) 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<LyricsHoster>)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<LyricsHoster>)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<LyricsHoster> 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<NSValidatedUserInterfaceItem>)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