diff --git a/English.lproj/MainMenu.nib/classes.nib b/English.lproj/MainMenu.nib/classes.nib index 5b14d8e..e9cb252 100644 --- a/English.lproj/MainMenu.nib/classes.nib +++ b/English.lproj/MainMenu.nib/classes.nib @@ -219,8 +219,14 @@ ObjC OUTLETS + _controlsMenuItem + NSMenuItem _fileMenu NSMenu + _outputDevices + NSMenuItem + _outputDevicesSeperator + NSMenuItem _profileChooser NSPopUpButton mAuthenticateButton diff --git a/English.lproj/MainMenu.nib/info.nib b/English.lproj/MainMenu.nib/info.nib index 8ad0581..f7283b5 100644 --- a/English.lproj/MainMenu.nib/info.nib +++ b/English.lproj/MainMenu.nib/info.nib @@ -10,6 +10,7 @@ 5 IBOpenObjects + 29 344 IBSystem Version diff --git a/English.lproj/MainMenu.nib/keyedobjects.nib b/English.lproj/MainMenu.nib/keyedobjects.nib index 9435948..8807da1 100644 Binary files a/English.lproj/MainMenu.nib/keyedobjects.nib and b/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/Source/MusicServer/MpdMusicServerClient.m b/Source/MusicServer/MpdMusicServerClient.m index 4b1544f..7dc2a82 100644 --- a/Source/MusicServer/MpdMusicServerClient.m +++ b/Source/MusicServer/MpdMusicServerClient.m @@ -41,7 +41,7 @@ static void MpdClientConnectionChangedCallback(MpdObj *mi, int connect, void *us @implementation MpdMusicServerClient + (unsigned int) capabilities { - return eMusicClientCapabilitiesRandomizePlaylist; + return eMusicClientCapabilitiesRandomizePlaylist | eMusicClientCapabilitiesOutputDevices; } - (id) init { @@ -517,4 +517,21 @@ - (oneway void) playerWindowClosed { [self resetDelayForStatusUpdateTimer:5]; } +- (bycopy NSArray*) getOutputDevices { + MpdData *data = mpd_server_get_output_devices(mConnection); + NSMutableArray *results = [NSMutableArray array]; + for (; data != NULL; data = mpd_data_get_next(data)) { + [results addObject:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:data->output_dev->enabled == 1 ? YES : NO], dEnabled, + [NSString stringWithUTF8String:data->output_dev->name], dName, + [NSNumber numberWithInt:data->output_dev->id], dId, nil]]; + } + + return results; +} + +- (void) setOutputDeviceWithId:(int)theId toEnabled:(BOOL)enabled { + mpd_server_set_output_device(mConnection, theId, enabled == YES ? 1 : 0); +} + @end diff --git a/Source/MusicServer/MusicServerClient.h b/Source/MusicServer/MusicServerClient.h index ef233ae..b3d74b5 100644 --- a/Source/MusicServer/MusicServerClient.h +++ b/Source/MusicServer/MusicServerClient.h @@ -68,6 +68,10 @@ extern NSString *dTotalTime; extern NSString *dElapsedTime; extern NSString *dPlaylistLength; +extern NSString *dId; +extern NSString *dName; +extern NSString *dEnabled; + typedef enum { eStatePaused, eStateStopped, @@ -82,6 +86,7 @@ typedef enum { typedef enum { eMusicClientCapabilitiesRandomizePlaylist = 0x0001, + eMusicClientCapabilitiesOutputDevices = 0x0002 } MusicClientCapabilities; @protocol MusicServerClientInterface @@ -147,6 +152,9 @@ typedef enum { // needed for randomzing - (oneway void) swapSongs:(bycopy Song *)srcSong with:(bycopy Song *)destSong; + +- (bycopy NSArray*) getOutputDevices; +- (void) setOutputDeviceWithId:(int)theId toEnabled:(BOOL)enabled; @end @interface MusicServerClient : NSObject { diff --git a/Source/MusicServer/MusicServerClient.m b/Source/MusicServer/MusicServerClient.m index 9654de2..3fe8c84 100644 --- a/Source/MusicServer/MusicServerClient.m +++ b/Source/MusicServer/MusicServerClient.m @@ -63,6 +63,10 @@ NSString *dElapsedTime= @"dElapsedTime"; NSString *dPlaylistLength = @"dPlaylistLength"; +NSString *dId = @"dId"; +NSString *dName = @"dName"; +NSString *dEnabled = @"dEnabled"; + @implementation MusicServerClient + (Class) musicServerClientClassForProfile:(Profile *)aProfile { switch ([aProfile mode]) { diff --git a/Source/OutputDevices/OutputDeviceHandler.h b/Source/OutputDevices/OutputDeviceHandler.h new file mode 100644 index 0000000..4c99a47 --- /dev/null +++ b/Source/OutputDevices/OutputDeviceHandler.h @@ -0,0 +1,27 @@ +/* + Copyright (C) 2006-2007 Patrik Weiskircher + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + */ + +#import + + +@interface OutputDeviceHandler : NSObject { + NSMenu *_menu; +} +- (id) initWithMenu:(NSMenu *)theMenu; +@end diff --git a/Source/OutputDevices/OutputDeviceHandler.m b/Source/OutputDevices/OutputDeviceHandler.m new file mode 100644 index 0000000..576a55e --- /dev/null +++ b/Source/OutputDevices/OutputDeviceHandler.m @@ -0,0 +1,103 @@ +// +// OutputDeviceHandler.m +// Theremin +// +// Created by Patrik Weiskircher on 18.05.09. +// Copyright 2009 __MyCompanyName__. All rights reserved. +// + +#import "OutputDeviceHandler.h" +#import "WindowController.h" +#import "PreferencesController.h" + +@interface OutputDeviceHandler (PrivateMethods) +- (void) discoverOutputDevices; +- (void) removeMenuItems; +@end + +const static int menuTagOutputDevices = 123411; +const static int menuTagOutputSeperator = 123412; + +@implementation OutputDeviceHandler +- (id) initWithMenu:(NSMenu *)theMenu { + self = [super init]; + if (self != nil) { + _menu = [theMenu retain]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(connected:) + name:(NSString *)nMusicServerClientConnected + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(disconnected:) + name:(NSString *)nMusicServerClientDisconnected + object:nil]; + + } + return self; +} + +- (void) dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [_menu release]; + [super dealloc]; +} + +- (void) connected:(NSNotification *)theNotification { + if (!([[MusicServerClient musicServerClientClassForProfile:[[[WindowController instance] preferences] currentProfile]] capabilities] & eMusicClientCapabilitiesOutputDevices)) { + [self removeMenuItems]; + } else { + [self discoverOutputDevices]; + } +} + +- (void) disconnected:(NSNotification *)theNotification { + [self removeMenuItems]; +} + +- (void) removeMenuItems { + NSMenuItem *item = [_menu itemWithTag:menuTagOutputDevices]; + if (item != nil) + [_menu removeItemAtIndex:[_menu indexOfItem:item]]; + + item = [_menu itemWithTag:menuTagOutputSeperator]; + if (item != nil) + [_menu removeItemAtIndex:[_menu indexOfItem:item]]; +} + +- (void) discoverOutputDevices { + [self removeMenuItems]; + + NSMenuItem *seperatorItem = [NSMenuItem separatorItem]; + [seperatorItem setTag:menuTagOutputSeperator]; + [_menu addItem:seperatorItem]; + + NSMenuItem *outputDeviceItem = [[[NSMenuItem alloc] initWithTitle:@"Output Devices" action:nil keyEquivalent:@""] autorelease]; + [outputDeviceItem setSubmenu:[[[NSMenu alloc] init] autorelease]]; + [outputDeviceItem setTag:menuTagOutputDevices]; + [_menu addItem:outputDeviceItem]; + + NSArray *outputDevices = [[[WindowController instance] musicClient] getOutputDevices]; + for (int i = 0; i < [outputDevices count]; i++) { + NSDictionary *outputDevice = [outputDevices objectAtIndex:i]; + NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:[outputDevice objectForKey:dName] + action:@selector(toggleOutputDevice:) + keyEquivalent:@""] autorelease]; + [item setTarget:self]; + [item setTag:[[outputDevice objectForKey:dId] intValue]]; + [item setState:[[outputDevice objectForKey:dEnabled] boolValue] == YES ? NSOnState : NSOffState]; + + [[outputDeviceItem submenu] addItem:item]; + } +} + +- (void) toggleOutputDevice:(NSMenuItem *)sender { + BOOL toggleValue = [sender state] == NSOnState ? NO : YES; + + [[[WindowController instance] musicClient] setOutputDeviceWithId:[sender tag] toEnabled:toggleValue]; + + [sender setState:toggleValue == YES ? NSOnState : NSOffState]; +} + +@end diff --git a/Source/WindowController.h b/Source/WindowController.h index 10f9971..74ac272 100644 --- a/Source/WindowController.h +++ b/Source/WindowController.h @@ -27,6 +27,7 @@ @class LicenseController, PWVolumeSlider, LibraryController, PWTableView, PWMusicSearchField; @class InfoAreaController, UpdateDatabaseController, UnifiedToolbar, UnifiedToolbarItem; @class PlayListFilesController, SongInfoController; +@class OutputDeviceHandler; extern NSString *tPlayControlItemIdentifier; extern NSString *tStopItemIdentifier; @@ -94,6 +95,9 @@ extern const NSString *dProfile; RemoteControl *mAppleRemote; PreferencesWindowController *preferencesWindowController; + + OutputDeviceHandler *_outputDeviceHandler; + IBOutlet NSMenuItem *_controlsMenuItem; } + (id) instance; diff --git a/Source/WindowController.m b/Source/WindowController.m index 30515b9..3f78d34 100644 --- a/Source/WindowController.m +++ b/Source/WindowController.m @@ -53,6 +53,7 @@ #import "ProfileMenuItem.h" #import "ProfileRepository.h" #import "PreferencesWindowController.h" +#import "OutputDeviceHandler.h" WindowController *globalWindowController = nil; @@ -268,6 +269,8 @@ - (void) awakeFromNib { if ([[self preferences] appleRemoteMode] == eAppleRemoteModeAlways) [[self appleRemote] startListening:self]; + + _outputDeviceHandler = [[OutputDeviceHandler alloc] initWithMenu:[_controlsMenuItem submenu]]; } - (void) dealloc { @@ -282,6 +285,8 @@ - (void) dealloc { [mPlayListFilesController release], mPlayListFilesController = nil; [mPreferencesController release]; + [_outputDeviceHandler release]; + [[self appleRemote] stopListening:self]; [super dealloc]; @@ -484,7 +489,7 @@ - (BOOL)validateMenuItem:(NSMenuItem *)item { else if ([item action] == @selector(detectCompilations:)) return connected && [[self currentLibraryDataSource] supportsDataSourceCapabilities] & eLibraryDataSourceSupportsCustomCompilations; else if ([item action] == @selector(randomizePlaylist:)) - return connected && [[MusicServerClient musicServerClientClassForProfile:[[self preferences] currentProfile]] capabilities] == eMusicClientCapabilitiesRandomizePlaylist; + return connected && [[MusicServerClient musicServerClientClassForProfile:[[self preferences] currentProfile]] capabilities] && eMusicClientCapabilitiesRandomizePlaylist; return [item isEnabled]; } diff --git a/Theremin.xcodeproj/project.pbxproj b/Theremin.xcodeproj/project.pbxproj index 50f59c2..04362ec 100644 --- a/Theremin.xcodeproj/project.pbxproj +++ b/Theremin.xcodeproj/project.pbxproj @@ -110,6 +110,7 @@ 43BF777A0BB52CCD00715336 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 43BF77720BB52CCD00715336 /* MainMenu.nib */; }; 43BF777B0BB52CCD00715336 /* PlayListFiles.nib in Resources */ = {isa = PBXBuildFile; fileRef = 43BF77740BB52CCD00715336 /* PlayListFiles.nib */; }; 43BF777C0BB52CCD00715336 /* UpdateDatabase.nib in Resources */ = {isa = PBXBuildFile; fileRef = 43BF77760BB52CCD00715336 /* UpdateDatabase.nib */; }; + 43CA16D80FC1E44200E14FE7 /* OutputDeviceHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 43CA16D70FC1E44200E14FE7 /* OutputDeviceHandler.m */; }; 43D0126D0B8B66BA00711A1B /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D0126C0B8B66BA00711A1B /* IOKit.framework */; }; 43D0C8B70C13F25900940B39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 43D0C8B50C13F25900940B39 /* Localizable.strings */; }; 43D254AA0B4D80F200660DD9 /* MPSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 43D254A90B4D80F200660DD9 /* MPSlider.m */; }; @@ -402,6 +403,8 @@ 43BF77750BB52CCD00715336 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/PlayListFiles.nib; sourceTree = ""; }; 43BF77770BB52CCD00715336 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/UpdateDatabase.nib; sourceTree = ""; }; 43C45DB10F09064300911191 /* ThereminEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ThereminEntity.h; path = Source/MusicServer/ThereminEntity.h; sourceTree = ""; }; + 43CA16D60FC1E44200E14FE7 /* OutputDeviceHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OutputDeviceHandler.h; sourceTree = ""; }; + 43CA16D70FC1E44200E14FE7 /* OutputDeviceHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OutputDeviceHandler.m; sourceTree = ""; }; 43D0126C0B8B66BA00711A1B /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = ""; }; 43D0C8B60C13F25900940B39 /* English */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/Localizable.strings; sourceTree = ""; }; 43D2549B0B4D80B900660DD9 /* MPSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MPSlider.h; path = Source/subclasses/MPSlider.h; sourceTree = ""; }; @@ -506,6 +509,7 @@ 080E96DDFE201D6D7F000001 /* Classes */ = { isa = PBXGroup; children = ( + 43CA16D50FC1E42800E14FE7 /* OutputDevices */, 431CCBAE0FBB5F640042E376 /* CoverArt */, 436D30ED0FB72AA30063DC8D /* Growl */, 436DB37B0E52589C00907E0F /* Preferences */, @@ -867,6 +871,16 @@ name = Categories; sourceTree = ""; }; + 43CA16D50FC1E42800E14FE7 /* OutputDevices */ = { + isa = PBXGroup; + children = ( + 43CA16D60FC1E44200E14FE7 /* OutputDeviceHandler.h */, + 43CA16D70FC1E44200E14FE7 /* OutputDeviceHandler.m */, + ); + name = OutputDevices; + path = Source/OutputDevices; + sourceTree = ""; + }; 43CD56E40B354EBE00C42092 /* libmpdclient */ = { isa = PBXGroup; children = ( @@ -1312,6 +1326,7 @@ 431CCC2F0FBB6A850042E376 /* ImageDownloader.m in Sources */, 431CCC420FBB6E060042E376 /* CoverArtImageView.m in Sources */, 43156E050FBF097E00EF3ECA /* CoverArtAsker.m in Sources */, + 43CA16D80FC1E44200E14FE7 /* OutputDeviceHandler.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };