Updated playlists to work with iTunes 5.
authorKent Sutherland <ksuther@ithinksw.com>
Mon, 12 Sep 2005 21:45:48 +0000 (21:45 +0000)
committerKent Sutherland <ksuther@ithinksw.com>
Mon, 12 Sep 2005 21:45:48 +0000 (21:45 +0000)
Added PlaylistNode class to handle the playlists menu, since it is nested now.
Support for nested folders in iTunes
Completely rewrote playlists methods. Podcasts menu item shows up in the proper place now too.
iTunes 5 orders sources differently than iTunes 4, as far as I can tell. Instead of doing weird indexing like iTunes 4 (1, 2, 3 (skipped if there's no CD), 4, 5), it counts linearly all the time. I need someone with iTunes 4 to be sure this still works in iTunes 4 though.

MenuController.m
MenuTunes.xcodeproj/project.pbxproj
PlaylistNode.h [new file with mode: 0644]
PlaylistNode.m [new file with mode: 0644]
iTunesRemote.h
iTunesRemote.m

index 7c618f5..382e6f2 100755 (executable)
@@ -10,6 +10,7 @@
 #import "MainController.h"
 #import "NetworkController.h"
 #import "ITMTRemote.h"
+#import "PlaylistNode.h"
 #import <ITFoundation/ITDebug.h>
 #import <ITKit/ITHotKeyCenter.h>
 #import <ITKit/ITHotKey.h>
@@ -24,6 +25,7 @@
 - (NSMenu *)eqMenu;
 - (NSMenu *)artistsMenu;
 - (NSMenu *)albumsMenu;
+- (void)playlistsMenuAux:(NSMenu *)menu node:(PlaylistNode *)node tagPrefix:(int)p;
 - (void)setKeyEquivalentForCode:(short)code andModifiers:(long)modifiers
         onItem:(id <NSMenuItem>)item;
 - (BOOL)iPodWithNameAutomaticallyUpdates:(NSString *)name;
     return playlistsMenu;
 }*/
 
+- (void)playlistsMenuAux:(NSMenu *)menu node:(PlaylistNode *)node tagPrefix:(int)p
+{
+       id <NSMenuItem> tempItem;
+       int i;
+       
+       for (i = 0; i < [[node children] count]; i++) {
+               PlaylistNode *nextNode = [[node children] objectAtIndex:i];
+               if ([nextNode type] == ITMTFolderNode) {
+                       NSMenu *submenu = [[NSMenu alloc] init];
+                       tempItem = [menu addItemWithTitle:[nextNode name] action:@selector(performPlaylistMenuAction:) keyEquivalent:@""];
+                       [tempItem setTag:p + [nextNode index] + 1];
+                       [tempItem setTarget:self];
+                       [tempItem setSubmenu:submenu];
+                       [self playlistsMenuAux:[submenu autorelease] node:nextNode tagPrefix:p];
+               } else {
+                       tempItem = [menu addItemWithTitle:[nextNode name] action:@selector(performPlaylistMenuAction:) keyEquivalent:@""];
+                       [tempItem setTag:p + [nextNode index] + 1];
+                       [tempItem setTarget:self];
+               }
+       }
+}
 
 - (NSMenu *)playlistsMenu
 {
     NSArray *playlists = nil;
     id <NSMenuItem> tempItem;
     ITMTRemotePlayerSource source = [[[MainController sharedController] currentRemote] currentSource];
-    int i, j;
+       int i;
     NSMutableArray *indices = [[NSMutableArray alloc] init];
     NS_DURING
         playlists = [[[MainController sharedController] currentRemote] playlists];
        NS_DURING
     ITDebugLog(@"Building \"Playlists\" menu.");
     {
-        NSArray *curPlaylist = [playlists objectAtIndex:0];
-        NSString *name = [curPlaylist objectAtIndex:0];
-        ITDebugLog(@"Adding main source: %@", name);
-        for (i = 3; i < [curPlaylist count]; i++) {
-            ITDebugLog(@"Adding playlist: %@", [curPlaylist objectAtIndex:i]);
-            tempItem = [playlistsMenu addItemWithTitle:[curPlaylist objectAtIndex:i] action:@selector(performPlaylistMenuAction:) keyEquivalent:@""];
-            [tempItem setTag:i - 1];
-            [tempItem setTarget:self];
-        }
+               //First we add the main Library source, since it is guaranteed to be there.
+        PlaylistNode *library = [playlists objectAtIndex:0];
+        ITDebugLog(@"Adding main source: %@", [library name]);
+               [self playlistsMenuAux:playlistsMenu node:library tagPrefix:0];
         ITDebugLog(@"Adding index to the index array.");
-        [indices addObject:[curPlaylist objectAtIndex:2]];
+        [indices addObject:[NSNumber numberWithInt:[library index]]];
     }
        
+       //Next go through the other sources
     if ([playlists count] > 1) {
-        if ([[[playlists objectAtIndex:1] objectAtIndex:1] intValue] == ITMTRemoteRadioSource) {
-            [indices addObject:[[playlists objectAtIndex:1] objectAtIndex:2]];
+               //Add the radio source if it is playing
+        if ([[playlists objectAtIndex:1] sourceType] == ITMTRemoteRadioSource) {
+            [indices addObject:[NSNumber numberWithInt:[[playlists objectAtIndex:1] index]]];
             if (source == ITMTRemoteRadioSource) {
                 [playlistsMenu addItem:[NSMenuItem separatorItem]];
                 [[playlistsMenu addItemWithTitle:NSLocalizedString(@"radio", @"Radio") action:@selector(performPlaylistMenuAction:) keyEquivalent:@""] setState:NSOnState];
         } else {
             [playlistsMenu addItem:[NSMenuItem separatorItem]];
         }
-    }
-       
-    if ([playlists count] > 1) {
-        for (i = 1; i < [playlists count]; i++) {
-            NSArray *curPlaylist = [playlists objectAtIndex:i];
-            if ([[curPlaylist objectAtIndex:1] intValue] != ITMTRemoteRadioSource) {
-                NSString *name = [curPlaylist objectAtIndex:0];
-                NSMenu *submenu = [[NSMenu alloc] init];
+               
+               //Add other sources as needed (shared music, iPods, CDs)
+        for (i = 2; i < [playlists count]; i++) {
+            PlaylistNode *nextSource = [playlists objectAtIndex:i];
+            if ([nextSource type] != ITMTRemoteRadioSource) {
+                NSString *name = [nextSource name];
                 ITDebugLog(@"Adding source: %@", name);
                 
-                if ( ([[curPlaylist objectAtIndex:1] intValue] == ITMTRemoteiPodSource) && [self iPodWithNameAutomaticallyUpdates:name] ) {
+                if ( ([nextSource type] == ITMTRemoteiPodSource) && [self iPodWithNameAutomaticallyUpdates:name] ) {
                     ITDebugLog(@"Invalid iPod source.");
                     [playlistsMenu addItemWithTitle:name action:NULL keyEquivalent:@""];
                 } else {
-                    for (j = 3; j < [curPlaylist count]; j++) {
-                        ITDebugLog(@"Adding playlist: %@", [curPlaylist objectAtIndex:j]);
-                        tempItem = [submenu addItemWithTitle:[curPlaylist objectAtIndex:j] action:@selector(performPlaylistMenuAction:) keyEquivalent:@""];
-                        [tempItem setTag:(i * 1000) + j - 1];
-                        [tempItem setTarget:self];
-                    }
-                    [[playlistsMenu addItemWithTitle:name action:NULL keyEquivalent:@""] setSubmenu:[submenu autorelease]];
+                                       NSMenu *menu = [[NSMenu alloc] init];
+                                       [[playlistsMenu addItemWithTitle:name action:NULL keyEquivalent:@""] setSubmenu:[menu autorelease]];
+                                       [self playlistsMenuAux:menu node:nextSource tagPrefix:(i * 1000)];
                 }
                 ITDebugLog(@"Adding index to the index array.");
-                [indices addObject:[curPlaylist objectAtIndex:2]];
+                [indices addObject:[NSNumber numberWithInt:[nextSource index]]];
             }
         }
     }
index 0903d53..685602f 100755 (executable)
@@ -7,6 +7,10 @@
        objects = {
 
 /* Begin PBXBuildFile section */
+               3739DDA308CFA0C600CCFBC6 /* PlaylistNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 3739DDA108CFA0C600CCFBC6 /* PlaylistNode.h */; };
+               3739DDA408CFA0C600CCFBC6 /* PlaylistNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3739DDA208CFA0C600CCFBC6 /* PlaylistNode.m */; };
+               3739DDA508CFA0C600CCFBC6 /* PlaylistNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 3739DDA108CFA0C600CCFBC6 /* PlaylistNode.h */; };
+               3739DDA608CFA0C600CCFBC6 /* PlaylistNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 3739DDA208CFA0C600CCFBC6 /* PlaylistNode.m */; };
                3740716D05ACE20500CC2142 /* libValidate.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3740716C05ACE20500CC2142 /* libValidate.a */; };
                37B7EA0406AECF0700A4DE86 /* ChasingArrow.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 37B7EA0306AECF0700A4DE86 /* ChasingArrow.tiff */; };
                37CB20230753EE1E00BB0E46 /* StatusWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C331FB305A922210088905B /* StatusWindowController.m */; };
                        remoteGlobalIDString = 8DC2EF5B0486A6940098B216;
                        remoteInfo = ITMac;
                };
-               7C505D460801F1F000017AF9 /* PBXContainerItemProxy */ = {
-                       isa = PBXContainerItemProxy;
-                       containerPortal = 7C505D2D0801F0D100017AF9 /* ITFoundation.xcode */;
-                       proxyType = 1;
-                       remoteGlobalIDString = 8DC2EF4F0486A6940098B216;
-                       remoteInfo = ITFoundation;
-               };
                7C505D480801F1F400017AF9 /* PBXContainerItemProxy */ = {
                        isa = PBXContainerItemProxy;
                        containerPortal = 7C505D290801F0C700017AF9 /* ITMac.xcode */;
                        remoteGlobalIDString = 8DC2EF4F0486A6940098B216;
                        remoteInfo = ITMac;
                };
-               7C505D4C0801F20500017AF9 /* PBXContainerItemProxy */ = {
-                       isa = PBXContainerItemProxy;
-                       containerPortal = 7C505D2D0801F0D100017AF9 /* ITFoundation.xcode */;
-                       proxyType = 1;
-                       remoteGlobalIDString = 8DC2EF4F0486A6940098B216;
-                       remoteInfo = ITFoundation;
-               };
                7C505D4E0801F20D00017AF9 /* PBXContainerItemProxy */ = {
                        isa = PBXContainerItemProxy;
                        containerPortal = 7C505D250801F0BE00017AF9 /* ITKit.xcode */;
                29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
                29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
                32CA4F630368D1EE00C91783 /* MenuTunes_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MenuTunes_Prefix.pch; sourceTree = "<group>"; };
+               3739DDA108CFA0C600CCFBC6 /* PlaylistNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaylistNode.h; sourceTree = "<group>"; };
+               3739DDA208CFA0C600CCFBC6 /* PlaylistNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaylistNode.m; sourceTree = "<group>"; };
                3740716C05ACE20500CC2142 /* libValidate.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libValidate.a; sourceTree = "<group>"; };
                37B7EA0306AECF0700A4DE86 /* ChasingArrow.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = ChasingArrow.tiff; sourceTree = "<group>"; };
                7C331F6805A918EC0088905B /* ITMTRemote.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ITMTRemote.framework; sourceTree = BUILT_PRODUCTS_DIR; };
                                7C331FBE05A922490088905B /* ITMTRemote.m */,
                                7C331FC205A9225A0088905B /* iTunesRemote.h */,
                                7C331FC105A9225A0088905B /* iTunesRemote.m */,
+                               3739DDA108CFA0C600CCFBC6 /* PlaylistNode.h */,
+                               3739DDA208CFA0C600CCFBC6 /* PlaylistNode.m */,
                        );
                        name = Classes;
                        sourceTree = "<group>";
                        buildActionMask = 2147483647;
                        files = (
                                7C331FC405A9225A0088905B /* iTunesRemote.h in Headers */,
+                               3739DDA508CFA0C600CCFBC6 /* PlaylistNode.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                7C331FBC05A922210088905B /* StatusWindowController.h in Headers */,
                                7C95A0CF05A9299E00B4F576 /* validate.h in Headers */,
                                7C95A0E405A929F400B4F576 /* CustomMenuTableView.h in Headers */,
+                               3739DDA308CFA0C600CCFBC6 /* PlaylistNode.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                WRAPPER_EXTENSION = remote;
                        };
                        dependencies = (
-                               7C505D470801F1F000017AF9 /* PBXTargetDependency */,
                                7C505D490801F1F400017AF9 /* PBXTargetDependency */,
                                7C331FF305A9253F0088905B /* PBXTargetDependency */,
                        );
                                WRAPPER_EXTENSION = app;
                        };
                        dependencies = (
-                               7C505D4D0801F20500017AF9 /* PBXTargetDependency */,
                                7C505D4B0801F20200017AF9 /* PBXTargetDependency */,
                                7C505D4F0801F20D00017AF9 /* PBXTargetDependency */,
                                7C331FF505A925550088905B /* PBXTargetDependency */,
                        buildActionMask = 2147483647;
                        files = (
                                7C331FC305A9225A0088905B /* iTunesRemote.m in Sources */,
+                               3739DDA608CFA0C600CCFBC6 /* PlaylistNode.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                7C331FBB05A922210088905B /* StatusWindow.m in Sources */,
                                7C95A0E305A929F400B4F576 /* CustomMenuTableView.m in Sources */,
                                37CB20230753EE1E00BB0E46 /* StatusWindowController.m in Sources */,
+                               3739DDA408CFA0C600CCFBC6 /* PlaylistNode.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        target = 7C331F6E05A918FB0088905B /* iTunesRemote */;
                        targetProxy = 7C331FF605A9255A0088905B /* PBXContainerItemProxy */;
                };
-               7C505D470801F1F000017AF9 /* PBXTargetDependency */ = {
-                       isa = PBXTargetDependency;
-                       name = "ITFoundation (from ITFoundation.xcode)";
-                       targetProxy = 7C505D460801F1F000017AF9 /* PBXContainerItemProxy */;
-               };
                7C505D490801F1F400017AF9 /* PBXTargetDependency */ = {
                        isa = PBXTargetDependency;
                        name = "ITMac (from ITMac.xcode)";
                        name = "ITMac (from ITMac.xcode)";
                        targetProxy = 7C505D4A0801F20200017AF9 /* PBXContainerItemProxy */;
                };
-               7C505D4D0801F20500017AF9 /* PBXTargetDependency */ = {
-                       isa = PBXTargetDependency;
-                       name = "ITFoundation (from ITFoundation.xcode)";
-                       targetProxy = 7C505D4C0801F20500017AF9 /* PBXContainerItemProxy */;
-               };
                7C505D4F0801F20D00017AF9 /* PBXTargetDependency */ = {
                        isa = PBXTargetDependency;
                        name = "ITKit (from ITKit.xcode)";
diff --git a/PlaylistNode.h b/PlaylistNode.h
new file mode 100644 (file)
index 0000000..6ca8c77
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *     MenuTunes
+ *  PlaylistNode
+ *    Helper class for keeping track of sources, playlists and folders
+ *
+ *  Original Author : Kent Sutherland <ksuther@ithinksw.com>
+ *   Responsibility : Kent Sutherland <ksuther@ithinksw.com>
+ *
+ *  Copyright (c) 2005 iThink Software.
+ *  All Rights Reserved
+ *
+ */
+#import <Cocoa/Cocoa.h>
+#import <ITMTRemote/ITMTRemote.h>
+
+typedef enum {
+    ITMTSourceNode = -1,
+       ITMTPlaylistNode,
+    ITMTFolderNode,
+       ITMTPartyShuffleNode,
+       ITMTPodcastsNode,
+       ITMTPurchasedMusicNode,
+       ITMTVideosNode
+} ITMTNodeType;
+
+@interface PlaylistNode : NSObject
+{
+       NSString *_name;
+       ITMTNodeType _type;
+       ITMTRemotePlayerSource _sourceType;
+       NSMutableArray *_children;
+       PlaylistNode *_parent;
+       int _index;
+}
++ (PlaylistNode *)playlistNodeWithName:(NSString *)n type:(ITMTNodeType)t index:(int)i;
+
+- (id)initWithName:(NSString *)n type:(ITMTNodeType)t index:(int)i;
+
+- (NSString *)name;
+- (NSMutableArray *)children;
+- (int)index;
+
+- (void)setType:(ITMTNodeType)t;
+- (ITMTNodeType)type;
+
+- (PlaylistNode *)parent;
+- (void)setParent:(PlaylistNode *)p;
+
+- (ITMTRemotePlayerSource)sourceType;
+- (void)setSourceType:(ITMTRemotePlayerSource)t;
+@end
diff --git a/PlaylistNode.m b/PlaylistNode.m
new file mode 100644 (file)
index 0000000..12ae00b
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ *     MenuTunes
+ *  PlaylistNode
+ *    Helper class for keeping track of sources, playlists and folders
+ *
+ *  Original Author : Kent Sutherland <ksuther@ithinksw.com>
+ *   Responsibility : Kent Sutherland <ksuther@ithinksw.com>
+ *
+ *  Copyright (c) 2005 iThink Software.
+ *  All Rights Reserved
+ *
+ */
+
+#import "PlaylistNode.h"
+
+
+@implementation PlaylistNode
+
++ (PlaylistNode *)playlistNodeWithName:(NSString *)n type:(ITMTNodeType)t index:(int)i
+{
+       return [[[PlaylistNode alloc] initWithName:n type:t index:i] autorelease];
+}
+
+- (id)initWithName:(NSString *)n type:(ITMTNodeType)t index:(int)i
+{
+       if ( (self = [super init]) ) {
+               _name = [n retain];
+               _type = t;
+               _index = i;
+               _children = [[NSMutableArray alloc] init];
+               _parent = nil;
+       }
+       return self;
+}
+
+- (void)dealloc
+{
+       [_name release];
+       [_children release];
+       [_parent release];
+       [super dealloc];
+}
+
+- (NSString *)description
+{
+       return [NSString stringWithFormat:@"{%@, index: %i, type: %i, parent: %@, children: %@}", _name, _index, _type, [_parent name], _children];
+}
+
+- (NSString *)name
+{
+       return _name;
+}
+
+- (NSMutableArray *)children
+{
+       return _children;
+}
+
+- (int)index
+{
+       return _index;
+}
+
+- (void)setType:(ITMTNodeType)t
+{
+       _type = t;
+}
+
+- (ITMTNodeType)type
+{
+       return _type;
+}
+
+- (PlaylistNode *)parent
+{
+       return _parent;
+}
+
+- (void)setParent:(PlaylistNode *)p
+{
+       [_parent release];
+       _parent = [p retain];
+}
+
+- (ITMTRemotePlayerSource)sourceType
+{
+       return _sourceType;
+}
+
+- (void)setSourceType:(ITMTRemotePlayerSource)t
+{
+       _sourceType = t;
+}
+
+@end
index 387c188..dbbe821 100755 (executable)
 #import <ITFoundation/ITFoundation.h>
 #import <ITMac/ITMac.h>
 
+@class PlaylistNode;
+
 @interface iTunesRemote : ITMTRemote <ITMTRemote>
 {
     ProcessSerialNumber savedPSN;
+       float _iTunesVersion;
 }
 - (BOOL)isPlaying;
 - (ProcessSerialNumber)iTunesPSN;
index 35768cd..ef53fc5 100755 (executable)
@@ -1,4 +1,5 @@
 #import "iTunesRemote.h"
+#import "PlaylistNode.h"
 
 @implementation iTunesRemote
 
     ITDebugLog(@"iTunesRemote begun");
     savedPSN = [self iTunesPSN];
        [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationHandler:) name:@"com.apple.iTunes.playerInfo" object:@"com.apple.iTunes.player"];
+       
+       NSString *iTunesPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"CustomPlayerPath"];
+       NSDictionary *iTunesInfoPlist;
+       float iTunesVersion;
+
+       //Check if iTunes 5.0 or later is installed     
+       if (!iTunesPath) {
+               iTunesPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:@"iTunes.app"];
+       }
+       iTunesInfoPlist = [[NSBundle bundleWithPath:iTunesPath] infoDictionary];
+       iTunesVersion = [[iTunesInfoPlist objectForKey:@"CFBundleVersion"] floatValue];
+       ITDebugLog(@"iTunes version found: %f.", iTunesVersion);
+       _iTunesVersion = iTunesVersion;
+       
     return YES;
 }
 
 }*/
 
 //Full source awareness
-- (NSArray *)playlists
+/*- (NSArray *)playlists
 {
     unsigned long i, k;
     SInt32 numSources = [ITSendAEWithString(@"kocl:type('cSrc'), '----':()", 'core', 'cnte', &savedPSN) int32Value];
             ITDebugLog(@"Source at index %i disappeared.", k);
         }
     }
+       NSLog(@"playlists: %@", allSources);
     ITDebugLog(@"Finished getting playlists.");
     return [allSources autorelease];
+}*/
+
+- (NSArray *)playlists
+{
+       SInt32 numSources = [ITSendAEWithString(@"kocl:type('cSrc'), '----':()", 'core', 'cnte', &savedPSN) int32Value];
+       NSMutableArray *sources = [[NSMutableArray alloc] init];
+       int i;
+       
+       ITDebugLog(@"Getting playlists.");
+    if (numSources == 0) {
+               [sources release];
+        ITDebugLog(@"No sources.");
+        return nil;
+    }
+       
+       //Loop through each source
+       for (i = 1; i <= numSources; i++) {
+        SInt32 fourcc = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pKnd'), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } }", i], 'core', 'getd', &savedPSN) int32Value]; //Type of the current source
+        NSString *sourceName = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } }", i], 'core', 'getd', &savedPSN) stringValue]; //Name of the current source
+        SInt32 index = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } }", i], 'core', 'getd', &savedPSN) int32Value]; //Index of the current source
+        ITMTRemotePlayerSource class; //The class of the current source
+               
+               //Make a new PlaylistNode for this source
+               PlaylistNode *sourceNode = [PlaylistNode playlistNodeWithName:sourceName type:ITMTSourceNode index:index];
+               
+               switch (fourcc) {
+                       case 'kTun':
+                               class = ITMTRemoteRadioSource;
+                               break;
+                       case 'kDev':
+                               class = ITMTRemoteGenericDeviceSource;
+                               break;
+                       case 'kPod':
+                               class = ITMTRemoteiPodSource;
+                               break;
+                       case 'kMCD':
+                       case 'kACD':
+                               class = ITMTRemoteCDSource;
+                               break;
+                       case 'kShd':
+                               class = ITMTRemoteSharedLibrarySource;
+                               break;
+                       case 'kUnk':
+                       case 'kLib':
+                       default:
+                               class = ITMTRemoteLibrarySource;
+                               break;
+               }
+               [sourceNode setSourceType:class];
+               ITDebugLog(@"New source %@ of type %i at index %i", sourceName, class, index);
+               
+               int j;
+               SInt32 numPlaylists = [ITSendAEWithString([NSString stringWithFormat:@"kocl:type('cPly'), '----':obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() }", i], 'core', 'cnte', &savedPSN) int32Value]; //Number of playlists in the current source
+               
+               //Pass 1, add all the playlists into the main array
+               for (j = 1; j <= numPlaylists; j++) {
+                       NSString *sendStr = [NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'indx', want:type('cPly'), seld:long(%i), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } } }", j, (_iTunesVersion >= 5) ? i : index];
+                       NSString *parentSendStr = [NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pnam'), from:obj { form:'prop', want:type('prop'), seld:type('pPlP'), from:obj { form:'indx', want:type('cPly'), seld:long(%i), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } } } }", j, i];
+                       NSString *theObj = [ITSendAEWithString(sendStr, 'core', 'getd', &savedPSN) stringValue], *parent = [ITSendAEWithString(parentSendStr, 'core', 'getd', &savedPSN) stringValue];
+                       ITDebugLog(@" - Adding playlist %@", theObj);
+                       if (theObj) {
+                               FourCharCode code = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pSpK'), from:obj { form:'indx', want:type('cPly'), seld:long(%i), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } } }", j, i], 'core', 'getd', &savedPSN) typeCodeValue];
+                               ITMTNodeType type;
+                               switch (code) {
+                                       case 'kSpN':
+                                               type = ITMTPlaylistNode;
+                                               break;
+                                       case 'kSpF':
+                                               type = ITMTFolderNode;
+                                               break;
+                                       case 'kSpS':
+                                               type = ITMTPartyShuffleNode;
+                                               break;
+                                       case 'kSpP':
+                                               type = ITMTPodcastsNode;
+                                               break;
+                                       case 'kSpM':
+                                               type = ITMTPurchasedMusicNode;
+                                               break;
+                                       case 'kSpV':
+                                               type = ITMTVideosNode;
+                                               break;
+                               }
+                               PlaylistNode *node = [PlaylistNode playlistNodeWithName:theObj type:type index:j];
+                               [[sourceNode children] addObject:node];
+                               if (parent) {
+                                       int parentIndex = [ITSendAEWithString([NSString stringWithFormat:@"'----':obj { form:'prop', want:type('prop'), seld:type('pidx'), from:obj { form:'prop', want:type('prop'), seld:type('pPlP'), from:obj { form:'indx', want:type('cPly'), seld:long(%i), from:obj { form:'indx', want:type('cSrc'), seld:long(%i), from:() } } } }", j, i], 'core', 'getd', &savedPSN) int32Value];
+                                       [node setParent:[PlaylistNode playlistNodeWithName:parent type:ITMTFolderNode index:parentIndex]];
+                               } else {
+                                       [node setParent:sourceNode];
+                               }
+                       }
+               }
+               
+               //Pass 2, nest each item under its proper parent. Once everything has been nested, delete the original from the main array.
+               NSEnumerator *enumerator = [[sourceNode children] objectEnumerator];
+               PlaylistNode *nextNode;
+               NSMutableArray *nested = [[NSMutableArray alloc] init];
+               
+               while ( (nextNode = [enumerator nextObject]) ) {
+                       PlaylistNode *pNode = [nextNode parent];
+                       if ([pNode type] == ITMTFolderNode) {
+                               PlaylistNode *newParent = nil;
+                               int k;
+                               for (k = 0; !newParent; k++) {
+                                       PlaylistNode *test = [[sourceNode children] objectAtIndex:k];
+                                       if ([test index] == [pNode index]) {
+                                               newParent = test;
+                                       }
+                               }
+                               [nextNode setParent:newParent];
+                               [[newParent children] addObject:nextNode];
+                               [newParent setType:ITMTFolderNode];
+                               [nested addObject:nextNode];
+                       }
+               }
+               
+               NSEnumerator *nestEnumerator = [nested objectEnumerator];
+               while ( (nextNode = [nestEnumerator nextObject]) ) {
+                       [[sourceNode children] removeObject:nextNode];
+               }
+               [nested release];
+               
+               //Move all the folders to the beginning of the list
+               //Move the podcasts playlist to the top
+               BOOL movedPodcasts = NO;
+               enumerator = [[sourceNode children] reverseObjectEnumerator];
+               while ( (nextNode = [enumerator nextObject]) ) {
+                       if ([nextNode type] == ITMTPodcastsNode) {
+                               [nextNode retain];
+                               [[sourceNode children] removeObject:nextNode];
+                               [[sourceNode children] insertObject:nextNode atIndex:1];
+                               movedPodcasts = YES;
+                       } else if ([nextNode type] == ITMTFolderNode) {
+                               [nextNode retain];
+                               [[sourceNode children] removeObject:nextNode];
+                               [[sourceNode children] insertObject:nextNode atIndex:1 + movedPodcasts];
+                       }
+               }
+               
+               [sources addObject:sourceNode];
+       }
+       
+       return [sources autorelease];
 }
 
 - (NSArray *)artists
 {
        int result;
     ITDebugLog(@"Getting shuffle enabled status.");
-       if ([[self playerStateUniqueIdentifier] isEqualToString:@"0-0"]) {
+       if (![self isPlaying]) {
                ITDebugLog(@"No current playlist, getting shuffle status from visible playlist.");
                result = (int)[ITSendAEWithString(@"'----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPly'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } } }", 'core', 'getd', &savedPSN) int32Value];
        } else {
 - (BOOL)setShuffleEnabled:(BOOL)enabled
 {
     ITDebugLog(@"Set shuffle enabled to %i", enabled);
-       if ([[self playerStateUniqueIdentifier] isEqualToString:@"0-0"]) {
+       if (![self isPlaying]) {
                ITDebugLog(@"No current playlist, setting shuffle status on visible playlist.");
                ITSendAEWithString([NSString stringWithFormat:@"data:long(%lu), '----':obj { form:'prop', want:type('prop'), seld:type('pShf'), from:obj { form:'prop', want:type('prop'), seld:type('pPly'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } } }", (unsigned long)enabled], 'core', 'setd', &savedPSN);
        } else {
             m00f = "kRp0";
             break;
     }
-       if ([[self playerStateUniqueIdentifier] isEqualToString:@"0-0"]) {
+       if (![self isPlaying]) {
                ITDebugLog(@"No current playlist, setting repeat mode on visible playlist.");
                ITSendAEWithString([NSString stringWithFormat:@"data:'%s', '----':obj { form:'prop', want:type('prop'), seld:type('pRpt'), from:obj { form:'prop', want:type('prop'), seld:type('pPly'), from:obj { form:'indx', want:type('cBrW'), seld:1, from:'null'() } } }", m00f], 'core', 'setd', &savedPSN);
        } else {