diff --git a/AppController.h b/AppController.h index 055c31295..94b3fad25 100644 --- a/AppController.h +++ b/AppController.h @@ -3,6 +3,7 @@ #import #import "PlaylistController.h" +#import "FileTreeController.h" @interface AppController : NSObject { @@ -16,23 +17,30 @@ IBOutlet NSButton *addButton; IBOutlet NSButton *remButton; IBOutlet NSButton *infoButton; + IBOutlet NSButton *fileButton; IBOutlet NSButton *shuffleButton; IBOutlet NSButton *repeatButton; IBOutlet NSDrawer *infoDrawer; + IBOutlet NSDrawer *fileDrawer; + + IBOutlet FileTreeController *fileTreeController; } -- (IBAction)addFiles:(id)sender; +- (IBAction)openFiles:(id)sender; - (IBAction)delEntries:(id)sender; - (IBAction)savePlaylist:(id)sender; - (IBAction)savePlaylistAs:(id)sender; - (IBAction)loadPlaylist:(id)sender; +- (IBAction)addFiles:(id)sender; + - (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo; - (IBAction)donate:(id)sender; - (IBAction)toggleInfoDrawer:(id)sender; +- (IBAction)toggleFileDrawer:(id)sender; - (void)drawerDidOpen:(NSNotification *)notification; - (void)drawerDidClose:(NSNotification *)notification; @@ -41,4 +49,7 @@ - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; - (void)application:(NSApplication *)theApplication openFiles:(NSArray *)filenames; +- (void)registerHotKeys; +OSStatus handleHotKey(EventHandlerCallRef nextHandler,EventRef theEvent,void *userData); + @end diff --git a/AppController.m b/AppController.m index c603a2551..c165f86c7 100644 --- a/AppController.m +++ b/AppController.m @@ -2,7 +2,7 @@ @implementation AppController -- (IBAction)addFiles:(id)sender +- (IBAction)openFiles:(id)sender { NSOpenPanel *p; @@ -11,24 +11,24 @@ [p setCanChooseDirectories:YES]; [p setAllowsMultipleSelection:YES]; - // [p beginSheetForDirectory:nil file:nil types:[listController acceptableFileTypes] modalForWindow:mainWindow modalDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; - // [p beginForDirectory:nil file:nil types:[playlistController acceptableFileTypes] modelessDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; + [p beginSheetForDirectory:nil file:nil types:[playlistController acceptableFileTypes] modalForWindow:mainWindow modalDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; + [p beginForDirectory:nil file:nil types:[playlistController acceptableFileTypes] modelessDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; - if ([p runModalForTypes:[playlistController acceptableFileTypes]] == NSOKButton) +/* if ([p runModalForTypes:[playlistController acceptableFileTypes]] == NSOKButton) { - [playlistController addPaths:[p filenames] sort:NO]; + [playlistController addPaths:[p filenames] sort:YES]; } - +*/ } - (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo { if (returnCode == NSOKButton) { - [playlistController addPaths:[panel filenames] sort:NO]; + [playlistController addPaths:[panel filenames] sort:YES]; } - [panel release]; +// [panel release]; } - (IBAction)delEntries:(id)sender @@ -36,6 +36,21 @@ [playlistController remove:self]; } +- (IBAction)addFiles:(id)sender +{ + NSMutableArray *paths = [[NSMutableArray alloc] init]; + NSArray *nodes = [fileTreeController selectedObjects]; + NSEnumerator *e = [nodes objectEnumerator]; + + id n; + while (n = [e nextObject]) { + [paths addObject:[n path]]; + } + + [playlistController addPaths:paths sort:YES]; + [paths release]; +} + - (PlaylistEntry *)currentEntry { return [playlistController currentEntry]; @@ -49,6 +64,8 @@ - (void)awakeFromNib { + [self initDefaults]; + // DBLog(@"AWAKe"); [playButton setToolTip:NSLocalizedString(@"PlayButtonTooltip", @"")]; [stopButton setToolTip:NSLocalizedString(@"StopButtonTooltip", @"")]; @@ -60,6 +77,8 @@ [shuffleButton setToolTip:NSLocalizedString(@"ShuffleButtonTooltip", @"")]; [repeatButton setToolTip:NSLocalizedString(@"RepeatButtonTooltip", @"")]; + [self registerHotKeys]; + NSString *filename = @"~/Library/Application Support/Cog/Default.playlist"; [playlistController loadPlaylist:[filename stringByExpandingTildeInPath]]; } @@ -156,14 +175,27 @@ [infoDrawer toggle:self]; } +- (IBAction)toggleFileDrawer:(id)sender +{ + [mainWindow makeKeyAndOrderFront:self]; + + [fileDrawer toggle:self]; +} + - (void)drawerDidOpen:(NSNotification *)notification { - [infoButton setState:NSOnState]; + if ([notification object] == infoDrawer) + [infoButton setState:NSOnState]; + else if ([notification object] == fileDrawer) + [fileButton setState:NSOnState]; } - (void)drawerDidClose:(NSNotification *)notification { - [infoButton setState:NSOffState]; + if ([notification object] == infoDrawer) + [infoButton setState:NSOffState]; + else if ([notification object] == fileDrawer) + [fileButton setState:NSOffState]; } - (IBAction)donate:(id)sender @@ -171,4 +203,99 @@ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://sourceforge.net/project/project_donations.php?group_id=140003"]]; } +- (void)initDefaults +{ + NSMutableDictionary *userDefaultsValuesDict = [NSMutableDictionary dictionary]; + [userDefaultsValuesDict setObject:[NSNumber numberWithInt:35] forKey:@"hotkeyCodePlay"]; + [userDefaultsValuesDict setObject:[NSNumber numberWithInt:controlKey+cmdKey] forKey:@"hotkeyModifiersPlay"]; + + [userDefaultsValuesDict setObject:[NSNumber numberWithInt:45] forKey:@"hotkeyCodeNext"]; + [userDefaultsValuesDict setObject:[NSNumber numberWithInt:controlKey+cmdKey] forKey:@"hotkeyModifiersNext"]; + + [userDefaultsValuesDict setObject:[NSNumber numberWithInt:15] forKey:@"hotkeyCodePrevious"]; + [userDefaultsValuesDict setObject:[NSNumber numberWithInt:controlKey+cmdKey] forKey:@"hotkeyModifiersPrevious"]; + + //Register and sync defaults + [[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +//Register the Hotkeys. Added by Chris Henderson, 21 May 2006 +//See http://www.dbachrach.com/blog/2005/11/program-global-hotkeys-in-cocoa-easily.html +- (void)registerHotKeys +{ + EventHotKeyRef gMyHotKeyRef; + EventHotKeyID gMyHotKeyID; + EventTypeSpec eventType; + eventType.eventClass=kEventClassKeyboard; + eventType.eventKind=kEventHotKeyPressed; + InstallApplicationEventHandler(&handleHotKey,1,&eventType,self,NULL); + //Play + gMyHotKeyID.signature='htk1'; + gMyHotKeyID.id=1; + if([[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyCodePlay"]!=-999) + { + RegisterEventHotKey([[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyCodePlay"], [[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyModifiersPlay"], gMyHotKeyID, GetApplicationEventTarget(), 0, &gMyHotKeyRef); + } + //Previous + gMyHotKeyID.signature='htk2'; + gMyHotKeyID.id=2; + if([[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyCodePrevious"]!=-999) + { + NSLog(@"REGISTERING: %i", [[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyCodePrevious"]); + RegisterEventHotKey([[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyCodePrevious"], [[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyModifiersPrevious"], gMyHotKeyID, GetApplicationEventTarget(), 0, &gMyHotKeyRef); + } + //Next + gMyHotKeyID.signature='htk3'; + gMyHotKeyID.id=3; + if([[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyCodeNext"]!=-999) + { + RegisterEventHotKey([[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyCodeNext"], [[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyModifiersNext"], gMyHotKeyID, GetApplicationEventTarget(), 0, &gMyHotKeyRef); + } +} + +//Handle the Hotkeys. Added by Chris Henderson, 21 May 2006 +OSStatus handleHotKey(EventHandlerCallRef nextHandler,EventRef theEvent,void *userData) +{ + EventHotKeyID hkID; + GetEventParameter(theEvent,kEventParamDirectObject,typeEventHotKeyID,NULL,sizeof(hkID),NULL,&hkID); + int i = hkID.id; + + NSLog(@"Handling: %i", i); + switch (i) + { + case 1: [userData clickPlay]; + break; + case 2: [userData clickPrev]; + break; + case 3: [userData clickNext]; + break; + } + return noErr; +} + + +- (void)clickPlay +{ + [playButton performClick:nil]; +} + +- (void)clickPrev +{ + NSLog(@"PREV"); + [prevButton performClick:nil]; +} + +- (void)clickNext +{ + NSLog(@"NEXT"); + [nextButton performClick:nil]; +} + +- (void)clickStop +{ + [stopButton performClick:nil]; +} + + @end diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index e0db5a769..075bd283e 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ 8E1849C90A43DB730084C69D /* MADFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E1849C70A43DB730084C69D /* MADFile.m */; }; 8E4C7F090A0509FC003BE25F /* DragScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E4C7F070A0509FC003BE25F /* DragScrollView.m */; }; 8E4CAB5B0A32251B00214C1D /* ShnFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8E4CAB5A0A32251B00214C1D /* ShnFile.mm */; }; + 8E4E7C1A0AA1ED4500D11405 /* file_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E4E7C180AA1ED4500D11405 /* file_blue.png */; }; + 8E4E7C1B0AA1ED4500D11405 /* file_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E4E7C190AA1ED4500D11405 /* file_gray.png */; }; 8E53E8610A44C11B007E5BCE /* ID3Tag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E53E8600A44C11B007E5BCE /* ID3Tag.framework */; }; 8E53E8690A44C121007E5BCE /* ID3Tag.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8E53E8600A44C11B007E5BCE /* ID3Tag.framework */; }; 8E6A8E2C0A0D8A68002ABE9C /* CoreAudioFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E6A8E280A0D8A68002ABE9C /* CoreAudioFile.m */; }; @@ -55,8 +57,6 @@ 8E75758B09F31D5A0080F1EE /* DBLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E75756409F31D5A0080F1EE /* DBLog.m */; }; 8E75758C09F31D5A0080F1EE /* Semaphore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E75756609F31D5A0080F1EE /* Semaphore.m */; }; 8E75758D09F31D5A0080F1EE /* VirtualRingBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E75756809F31D5A0080F1EE /* VirtualRingBuffer.m */; }; - 8E7575BC09F31D800080F1EE /* volume_high.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7575A409F31D800080F1EE /* volume_high.png */; }; - 8E7575BD09F31D800080F1EE /* volume_low.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7575A509F31D800080F1EE /* volume_low.png */; }; 8E7575BE09F31D800080F1EE /* wheel.icns in Resources */ = {isa = PBXBuildFile; fileRef = 8E7575A609F31D800080F1EE /* wheel.icns */; }; 8E7575CB09F31DCA0080F1EE /* Changelog in Resources */ = {isa = PBXBuildFile; fileRef = 8E7575C309F31DCA0080F1EE /* Changelog */; }; 8E7575CC09F31DCA0080F1EE /* Cog.scriptSuite in Resources */ = {isa = PBXBuildFile; fileRef = 8E7575C409F31DCA0080F1EE /* Cog.scriptSuite */; }; @@ -85,30 +85,56 @@ 8E757B5709F326710080F1EE /* OggFLAC.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8E75773809F31F1F0080F1EE /* OggFLAC.framework */; }; 8E757C7C09F32F070080F1EE /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E757C7A09F32F070080F1EE /* AudioToolbox.framework */; }; 8E757C7D09F32F070080F1EE /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E757C7B09F32F070080F1EE /* AudioUnit.framework */; }; + 8E7A0F1A0A8FEB4A00F27EE8 /* add_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F060A8FEB4A00F27EE8 /* add_blue.png */; }; + 8E7A0F1B0A8FEB4A00F27EE8 /* add_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F070A8FEB4A00F27EE8 /* add_gray.png */; }; + 8E7A0F1C0A8FEB4A00F27EE8 /* info_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F080A8FEB4A00F27EE8 /* info_blue.png */; }; + 8E7A0F1D0A8FEB4A00F27EE8 /* info_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F090A8FEB4A00F27EE8 /* info_gray.png */; }; + 8E7A0F1E0A8FEB4A00F27EE8 /* next_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F0A0A8FEB4A00F27EE8 /* next_blue.png */; }; + 8E7A0F1F0A8FEB4A00F27EE8 /* next_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F0B0A8FEB4A00F27EE8 /* next_gray.png */; }; + 8E7A0F200A8FEB4A00F27EE8 /* pause_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F0C0A8FEB4A00F27EE8 /* pause_blue.png */; }; + 8E7A0F210A8FEB4A00F27EE8 /* pause_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F0D0A8FEB4A00F27EE8 /* pause_gray.png */; }; + 8E7A0F220A8FEB4A00F27EE8 /* play_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F0E0A8FEB4A00F27EE8 /* play_blue.png */; }; + 8E7A0F230A8FEB4A00F27EE8 /* play_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F0F0A8FEB4A00F27EE8 /* play_gray.png */; }; + 8E7A0F240A8FEB4A00F27EE8 /* prev_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F100A8FEB4A00F27EE8 /* prev_blue.png */; }; + 8E7A0F250A8FEB4A00F27EE8 /* prev_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F110A8FEB4A00F27EE8 /* prev_gray.png */; }; + 8E7A0F260A8FEB4A00F27EE8 /* remove_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F120A8FEB4A00F27EE8 /* remove_blue.png */; }; + 8E7A0F270A8FEB4A00F27EE8 /* remove_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F130A8FEB4A00F27EE8 /* remove_gray.png */; }; + 8E7A0F280A8FEB4A00F27EE8 /* repeat_off.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F140A8FEB4A00F27EE8 /* repeat_off.png */; }; + 8E7A0F290A8FEB4A00F27EE8 /* repeat_on.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F150A8FEB4A00F27EE8 /* repeat_on.png */; }; + 8E7A0F2A0A8FEB4A00F27EE8 /* shuffle_off.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F160A8FEB4A00F27EE8 /* shuffle_off.png */; }; + 8E7A0F2B0A8FEB4A00F27EE8 /* shuffle_on.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F170A8FEB4A00F27EE8 /* shuffle_on.png */; }; + 8E7A0F2C0A8FEB4A00F27EE8 /* volume_high.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F180A8FEB4A00F27EE8 /* volume_high.png */; }; + 8E7A0F2D0A8FEB4A00F27EE8 /* volume_low.png in Resources */ = {isa = PBXBuildFile; fileRef = 8E7A0F190A8FEB4A00F27EE8 /* volume_low.png */; }; 8EA917300A336CC30087CDE2 /* Shorten.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8EA9172F0A336CC30087CDE2 /* Shorten.framework */; }; 8EB450080A2BB8B300AA711F /* Cog Help in Resources */ = {isa = PBXBuildFile; fileRef = 8EB44FF90A2BB8B300AA711F /* Cog Help */; }; 8EB971790A44B74A009803E3 /* MAD.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8E1849C40A43DB5C0084C69D /* MAD.framework */; }; - 8EC077AA0A4C950E00E8961C /* info_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077950A4C950E00E8961C /* info_blue.png */; }; - 8EC077AB0A4C950E00E8961C /* info_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077960A4C950E00E8961C /* info_gray.png */; }; - 8EC077AC0A4C950E00E8961C /* loop_blue_1.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077970A4C950E00E8961C /* loop_blue_1.png */; }; - 8EC077AD0A4C950E00E8961C /* loop_blue_2.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077980A4C950E00E8961C /* loop_blue_2.png */; }; - 8EC077AE0A4C950E00E8961C /* loop_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077990A4C950E00E8961C /* loop_gray.png */; }; - 8EC077AF0A4C950E00E8961C /* minus_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC0779A0A4C950E00E8961C /* minus_blue.png */; }; - 8EC077B00A4C950E00E8961C /* minus_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC0779B0A4C950E00E8961C /* minus_gray.png */; }; - 8EC077B10A4C950E00E8961C /* next_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC0779C0A4C950E00E8961C /* next_blue.png */; }; - 8EC077B20A4C950E00E8961C /* next_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC0779D0A4C950E00E8961C /* next_gray.png */; }; - 8EC077B30A4C950E00E8961C /* pause_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC0779E0A4C950E00E8961C /* pause_blue.png */; }; - 8EC077B40A4C950E00E8961C /* pause_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC0779F0A4C950E00E8961C /* pause_gray.png */; }; - 8EC077B50A4C950E00E8961C /* play_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A00A4C950E00E8961C /* play_blue.png */; }; - 8EC077B60A4C950E00E8961C /* play_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A10A4C950E00E8961C /* play_gray.png */; }; - 8EC077B70A4C950E00E8961C /* playlist_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A20A4C950E00E8961C /* playlist_blue.png */; }; - 8EC077B80A4C950E00E8961C /* playlist_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A30A4C950E00E8961C /* playlist_gray.png */; }; - 8EC077B90A4C950E00E8961C /* plus_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A40A4C950E00E8961C /* plus_blue.png */; }; - 8EC077BA0A4C950E00E8961C /* plus_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A50A4C950E00E8961C /* plus_gray.png */; }; - 8EC077BB0A4C950E00E8961C /* previous_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A60A4C950E00E8961C /* previous_blue.png */; }; - 8EC077BC0A4C950E00E8961C /* previous_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A70A4C950E00E8961C /* previous_gray.png */; }; - 8EC077BD0A4C950E00E8961C /* random_blue.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A80A4C950E00E8961C /* random_blue.png */; }; - 8EC077BE0A4C950E00E8961C /* random_gray.png in Resources */ = {isa = PBXBuildFile; fileRef = 8EC077A90A4C950E00E8961C /* random_gray.png */; }; + 8EFFCD5E0AA093AF00C458A5 /* DirectoryNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD420AA093AF00C458A5 /* DirectoryNode.h */; }; + 8EFFCD5F0AA093AF00C458A5 /* DirectoryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD430AA093AF00C458A5 /* DirectoryNode.m */; }; + 8EFFCD600AA093AF00C458A5 /* FileIconCell.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD440AA093AF00C458A5 /* FileIconCell.h */; }; + 8EFFCD610AA093AF00C458A5 /* FileIconCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD450AA093AF00C458A5 /* FileIconCell.m */; }; + 8EFFCD620AA093AF00C458A5 /* FileNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD460AA093AF00C458A5 /* FileNode.h */; }; + 8EFFCD630AA093AF00C458A5 /* FileNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD470AA093AF00C458A5 /* FileNode.m */; }; + 8EFFCD640AA093AF00C458A5 /* FileOutlineView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD480AA093AF00C458A5 /* FileOutlineView.h */; }; + 8EFFCD650AA093AF00C458A5 /* FileOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD490AA093AF00C458A5 /* FileOutlineView.m */; }; + 8EFFCD660AA093AF00C458A5 /* FileTreeController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD4A0AA093AF00C458A5 /* FileTreeController.h */; }; + 8EFFCD670AA093AF00C458A5 /* FileTreeController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD4B0AA093AF00C458A5 /* FileTreeController.m */; }; + 8EFFCD680AA093AF00C458A5 /* FileTreeWatcher.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD4C0AA093AF00C458A5 /* FileTreeWatcher.h */; }; + 8EFFCD690AA093AF00C458A5 /* FileTreeWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD4D0AA093AF00C458A5 /* FileTreeWatcher.m */; }; + 8EFFCD6A0AA093AF00C458A5 /* ImageTextCell.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD4E0AA093AF00C458A5 /* ImageTextCell.h */; }; + 8EFFCD6B0AA093AF00C458A5 /* ImageTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD4F0AA093AF00C458A5 /* ImageTextCell.m */; }; + 8EFFCD6C0AA093AF00C458A5 /* PathIcon.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD500AA093AF00C458A5 /* PathIcon.h */; }; + 8EFFCD6D0AA093AF00C458A5 /* PathIcon.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD510AA093AF00C458A5 /* PathIcon.m */; }; + 8EFFCD6E0AA093AF00C458A5 /* PathNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD520AA093AF00C458A5 /* PathNode.h */; }; + 8EFFCD6F0AA093AF00C458A5 /* PathNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD530AA093AF00C458A5 /* PathNode.m */; }; + 8EFFCD700AA093AF00C458A5 /* UKFileWatcher.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD550AA093AF00C458A5 /* UKFileWatcher.h */; }; + 8EFFCD710AA093AF00C458A5 /* UKFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD560AA093AF00C458A5 /* UKFileWatcher.m */; }; + 8EFFCD720AA093AF00C458A5 /* UKFNSubscribeFileWatcher.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD570AA093AF00C458A5 /* UKFNSubscribeFileWatcher.h */; }; + 8EFFCD730AA093AF00C458A5 /* UKFNSubscribeFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD580AA093AF00C458A5 /* UKFNSubscribeFileWatcher.m */; }; + 8EFFCD740AA093AF00C458A5 /* UKKQueue Readme.txt in Resources */ = {isa = PBXBuildFile; fileRef = 8EFFCD590AA093AF00C458A5 /* UKKQueue Readme.txt */; }; + 8EFFCD750AA093AF00C458A5 /* UKKQueue.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD5A0AA093AF00C458A5 /* UKKQueue.h */; }; + 8EFFCD760AA093AF00C458A5 /* UKKQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD5B0AA093AF00C458A5 /* UKKQueue.m */; }; + 8EFFCD770AA093AF00C458A5 /* UKMainThreadProxy.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8EFFCD5C0AA093AF00C458A5 /* UKMainThreadProxy.h */; }; + 8EFFCD780AA093AF00C458A5 /* UKMainThreadProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 8EFFCD5D0AA093AF00C458A5 /* UKMainThreadProxy.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -131,6 +157,19 @@ 8E757B5709F326710080F1EE /* OggFLAC.framework in CopyFiles */, 8E1296DA0A2BA9CE00443124 /* PlaylistHeaderView.h in CopyFiles */, 8E1849C80A43DB730084C69D /* MADFile.h in CopyFiles */, + 8EFFCD5E0AA093AF00C458A5 /* DirectoryNode.h in CopyFiles */, + 8EFFCD600AA093AF00C458A5 /* FileIconCell.h in CopyFiles */, + 8EFFCD620AA093AF00C458A5 /* FileNode.h in CopyFiles */, + 8EFFCD640AA093AF00C458A5 /* FileOutlineView.h in CopyFiles */, + 8EFFCD660AA093AF00C458A5 /* FileTreeController.h in CopyFiles */, + 8EFFCD680AA093AF00C458A5 /* FileTreeWatcher.h in CopyFiles */, + 8EFFCD6A0AA093AF00C458A5 /* ImageTextCell.h in CopyFiles */, + 8EFFCD6C0AA093AF00C458A5 /* PathIcon.h in CopyFiles */, + 8EFFCD6E0AA093AF00C458A5 /* PathNode.h in CopyFiles */, + 8EFFCD700AA093AF00C458A5 /* UKFileWatcher.h in CopyFiles */, + 8EFFCD720AA093AF00C458A5 /* UKFNSubscribeFileWatcher.h in CopyFiles */, + 8EFFCD750AA093AF00C458A5 /* UKKQueue.h in CopyFiles */, + 8EFFCD770AA093AF00C458A5 /* UKMainThreadProxy.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -164,6 +203,8 @@ 8E4C7F060A0509FC003BE25F /* DragScrollView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DragScrollView.h; sourceTree = ""; }; 8E4C7F070A0509FC003BE25F /* DragScrollView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = DragScrollView.m; sourceTree = ""; }; 8E4CAB5A0A32251B00214C1D /* ShnFile.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = ShnFile.mm; sourceTree = ""; }; + 8E4E7C180AA1ED4500D11405 /* file_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_blue.png; sourceTree = ""; }; + 8E4E7C190AA1ED4500D11405 /* file_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_gray.png; sourceTree = ""; }; 8E53E8600A44C11B007E5BCE /* ID3Tag.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ID3Tag.framework; path = Libraries/ID3Tag/build/Release/ID3Tag.framework; sourceTree = ""; }; 8E643DF20A2B585600844A28 /* GameFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GameFile.h; sourceTree = ""; }; 8E643DF30A2B585600844A28 /* GameFile.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = GameFile.mm; sourceTree = ""; }; @@ -218,8 +259,6 @@ 8E75754809F31D5A0080F1EE /* FlacFile.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = FlacFile.m; sourceTree = ""; }; 8E75754909F31D5A0080F1EE /* MonkeysFile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MonkeysFile.h; sourceTree = ""; }; 8E75754A09F31D5A0080F1EE /* MonkeysFile.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = MonkeysFile.mm; sourceTree = ""; }; - 8E75754B09F31D5A0080F1EE /* MPEGFile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MPEGFile.h; sourceTree = ""; }; - 8E75754C09F31D5A0080F1EE /* MPEGFile.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = MPEGFile.mm; sourceTree = ""; }; 8E75754D09F31D5A0080F1EE /* MusepackFile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MusepackFile.h; sourceTree = ""; }; 8E75754E09F31D5A0080F1EE /* MusepackFile.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = MusepackFile.m; sourceTree = ""; }; 8E75754F09F31D5A0080F1EE /* ShnFile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ShnFile.h; sourceTree = ""; }; @@ -243,8 +282,6 @@ 8E75756609F31D5A0080F1EE /* Semaphore.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = Semaphore.m; sourceTree = ""; }; 8E75756709F31D5A0080F1EE /* VirtualRingBuffer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VirtualRingBuffer.h; sourceTree = ""; }; 8E75756809F31D5A0080F1EE /* VirtualRingBuffer.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = VirtualRingBuffer.m; sourceTree = ""; }; - 8E7575A409F31D800080F1EE /* volume_high.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = volume_high.png; sourceTree = ""; }; - 8E7575A509F31D800080F1EE /* volume_low.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = volume_low.png; sourceTree = ""; }; 8E7575A609F31D800080F1EE /* wheel.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = wheel.icns; sourceTree = ""; }; 8E7575C309F31DCA0080F1EE /* Changelog */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = Changelog; sourceTree = ""; }; 8E7575C409F31DCA0080F1EE /* Cog.scriptSuite */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = Cog.scriptSuite; sourceTree = ""; }; @@ -266,29 +303,55 @@ 8E75775309F31F750080F1EE /* WavPack.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WavPack.framework; path = Libraries/WavPack/build/Release/WavPack.framework; sourceTree = ""; }; 8E757C7A09F32F070080F1EE /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = /System/Library/Frameworks/AudioToolbox.framework; sourceTree = ""; }; 8E757C7B09F32F070080F1EE /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = /System/Library/Frameworks/AudioUnit.framework; sourceTree = ""; }; + 8E7A0F060A8FEB4A00F27EE8 /* add_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = add_blue.png; sourceTree = ""; }; + 8E7A0F070A8FEB4A00F27EE8 /* add_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = add_gray.png; sourceTree = ""; }; + 8E7A0F080A8FEB4A00F27EE8 /* info_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = info_blue.png; sourceTree = ""; }; + 8E7A0F090A8FEB4A00F27EE8 /* info_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = info_gray.png; sourceTree = ""; }; + 8E7A0F0A0A8FEB4A00F27EE8 /* next_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = next_blue.png; sourceTree = ""; }; + 8E7A0F0B0A8FEB4A00F27EE8 /* next_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = next_gray.png; sourceTree = ""; }; + 8E7A0F0C0A8FEB4A00F27EE8 /* pause_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pause_blue.png; sourceTree = ""; }; + 8E7A0F0D0A8FEB4A00F27EE8 /* pause_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pause_gray.png; sourceTree = ""; }; + 8E7A0F0E0A8FEB4A00F27EE8 /* play_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = play_blue.png; sourceTree = ""; }; + 8E7A0F0F0A8FEB4A00F27EE8 /* play_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = play_gray.png; sourceTree = ""; }; + 8E7A0F100A8FEB4A00F27EE8 /* prev_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = prev_blue.png; sourceTree = ""; }; + 8E7A0F110A8FEB4A00F27EE8 /* prev_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = prev_gray.png; sourceTree = ""; }; + 8E7A0F120A8FEB4A00F27EE8 /* remove_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = remove_blue.png; sourceTree = ""; }; + 8E7A0F130A8FEB4A00F27EE8 /* remove_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = remove_gray.png; sourceTree = ""; }; + 8E7A0F140A8FEB4A00F27EE8 /* repeat_off.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = repeat_off.png; sourceTree = ""; }; + 8E7A0F150A8FEB4A00F27EE8 /* repeat_on.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = repeat_on.png; sourceTree = ""; }; + 8E7A0F160A8FEB4A00F27EE8 /* shuffle_off.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = shuffle_off.png; sourceTree = ""; }; + 8E7A0F170A8FEB4A00F27EE8 /* shuffle_on.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = shuffle_on.png; sourceTree = ""; }; + 8E7A0F180A8FEB4A00F27EE8 /* volume_high.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = volume_high.png; sourceTree = ""; }; + 8E7A0F190A8FEB4A00F27EE8 /* volume_low.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = volume_low.png; sourceTree = ""; }; 8EA9172F0A336CC30087CDE2 /* Shorten.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Shorten.framework; path = Libraries/Shorten/build/Release/Shorten.framework; sourceTree = ""; }; 8EB44FF90A2BB8B300AA711F /* Cog Help */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Cog Help"; sourceTree = ""; }; - 8EC077950A4C950E00E8961C /* info_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = info_blue.png; sourceTree = ""; }; - 8EC077960A4C950E00E8961C /* info_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = info_gray.png; sourceTree = ""; }; - 8EC077970A4C950E00E8961C /* loop_blue_1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = loop_blue_1.png; sourceTree = ""; }; - 8EC077980A4C950E00E8961C /* loop_blue_2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = loop_blue_2.png; sourceTree = ""; }; - 8EC077990A4C950E00E8961C /* loop_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = loop_gray.png; sourceTree = ""; }; - 8EC0779A0A4C950E00E8961C /* minus_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = minus_blue.png; sourceTree = ""; }; - 8EC0779B0A4C950E00E8961C /* minus_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = minus_gray.png; sourceTree = ""; }; - 8EC0779C0A4C950E00E8961C /* next_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = next_blue.png; sourceTree = ""; }; - 8EC0779D0A4C950E00E8961C /* next_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = next_gray.png; sourceTree = ""; }; - 8EC0779E0A4C950E00E8961C /* pause_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pause_blue.png; sourceTree = ""; }; - 8EC0779F0A4C950E00E8961C /* pause_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pause_gray.png; sourceTree = ""; }; - 8EC077A00A4C950E00E8961C /* play_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = play_blue.png; sourceTree = ""; }; - 8EC077A10A4C950E00E8961C /* play_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = play_gray.png; sourceTree = ""; }; - 8EC077A20A4C950E00E8961C /* playlist_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = playlist_blue.png; sourceTree = ""; }; - 8EC077A30A4C950E00E8961C /* playlist_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = playlist_gray.png; sourceTree = ""; }; - 8EC077A40A4C950E00E8961C /* plus_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = plus_blue.png; sourceTree = ""; }; - 8EC077A50A4C950E00E8961C /* plus_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = plus_gray.png; sourceTree = ""; }; - 8EC077A60A4C950E00E8961C /* previous_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = previous_blue.png; sourceTree = ""; }; - 8EC077A70A4C950E00E8961C /* previous_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = previous_gray.png; sourceTree = ""; }; - 8EC077A80A4C950E00E8961C /* random_blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = random_blue.png; sourceTree = ""; }; - 8EC077A90A4C950E00E8961C /* random_gray.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = random_gray.png; sourceTree = ""; }; + 8EFFCD420AA093AF00C458A5 /* DirectoryNode.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DirectoryNode.h; sourceTree = ""; }; + 8EFFCD430AA093AF00C458A5 /* DirectoryNode.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = DirectoryNode.m; sourceTree = ""; }; + 8EFFCD440AA093AF00C458A5 /* FileIconCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FileIconCell.h; sourceTree = ""; }; + 8EFFCD450AA093AF00C458A5 /* FileIconCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = FileIconCell.m; sourceTree = ""; }; + 8EFFCD460AA093AF00C458A5 /* FileNode.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FileNode.h; sourceTree = ""; }; + 8EFFCD470AA093AF00C458A5 /* FileNode.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = FileNode.m; sourceTree = ""; }; + 8EFFCD480AA093AF00C458A5 /* FileOutlineView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FileOutlineView.h; sourceTree = ""; }; + 8EFFCD490AA093AF00C458A5 /* FileOutlineView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = FileOutlineView.m; sourceTree = ""; }; + 8EFFCD4A0AA093AF00C458A5 /* FileTreeController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FileTreeController.h; sourceTree = ""; }; + 8EFFCD4B0AA093AF00C458A5 /* FileTreeController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = FileTreeController.m; sourceTree = ""; }; + 8EFFCD4C0AA093AF00C458A5 /* FileTreeWatcher.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FileTreeWatcher.h; sourceTree = ""; }; + 8EFFCD4D0AA093AF00C458A5 /* FileTreeWatcher.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = FileTreeWatcher.m; sourceTree = ""; }; + 8EFFCD4E0AA093AF00C458A5 /* ImageTextCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ImageTextCell.h; sourceTree = ""; }; + 8EFFCD4F0AA093AF00C458A5 /* ImageTextCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ImageTextCell.m; sourceTree = ""; }; + 8EFFCD500AA093AF00C458A5 /* PathIcon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PathIcon.h; sourceTree = ""; }; + 8EFFCD510AA093AF00C458A5 /* PathIcon.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PathIcon.m; sourceTree = ""; }; + 8EFFCD520AA093AF00C458A5 /* PathNode.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PathNode.h; sourceTree = ""; }; + 8EFFCD530AA093AF00C458A5 /* PathNode.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PathNode.m; sourceTree = ""; }; + 8EFFCD550AA093AF00C458A5 /* UKFileWatcher.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = UKFileWatcher.h; sourceTree = ""; }; + 8EFFCD560AA093AF00C458A5 /* UKFileWatcher.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = UKFileWatcher.m; sourceTree = ""; }; + 8EFFCD570AA093AF00C458A5 /* UKFNSubscribeFileWatcher.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = UKFNSubscribeFileWatcher.h; sourceTree = ""; }; + 8EFFCD580AA093AF00C458A5 /* UKFNSubscribeFileWatcher.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = UKFNSubscribeFileWatcher.m; sourceTree = ""; }; + 8EFFCD590AA093AF00C458A5 /* UKKQueue Readme.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = "UKKQueue Readme.txt"; sourceTree = ""; }; + 8EFFCD5A0AA093AF00C458A5 /* UKKQueue.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = UKKQueue.h; sourceTree = ""; }; + 8EFFCD5B0AA093AF00C458A5 /* UKKQueue.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = UKKQueue.m; sourceTree = ""; }; + 8EFFCD5C0AA093AF00C458A5 /* UKMainThreadProxy.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = UKMainThreadProxy.h; sourceTree = ""; }; + 8EFFCD5D0AA093AF00C458A5 /* UKMainThreadProxy.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = UKMainThreadProxy.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -319,6 +382,7 @@ 080E96DDFE201D6D7F000001 /* Classes */ = { isa = PBXGroup; children = ( + 8EFFCD410AA093AF00C458A5 /* FileDrawer */, 8E75752309F31D5A0080F1EE /* Feedback */, 8E75755D09F31D5A0080F1EE /* Updates */, 8E75756209F31D5A0080F1EE /* Utils */, @@ -495,8 +559,6 @@ 8E75754809F31D5A0080F1EE /* FlacFile.m */, 8E75754909F31D5A0080F1EE /* MonkeysFile.h */, 8E75754A09F31D5A0080F1EE /* MonkeysFile.mm */, - 8E75754B09F31D5A0080F1EE /* MPEGFile.h */, - 8E75754C09F31D5A0080F1EE /* MPEGFile.mm */, 8E75754D09F31D5A0080F1EE /* MusepackFile.h */, 8E75754E09F31D5A0080F1EE /* MusepackFile.m */, 8E75754F09F31D5A0080F1EE /* ShnFile.h */, @@ -546,29 +608,28 @@ 8E75758E09F31D800080F1EE /* Icons */ = { isa = PBXGroup; children = ( - 8EC077950A4C950E00E8961C /* info_blue.png */, - 8EC077960A4C950E00E8961C /* info_gray.png */, - 8EC077970A4C950E00E8961C /* loop_blue_1.png */, - 8EC077980A4C950E00E8961C /* loop_blue_2.png */, - 8EC077990A4C950E00E8961C /* loop_gray.png */, - 8EC0779A0A4C950E00E8961C /* minus_blue.png */, - 8EC0779B0A4C950E00E8961C /* minus_gray.png */, - 8EC0779C0A4C950E00E8961C /* next_blue.png */, - 8EC0779D0A4C950E00E8961C /* next_gray.png */, - 8EC0779E0A4C950E00E8961C /* pause_blue.png */, - 8EC0779F0A4C950E00E8961C /* pause_gray.png */, - 8EC077A00A4C950E00E8961C /* play_blue.png */, - 8EC077A10A4C950E00E8961C /* play_gray.png */, - 8EC077A20A4C950E00E8961C /* playlist_blue.png */, - 8EC077A30A4C950E00E8961C /* playlist_gray.png */, - 8EC077A40A4C950E00E8961C /* plus_blue.png */, - 8EC077A50A4C950E00E8961C /* plus_gray.png */, - 8EC077A60A4C950E00E8961C /* previous_blue.png */, - 8EC077A70A4C950E00E8961C /* previous_gray.png */, - 8EC077A80A4C950E00E8961C /* random_blue.png */, - 8EC077A90A4C950E00E8961C /* random_gray.png */, - 8E7575A409F31D800080F1EE /* volume_high.png */, - 8E7575A509F31D800080F1EE /* volume_low.png */, + 8E7A0F060A8FEB4A00F27EE8 /* add_blue.png */, + 8E7A0F070A8FEB4A00F27EE8 /* add_gray.png */, + 8E4E7C180AA1ED4500D11405 /* file_blue.png */, + 8E4E7C190AA1ED4500D11405 /* file_gray.png */, + 8E7A0F080A8FEB4A00F27EE8 /* info_blue.png */, + 8E7A0F090A8FEB4A00F27EE8 /* info_gray.png */, + 8E7A0F0A0A8FEB4A00F27EE8 /* next_blue.png */, + 8E7A0F0B0A8FEB4A00F27EE8 /* next_gray.png */, + 8E7A0F0C0A8FEB4A00F27EE8 /* pause_blue.png */, + 8E7A0F0D0A8FEB4A00F27EE8 /* pause_gray.png */, + 8E7A0F0E0A8FEB4A00F27EE8 /* play_blue.png */, + 8E7A0F0F0A8FEB4A00F27EE8 /* play_gray.png */, + 8E7A0F100A8FEB4A00F27EE8 /* prev_blue.png */, + 8E7A0F110A8FEB4A00F27EE8 /* prev_gray.png */, + 8E7A0F120A8FEB4A00F27EE8 /* remove_blue.png */, + 8E7A0F130A8FEB4A00F27EE8 /* remove_gray.png */, + 8E7A0F140A8FEB4A00F27EE8 /* repeat_off.png */, + 8E7A0F150A8FEB4A00F27EE8 /* repeat_on.png */, + 8E7A0F160A8FEB4A00F27EE8 /* shuffle_off.png */, + 8E7A0F170A8FEB4A00F27EE8 /* shuffle_on.png */, + 8E7A0F180A8FEB4A00F27EE8 /* volume_high.png */, + 8E7A0F190A8FEB4A00F27EE8 /* volume_low.png */, 8E7575A609F31D800080F1EE /* wheel.icns */, ); path = Icons; @@ -592,6 +653,48 @@ name = "Codec Frameworks"; sourceTree = ""; }; + 8EFFCD410AA093AF00C458A5 /* FileDrawer */ = { + isa = PBXGroup; + children = ( + 8EFFCD440AA093AF00C458A5 /* FileIconCell.h */, + 8EFFCD450AA093AF00C458A5 /* FileIconCell.m */, + 8EFFCD4E0AA093AF00C458A5 /* ImageTextCell.h */, + 8EFFCD4F0AA093AF00C458A5 /* ImageTextCell.m */, + 8EFFCD500AA093AF00C458A5 /* PathIcon.h */, + 8EFFCD510AA093AF00C458A5 /* PathIcon.m */, + 8EFFCD480AA093AF00C458A5 /* FileOutlineView.h */, + 8EFFCD490AA093AF00C458A5 /* FileOutlineView.m */, + 8EFFCD4A0AA093AF00C458A5 /* FileTreeController.h */, + 8EFFCD4B0AA093AF00C458A5 /* FileTreeController.m */, + 8EFFCD4C0AA093AF00C458A5 /* FileTreeWatcher.h */, + 8EFFCD4D0AA093AF00C458A5 /* FileTreeWatcher.m */, + 8EFFCD420AA093AF00C458A5 /* DirectoryNode.h */, + 8EFFCD430AA093AF00C458A5 /* DirectoryNode.m */, + 8EFFCD460AA093AF00C458A5 /* FileNode.h */, + 8EFFCD470AA093AF00C458A5 /* FileNode.m */, + 8EFFCD520AA093AF00C458A5 /* PathNode.h */, + 8EFFCD530AA093AF00C458A5 /* PathNode.m */, + 8EFFCD540AA093AF00C458A5 /* UKKQueue */, + ); + path = FileDrawer; + sourceTree = ""; + }; + 8EFFCD540AA093AF00C458A5 /* UKKQueue */ = { + isa = PBXGroup; + children = ( + 8EFFCD550AA093AF00C458A5 /* UKFileWatcher.h */, + 8EFFCD560AA093AF00C458A5 /* UKFileWatcher.m */, + 8EFFCD570AA093AF00C458A5 /* UKFNSubscribeFileWatcher.h */, + 8EFFCD580AA093AF00C458A5 /* UKFNSubscribeFileWatcher.m */, + 8EFFCD590AA093AF00C458A5 /* UKKQueue Readme.txt */, + 8EFFCD5A0AA093AF00C458A5 /* UKKQueue.h */, + 8EFFCD5B0AA093AF00C458A5 /* UKKQueue.m */, + 8EFFCD5C0AA093AF00C458A5 /* UKMainThreadProxy.h */, + 8EFFCD5D0AA093AF00C458A5 /* UKMainThreadProxy.m */, + ); + path = UKKQueue; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -638,8 +741,6 @@ 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */, 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 8E75758709F31D5A0080F1EE /* SOUNDTODO in Resources */, - 8E7575BC09F31D800080F1EE /* volume_high.png in Resources */, - 8E7575BD09F31D800080F1EE /* volume_low.png in Resources */, 8E7575BE09F31D800080F1EE /* wheel.icns in Resources */, 8E7575CB09F31DCA0080F1EE /* Changelog in Resources */, 8E7575CC09F31DCA0080F1EE /* Cog.scriptSuite in Resources */, @@ -651,27 +752,29 @@ 8E7575D209F31DCA0080F1EE /* TODO in Resources */, 8E7575DB09F31E930080F1EE /* Localizable.strings in Resources */, 8EB450080A2BB8B300AA711F /* Cog Help in Resources */, - 8EC077AA0A4C950E00E8961C /* info_blue.png in Resources */, - 8EC077AB0A4C950E00E8961C /* info_gray.png in Resources */, - 8EC077AC0A4C950E00E8961C /* loop_blue_1.png in Resources */, - 8EC077AD0A4C950E00E8961C /* loop_blue_2.png in Resources */, - 8EC077AE0A4C950E00E8961C /* loop_gray.png in Resources */, - 8EC077AF0A4C950E00E8961C /* minus_blue.png in Resources */, - 8EC077B00A4C950E00E8961C /* minus_gray.png in Resources */, - 8EC077B10A4C950E00E8961C /* next_blue.png in Resources */, - 8EC077B20A4C950E00E8961C /* next_gray.png in Resources */, - 8EC077B30A4C950E00E8961C /* pause_blue.png in Resources */, - 8EC077B40A4C950E00E8961C /* pause_gray.png in Resources */, - 8EC077B50A4C950E00E8961C /* play_blue.png in Resources */, - 8EC077B60A4C950E00E8961C /* play_gray.png in Resources */, - 8EC077B70A4C950E00E8961C /* playlist_blue.png in Resources */, - 8EC077B80A4C950E00E8961C /* playlist_gray.png in Resources */, - 8EC077B90A4C950E00E8961C /* plus_blue.png in Resources */, - 8EC077BA0A4C950E00E8961C /* plus_gray.png in Resources */, - 8EC077BB0A4C950E00E8961C /* previous_blue.png in Resources */, - 8EC077BC0A4C950E00E8961C /* previous_gray.png in Resources */, - 8EC077BD0A4C950E00E8961C /* random_blue.png in Resources */, - 8EC077BE0A4C950E00E8961C /* random_gray.png in Resources */, + 8E7A0F1A0A8FEB4A00F27EE8 /* add_blue.png in Resources */, + 8E7A0F1B0A8FEB4A00F27EE8 /* add_gray.png in Resources */, + 8E7A0F1C0A8FEB4A00F27EE8 /* info_blue.png in Resources */, + 8E7A0F1D0A8FEB4A00F27EE8 /* info_gray.png in Resources */, + 8E7A0F1E0A8FEB4A00F27EE8 /* next_blue.png in Resources */, + 8E7A0F1F0A8FEB4A00F27EE8 /* next_gray.png in Resources */, + 8E7A0F200A8FEB4A00F27EE8 /* pause_blue.png in Resources */, + 8E7A0F210A8FEB4A00F27EE8 /* pause_gray.png in Resources */, + 8E7A0F220A8FEB4A00F27EE8 /* play_blue.png in Resources */, + 8E7A0F230A8FEB4A00F27EE8 /* play_gray.png in Resources */, + 8E7A0F240A8FEB4A00F27EE8 /* prev_blue.png in Resources */, + 8E7A0F250A8FEB4A00F27EE8 /* prev_gray.png in Resources */, + 8E7A0F260A8FEB4A00F27EE8 /* remove_blue.png in Resources */, + 8E7A0F270A8FEB4A00F27EE8 /* remove_gray.png in Resources */, + 8E7A0F280A8FEB4A00F27EE8 /* repeat_off.png in Resources */, + 8E7A0F290A8FEB4A00F27EE8 /* repeat_on.png in Resources */, + 8E7A0F2A0A8FEB4A00F27EE8 /* shuffle_off.png in Resources */, + 8E7A0F2B0A8FEB4A00F27EE8 /* shuffle_on.png in Resources */, + 8E7A0F2C0A8FEB4A00F27EE8 /* volume_high.png in Resources */, + 8E7A0F2D0A8FEB4A00F27EE8 /* volume_low.png in Resources */, + 8EFFCD740AA093AF00C458A5 /* UKKQueue Readme.txt in Resources */, + 8E4E7C1A0AA1ED4500D11405 /* file_blue.png in Resources */, + 8E4E7C1B0AA1ED4500D11405 /* file_gray.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -720,6 +823,19 @@ 8E1296DB0A2BA9CE00443124 /* PlaylistHeaderView.m in Sources */, 8E4CAB5B0A32251B00214C1D /* ShnFile.mm in Sources */, 8E1849C90A43DB730084C69D /* MADFile.m in Sources */, + 8EFFCD5F0AA093AF00C458A5 /* DirectoryNode.m in Sources */, + 8EFFCD610AA093AF00C458A5 /* FileIconCell.m in Sources */, + 8EFFCD630AA093AF00C458A5 /* FileNode.m in Sources */, + 8EFFCD650AA093AF00C458A5 /* FileOutlineView.m in Sources */, + 8EFFCD670AA093AF00C458A5 /* FileTreeController.m in Sources */, + 8EFFCD690AA093AF00C458A5 /* FileTreeWatcher.m in Sources */, + 8EFFCD6B0AA093AF00C458A5 /* ImageTextCell.m in Sources */, + 8EFFCD6D0AA093AF00C458A5 /* PathIcon.m in Sources */, + 8EFFCD6F0AA093AF00C458A5 /* PathNode.m in Sources */, + 8EFFCD710AA093AF00C458A5 /* UKFileWatcher.m in Sources */, + 8EFFCD730AA093AF00C458A5 /* UKFNSubscribeFileWatcher.m in Sources */, + 8EFFCD760AA093AF00C458A5 /* UKKQueue.m in Sources */, + 8EFFCD780AA093AF00C458A5 /* UKMainThreadProxy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Credits.html b/Credits.html index 71d5575a3..8696aad95 100644 --- a/Credits.html +++ b/Credits.html @@ -2,6 +2,6 @@ Cog is dedicated to snacos, the ungodly combination of snacks and tacos.

-This program has been made possible through donations from users like you. +This program has been made possible through contributions from users like you. diff --git a/English.lproj/MainMenu.nib/classes.nib b/English.lproj/MainMenu.nib/classes.nib index da24304ce..5b3e6ca70 100644 --- a/English.lproj/MainMenu.nib/classes.nib +++ b/English.lproj/MainMenu.nib/classes.nib @@ -6,16 +6,19 @@ delEntries = id; donate = id; loadPlaylist = id; + openFiles = id; savePlaylist = id; savePlaylistAs = id; - shufflePlaylist = id; - sortByPath = id; + toggleFileDrawer = id; toggleInfoDrawer = id; }; CLASS = AppController; LANGUAGE = ObjC; OUTLETS = { addButton = NSButton; + fileButton = NSButton; + fileDrawer = NSDrawer; + fileTreeController = FileTreeController; infoButton = NSButton; infoDrawer = NSDrawer; mainWindow = NSPanel; @@ -51,10 +54,18 @@ }; SUPERCLASS = NSObject; }, + {CLASS = FileOutlineView; LANGUAGE = ObjC; SUPERCLASS = NSOutlineView; }, + { + CLASS = FileTreeController; + LANGUAGE = ObjC; + OUTLETS = {playlistController = PlaylistController; }; + SUPERCLASS = NSTreeController; + }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, {CLASS = InfoController; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, {CLASS = InfoView; LANGUAGE = ObjC; SUPERCLASS = NSScrollView; }, {CLASS = NSSegmentedControl; LANGUAGE = ObjC; SUPERCLASS = NSControl; }, + {CLASS = PathNode; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, { ACTIONS = { changeVolume = id; diff --git a/English.lproj/MainMenu.nib/info.nib b/English.lproj/MainMenu.nib/info.nib index 16c529569..9679d38a3 100644 --- a/English.lproj/MainMenu.nib/info.nib +++ b/English.lproj/MainMenu.nib/info.nib @@ -3,11 +3,13 @@ IBDocumentLocation - 19 323 617 240 0 0 1024 746 + 71 108 639 388 0 0 1024 746 IBEditorPositions 1063 0 228 136 49 0 0 1024 746 + 1156 + 233 341 241 366 0 0 1024 746 29 65 680 383 44 0 0 1024 746 463 @@ -30,12 +32,11 @@ 3 IBOpenObjects - 21 - 1063 - 513 + 463 29 + 21 IBSystem Version - 8I127 + 8J135 diff --git a/English.lproj/MainMenu.nib/keyedobjects.nib b/English.lproj/MainMenu.nib/keyedobjects.nib index 3de216a97..bd689c696 100644 Binary files a/English.lproj/MainMenu.nib/keyedobjects.nib and b/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/FileDrawer/DirectoryNode.h b/FileDrawer/DirectoryNode.h new file mode 100644 index 000000000..e1c127176 --- /dev/null +++ b/FileDrawer/DirectoryNode.h @@ -0,0 +1,20 @@ +// +// DirectoryNode.h +// Cog +// +// Created by Vincent Spader on 8/20/2006. +// Copyright 2006 Vincent Spader. All rights reserved. +// + +#import +#import "PathNode.h" + +@interface DirectoryNode : PathNode +{ + NSMutableArray *subpaths; + id controller; +} + +- (NSArray *)subpaths; + +@end diff --git a/FileDrawer/DirectoryNode.m b/FileDrawer/DirectoryNode.m new file mode 100644 index 000000000..1a9c075a5 --- /dev/null +++ b/FileDrawer/DirectoryNode.m @@ -0,0 +1,94 @@ +// +// DirectoryNode.m +// Cog +// +// Created by Vincent Spader on 8/20/2006. +// Copyright 2006 Vincent Spader. All rights reserved. +// + +#import "DirectoryNode.h" +#import "FileNode.h" + +@implementation DirectoryNode + +-(id)initWithPath:(NSString *)p controller:(id) c +{ + self = [super initWithPath:p]; + if (self) + { + controller = [c retain]; + } + + return self; +} + +- (void)dealloc { + [controller release]; + [super dealloc]; +} + +- (BOOL)isLeaf +{ + return NO; +} + +- (NSArray *)subpaths +{ + if (subpaths == nil) + { + subpaths = [[NSMutableArray alloc] init]; + NSArray *contents = [[NSFileManager defaultManager] directoryContentsAtPath:path]; + NSEnumerator *e = [contents objectEnumerator]; + NSString *s; + while ((s = [e nextObject])) + { + if ([s characterAtIndex:0] == '.') + { + continue; + } + + BOOL isDir; + PathNode *newNode; + NSString *newSubpath = [path stringByAppendingPathComponent: s]; + + [[NSFileManager defaultManager] fileExistsAtPath:newSubpath isDirectory:&isDir]; + + if (!isDir && ![[controller acceptableFileTypes] containsObject:[s pathExtension]]) + { + continue; + } + + if (isDir) + newNode = [[DirectoryNode alloc] initWithPath: newSubpath controller:controller]; + else + newNode = [[FileNode alloc] initWithPath: newSubpath]; + + [subpaths addObject:newNode]; + + [newNode release]; + } + [[controller watcher] addPath:[self path]]; + } + +// NSLog(@"subpaths; %@", subpaths); + return subpaths; +} + +- (void)setSubpaths:(id)s +{ + [s retain]; + [subpaths release]; + subpaths = s; +} + +- (unsigned int)countOfSubpaths +{ + return [[self subpaths] count]; +} + +- (PathNode *)objectInSubpathsAtIndex:(unsigned int)index +{ + return [[self subpaths] objectAtIndex:index]; +} + +@end diff --git a/FileDrawer/FileIconCell.h b/FileDrawer/FileIconCell.h new file mode 100644 index 000000000..499dc6f87 --- /dev/null +++ b/FileDrawer/FileIconCell.h @@ -0,0 +1,16 @@ +// +// FileIconTextCell.h +// Cog +// +// Created by Zaphod Beeblebrox on 8/20/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import +#import "ImageTextCell.h" + +@interface FileIconCell : ImageTextCell { + +} + +@end diff --git a/FileDrawer/FileIconCell.m b/FileDrawer/FileIconCell.m new file mode 100644 index 000000000..799875bdf --- /dev/null +++ b/FileDrawer/FileIconCell.m @@ -0,0 +1,26 @@ +// +// FileIconTextCell.m +// Cog +// +// Created by Zaphod Beeblebrox on 8/20/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import "FileIconCell.h" + + +@implementation FileIconCell + +- (void)setObjectValue:(id)o +{ + if ([o respondsToSelector:@selector(icon)]) { + [super setObjectValue:[[o path] lastPathComponent]]; + [super setImage: [o icon]]; + } + else { + [super setObjectValue:o]; + } +} + + +@end diff --git a/FileDrawer/FileNode.h b/FileDrawer/FileNode.h new file mode 100644 index 000000000..18e2e34fb --- /dev/null +++ b/FileDrawer/FileNode.h @@ -0,0 +1,17 @@ +// +// FileNode.h +// Cog +// +// Created by Vincent Spader on 8/20/2006. +// Copyright 2006 Vincent Spader. All rights reserved. +// + +#import +#import "PathNode.h" + +@interface FileNode : PathNode +{ + +} + +@end diff --git a/FileDrawer/FileNode.m b/FileDrawer/FileNode.m new file mode 100644 index 000000000..3b4c79cb7 --- /dev/null +++ b/FileDrawer/FileNode.m @@ -0,0 +1,18 @@ +// +// FileNode.m +// Cog +// +// Created by Vincent Spader on 8/20/2006. +// Copyright 2006 Vincent Spader. All rights reserved. +// + +#import "FileNode.h" + +@implementation FileNode + +- (BOOL)isLeaf +{ + return YES; +} + +@end diff --git a/FileDrawer/FileOutlineView.h b/FileDrawer/FileOutlineView.h new file mode 100644 index 000000000..fd2cbb295 --- /dev/null +++ b/FileDrawer/FileOutlineView.h @@ -0,0 +1,16 @@ +// +// FileOutlineView.h +// Cog +// +// Created by Zaphod Beeblebrox on 8/20/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import + + +@interface FileOutlineView : NSOutlineView { + +} + +@end diff --git a/FileDrawer/FileOutlineView.m b/FileDrawer/FileOutlineView.m new file mode 100644 index 000000000..046f72dfd --- /dev/null +++ b/FileDrawer/FileOutlineView.m @@ -0,0 +1,31 @@ +// +// FileOutlineView.m +// BindTest +// +// Created by Zaphod Beeblebrox on 8/20/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import "FileOutlineView.h" +#import "FileIconCell.h" + +@implementation FileOutlineView + +- (void) awakeFromNib +{ + NSLog(@"FILE OUTLINE VIEW"); + + NSEnumerator *e = [[self tableColumns] objectEnumerator]; + id c; + while ((c = [e nextObject])) + { +// id headerCell = [[ImageTextCell alloc] init]; + id dataCell = [[FileIconCell alloc] init]; + + [dataCell setLineBreakMode:NSLineBreakByTruncatingTail]; +// [c setHeaderCell: headerCell]; + [c setDataCell: dataCell]; + } +} + +@end diff --git a/FileDrawer/FileTreeController.h b/FileDrawer/FileTreeController.h new file mode 100644 index 000000000..ae3ed0771 --- /dev/null +++ b/FileDrawer/FileTreeController.h @@ -0,0 +1,25 @@ +// +// FileTreeController.h +// Cog +// +// Created by Vincent Spader on 8/20/2006. +// Copyright 2006 Vincent Spader. All rights reserved. +// + +#import +#import "FileTreeWatcher.h" +#import "PlaylistController.h" + +@interface FileTreeController : NSTreeController +{ + IBOutlet PlaylistController *playlistController; + + NSString *rootPath; + + FileTreeWatcher *watcher; +} + +- (id)rootPath; +- (void)setRootPath:(id)r; + +@end diff --git a/FileDrawer/FileTreeController.m b/FileDrawer/FileTreeController.m new file mode 100644 index 000000000..a9bbd000c --- /dev/null +++ b/FileDrawer/FileTreeController.m @@ -0,0 +1,145 @@ +// +// FileTreeController.m +// Cog +// +// Created by Vincent Spader on 8/20/2006. +// Copyright 2006 Vincent Spader. All rights reserved. +// + +#import "FileTreeController.h" +#import "DirectoryNode.h" +#import "ImageTextCell.h" + +@implementation FileTreeController + +- (void)awakeFromNib +{ + watcher = [[FileTreeWatcher alloc] init]; + [watcher setDelegate:self]; + + [self setRootPath: @"/Users/xugg/Music"]; +} + +- (void)dealloc +{ + [rootPath release]; + [super dealloc]; +} + +- (id)rootPath +{ + return rootPath; +} + +- (void)setRootPath:(id)r +{ + [r retain]; + [rootPath release]; + rootPath = r; + + [self refreshRoot]; +} + +- (void) refreshRoot +{ + DirectoryNode *base = [[DirectoryNode alloc] initWithPath:rootPath controller:self]; + [self setContent: [base subpaths]]; + [base release]; +} + +- (void)refreshPath:(NSString *)path +{ + if ([path compare:rootPath] == NSOrderedSame) { + [self refreshRoot]; + + return; + } + + NSArray *pathComponents = [path pathComponents]; + NSArray *rootComponents = [rootPath pathComponents]; + int i = 0; + while (i < [rootComponents count] && i < [pathComponents count] && + NSOrderedSame == [[rootComponents objectAtIndex: i] compare:[pathComponents objectAtIndex: i]]) + { + i++; + } + + + id p; + NSEnumerator *e = [[self content] objectEnumerator]; + while ((p = [e nextObject])) + { + id c = [pathComponents objectAtIndex:i]; + if (NSOrderedSame == [[[p path] lastPathComponent] compare:c]) { + if (i == [pathComponents count] - 1) { + [p setSubpaths:nil]; +// [self rearrangeObjects]; + } + else { + e = [[c subpaths] objectEnumerator]; + i++; + } + } + } +} + +- (NSArray *)acceptableFileTypes +{ + return [playlistController acceptableFileTypes]; +} + +- (FileTreeWatcher *)watcher +{ + return watcher; +} + +// Required Protocol Bullshit (RPB) +- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item +{ + return nil; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item +{ + return NO; +} + +- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item +{ + return 0; +} +- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item +{ + return nil; +} +//End of RPB + +- (BOOL)outlineView:(NSOutlineView *)olv writeItems:(NSArray*)items toPasteboard:(NSPasteboard*)pboard { + //Get selected paths + NSLog(@"Items: %@", items); + NSMutableArray *paths = [NSMutableArray arrayWithCapacity:[items count]]; + id p; + NSEnumerator *e = [items objectEnumerator]; + + while (p = [e nextObject]) { + int i; + PathNode *n = nil; + NSIndexPath *ip = [p indexPath]; + NSLog(@"Content: %@", n); + for (i = 0; i < [ip length]; i++) + { + NSArray *a = (n == nil) ? [self content] : [n subpaths]; + n = [a objectAtIndex:[ip indexAtPosition:i]]; + } + NSLog(@"Path: %@", n); + [paths addObject:[n path]]; + } + + [pboard declareTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,nil] owner:nil]; //add it to pboard + [pboard setPropertyList:paths forType:NSFilenamesPboardType]; + + return YES; +} + + +@end diff --git a/FileDrawer/FileTreeWatcher.h b/FileDrawer/FileTreeWatcher.h new file mode 100644 index 000000000..317113621 --- /dev/null +++ b/FileDrawer/FileTreeWatcher.h @@ -0,0 +1,19 @@ +// +// FileTreeDelegate.h +// BindTest +// +// Created by Zaphod Beeblebrox on 8/20/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import +#import "UKKQueue/UKKQueue.h" + +@interface FileTreeWatcher : NSObject { + UKKQueue *kqueue; + id delegate; + + NSMutableArray *watchedPaths; +} + +@end diff --git a/FileDrawer/FileTreeWatcher.m b/FileDrawer/FileTreeWatcher.m new file mode 100644 index 000000000..c900202a5 --- /dev/null +++ b/FileDrawer/FileTreeWatcher.m @@ -0,0 +1,55 @@ +// +// FileTreeDelegate.m +// BindTest +// +// Created by Zaphod Beeblebrox on 8/20/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import "FileTreeWatcher.h" + +@implementation FileTreeWatcher + +- (id)init +{ + self = [super init]; + if (self) + { + kqueue = [[UKKQueue alloc] init]; + [kqueue setDelegate:self]; + + watchedPaths = [[NSMutableArray alloc] init]; + } + + return self; +} + +- (void)dealloc +{ + [delegate release]; + [super dealloc]; +} + + +- (void)addPath: (NSString *)path +{ + if ([watchedPaths containsObject:path] == NO) { + [watchedPaths addObject:path]; + + [kqueue addPath: path]; + } +} + +-(void) setDelegate: (id)d +{ + delegate = [d retain]; +} + + +-(void) watcher: (id)kq receivedNotification: (NSString*)nm forPath: (NSString*)fpath +{ + NSLog(@"CHANGED! %@", fpath); + [delegate refreshPath: fpath]; +} + +@end diff --git a/FileDrawer/ImageTextCell.h b/FileDrawer/ImageTextCell.h new file mode 100644 index 000000000..49d8ed7b7 --- /dev/null +++ b/FileDrawer/ImageTextCell.h @@ -0,0 +1,20 @@ +// +// ImageAndTextCell.h +// +// Copyright (c) 2001-2002, Apple. All rights reserved. +// + +#import + +@interface ImageTextCell : NSTextFieldCell { +@private + NSImage *image; +} + +- (void)setImage:(NSImage *)anImage; +- (NSImage *)image; + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView; +- (NSSize)cellSize; + +@end \ No newline at end of file diff --git a/FileDrawer/ImageTextCell.m b/FileDrawer/ImageTextCell.m new file mode 100644 index 000000000..965c512df --- /dev/null +++ b/FileDrawer/ImageTextCell.m @@ -0,0 +1,83 @@ +#import "ImageTextCell.h" + +@implementation ImageTextCell + +- (void)dealloc { + [image release]; + image = nil; + [super dealloc]; +} + +- copyWithZone:(NSZone *)zone { + ImageTextCell *cell = (ImageTextCell *)[super copyWithZone:zone]; + cell->image = [image retain]; + return cell; +} + +- (void)setImage:(NSImage *)anImage { + if (anImage != image) { + [image release]; + image = [anImage retain]; + } +} + +- (NSImage *)image { + return image; +} + +- (NSRect)imageFrameForCellFrame:(NSRect)cellFrame { + if (image != nil) { + NSRect imageFrame; + imageFrame.size = [image size]; + imageFrame.origin = cellFrame.origin; + imageFrame.origin.x += 3; + imageFrame.origin.y += ceil((cellFrame.size.height - imageFrame.size.height) / 2); + return imageFrame; + } + else + return NSZeroRect; +} + +- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent { + NSRect textFrame, imageFrame; + NSDivideRect (aRect, &imageFrame, &textFrame, 3 + [image size].width, NSMinXEdge); + [super editWithFrame: textFrame inView: controlView editor:textObj delegate:anObject event: theEvent]; +} + +- (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(int)selStart length:(int)selLength { + NSRect textFrame, imageFrame; + NSDivideRect (aRect, &imageFrame, &textFrame, 3 + [image size].width, NSMinXEdge); + [super selectWithFrame: textFrame inView: controlView editor:textObj delegate:anObject start:selStart length:selLength]; +} + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + if (image != nil) { + NSSize imageSize; + NSRect imageFrame; + + imageSize = [image size]; + NSDivideRect(cellFrame, &imageFrame, &cellFrame, 3 + imageSize.width, NSMinXEdge); + if ([self drawsBackground]) { + [[self backgroundColor] set]; + NSRectFill(imageFrame); + } + imageFrame.origin.x += 3; + imageFrame.size = imageSize; + + if ([controlView isFlipped]) + imageFrame.origin.y += ceil((cellFrame.size.height + imageFrame.size.height) / 2); + else + imageFrame.origin.y += ceil((cellFrame.size.height - imageFrame.size.height) / 2); + + [image compositeToPoint:imageFrame.origin operation:NSCompositeSourceOver]; + } + [super drawWithFrame:cellFrame inView:controlView]; +} + +- (NSSize)cellSize { + NSSize cellSize = [super cellSize]; + cellSize.width += (image ? [image size].width : 0) + 3; + return cellSize; +} + +@end \ No newline at end of file diff --git a/FileDrawer/PathIcon.h b/FileDrawer/PathIcon.h new file mode 100644 index 000000000..c31e19a44 --- /dev/null +++ b/FileDrawer/PathIcon.h @@ -0,0 +1,19 @@ +// +// FileIcon.h +// Cog +// +// Created by Zaphod Beeblebrox on 8/20/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import + + +@interface PathIcon : NSObject { + NSString *path; + NSImage *icon; +} + +-(id)initWithPath:(NSString *)p; + +@end diff --git a/FileDrawer/PathIcon.m b/FileDrawer/PathIcon.m new file mode 100644 index 000000000..0044e5e11 --- /dev/null +++ b/FileDrawer/PathIcon.m @@ -0,0 +1,38 @@ +// +// FileIcon.m +// Cog +// +// Created by Zaphod Beeblebrox on 8/20/06. +// Copyright 2006 __MyCompanyName__. All rights reserved. +// + +#import "PathIcon.h" + + +@implementation PathIcon + +-(id)initWithPath:(NSString *)p +{ + self = [super init]; + if (self) + { + path = [p retain]; + icon = [[[NSWorkspace sharedWorkspace] iconForFile:path] retain]; + + [icon setSize: NSMakeSize(16.0, 16.0)]; + } + + return self; +} + +- (NSString *) path +{ + return path; +} + +- (NSImage *) icon +{ + return icon; +} + +@end diff --git a/FileDrawer/PathNode.h b/FileDrawer/PathNode.h new file mode 100644 index 000000000..906764e5b --- /dev/null +++ b/FileDrawer/PathNode.h @@ -0,0 +1,23 @@ +// +// Node.h +// Cog +// +// Created by Vincent Spader on 8/20/2006. +// Copyright 2006 Vincent Spader. All rights reserved. +// + +#import +#import "PathIcon.h" + +@interface PathNode : NSObject +{ + NSString *path; + PathIcon *pathIcon; +} + +- (id)initWithPath:(NSString *)p; + +- (id)pathIcon; +- (void)setPathIcon:(id)pi; + +@end diff --git a/FileDrawer/PathNode.m b/FileDrawer/PathNode.m new file mode 100644 index 000000000..8c6fdc444 --- /dev/null +++ b/FileDrawer/PathNode.m @@ -0,0 +1,50 @@ +// +// Node.m +// Cog +// +// Created by Vincent Spader on 8/20/2006. +// Copyright 2006 Vincent Spader. All rights reserved. +// + +#import "PathNode.h" + +@implementation PathNode + +- (id)initWithPath:(NSString *)p +{ + self = [super init]; + + if (self) + { + path = [p retain]; + [self setPathIcon:[[PathIcon alloc] initWithPath:path]]; + } + + return self; +} + +- (void)dealloc +{ + [path release]; + [super dealloc]; +} + +- (NSString *)path +{ + return path; +} + +- (id)pathIcon +{ + return pathIcon; +} + +- (void)setPathIcon:(id)pi +{ + [pi retain]; + [pathIcon release]; + pathIcon = pi; +} + + +@end diff --git a/FileDrawer/UKKQueue/UKFNSubscribeFileWatcher.h b/FileDrawer/UKKQueue/UKFNSubscribeFileWatcher.h new file mode 100644 index 000000000..259cb8378 --- /dev/null +++ b/FileDrawer/UKKQueue/UKFNSubscribeFileWatcher.h @@ -0,0 +1,49 @@ +/* ============================================================================= + FILE: UKFNSubscribeFileWatcher.m + PROJECT: Filie + + COPYRIGHT: (c) 2005 M. Uli Kusterer, all rights reserved. + + AUTHORS: M. Uli Kusterer - UK + + LICENSES: MIT License + + REVISIONS: + 2006-03-13 UK Commented, added singleton. + 2005-03-02 UK Created. + ========================================================================== */ + +// ----------------------------------------------------------------------------- +// Headers: +// ----------------------------------------------------------------------------- + +#import +#import "UKFileWatcher.h" +#import + +/* + NOTE: FNSubscribe has a built-in delay: If your application is in the + background while the changes happen, all notifications will be queued up + and sent to your app at once the moment it is brought to front again. If + your app really needs to do live updates in the background, use a KQueue + instead. +*/ + +// ----------------------------------------------------------------------------- +// Class declaration: +// ----------------------------------------------------------------------------- + +@interface UKFNSubscribeFileWatcher : NSObject +{ + id delegate; // Delegate must respond to UKFileWatcherDelegate protocol. + NSMutableDictionary* subscriptions; // List of FNSubscription pointers in NSValues, with the pathnames as their keys. +} + ++(id) sharedFileWatcher; + +// UKFileWatcher defines the methods: addPath: removePath: and delegate accessors. + +// Private: +-(void) sendDelegateMessage: (FNMessage)message forSubscription: (FNSubscriptionRef)subscription; + +@end diff --git a/FileDrawer/UKKQueue/UKFNSubscribeFileWatcher.m b/FileDrawer/UKKQueue/UKFNSubscribeFileWatcher.m new file mode 100644 index 000000000..f4df11ee7 --- /dev/null +++ b/FileDrawer/UKKQueue/UKFNSubscribeFileWatcher.m @@ -0,0 +1,201 @@ +/* ============================================================================= + FILE: UKFNSubscribeFileWatcher.m + PROJECT: Filie + + COPYRIGHT: (c) 2005 M. Uli Kusterer, all rights reserved. + + AUTHORS: M. Uli Kusterer - UK + + LICENSES: MIT License + + REVISIONS: + 2006-03-13 UK Commented, added singleton, added notifications. + 2005-03-02 UK Created. + ========================================================================== */ + +// ----------------------------------------------------------------------------- +// Headers: +// ----------------------------------------------------------------------------- + +#import "UKFNSubscribeFileWatcher.h" +#import + + +// ----------------------------------------------------------------------------- +// Prototypes: +// ----------------------------------------------------------------------------- + +void UKFileSubscriptionProc(FNMessage message, OptionBits flags, void *refcon, FNSubscriptionRef subscription); + + +@implementation UKFNSubscribeFileWatcher + +// ----------------------------------------------------------------------------- +// sharedFileWatcher: +// Singleton accessor. +// ----------------------------------------------------------------------------- + ++(id) sharedFileWatcher +{ + static UKFNSubscribeFileWatcher* sSharedFileWatcher = nil; + + if( !sSharedFileWatcher ) + sSharedFileWatcher = [[UKFNSubscribeFileWatcher alloc] init]; // This is a singleton, and thus an intentional "leak". + + return sSharedFileWatcher; +} + + +// ----------------------------------------------------------------------------- +// * CONSTRUCTOR: +// ----------------------------------------------------------------------------- + +-(id) init +{ + self = [super init]; + if( !self ) + return nil; + + subscriptions = [[NSMutableDictionary alloc] init]; + + return self; +} + + +// ----------------------------------------------------------------------------- +// * DESTRUCTOR: +// ----------------------------------------------------------------------------- + +-(void) dealloc +{ + NSEnumerator* enny = [subscriptions objectEnumerator]; + NSValue* subValue = nil; + + while( (subValue = [enny nextObject]) ) + { + FNSubscriptionRef subscription = [subValue pointerValue]; + FNUnsubscribe( subscription ); + } + + [subscriptions release]; + [super dealloc]; +} + + +// ----------------------------------------------------------------------------- +// addPath: +// Start watching the object at the specified path. This only sends write +// notifications for all changes, as FNSubscribe doesn't tell what actually +// changed about our folder. +// ----------------------------------------------------------------------------- + +-(void) addPath: (NSString*)path +{ + OSStatus err = noErr; + static FNSubscriptionUPP subscriptionUPP = NULL; + FNSubscriptionRef subscription = NULL; + + if( !subscriptionUPP ) + subscriptionUPP = NewFNSubscriptionUPP( UKFileSubscriptionProc ); + + err = FNSubscribeByPath( (UInt8*) [path fileSystemRepresentation], subscriptionUPP, (void*)self, + kNilOptions, &subscription ); + if( err != noErr ) + { + NSLog( @"UKFNSubscribeFileWatcher addPath: %@ failed due to error ID=%ld.", path, err ); + return; + } + + [subscriptions setObject: [NSValue valueWithPointer: subscription] forKey: path]; +} + + +// ----------------------------------------------------------------------------- +// removePath: +// Stop watching the object at the specified path. +// ----------------------------------------------------------------------------- + +-(void) removePath: (NSString*)path +{ + NSValue* subValue = nil; + @synchronized( self ) + { + subValue = [[[subscriptions objectForKey: path] retain] autorelease]; + [subscriptions removeObjectForKey: path]; + } + + if( subValue ) + { + FNSubscriptionRef subscription = [subValue pointerValue]; + + FNUnsubscribe( subscription ); + } +} + + +// ----------------------------------------------------------------------------- +// sendDelegateMessage:forSubscription: +// Bottleneck for change notifications. This is called by our callback +// function to actually inform the delegate and send out notifications. +// +// This *only* sends out write notifications, as FNSubscribe doesn't tell +// what changed about our folder. +// ----------------------------------------------------------------------------- + +-(void) sendDelegateMessage: (FNMessage)message forSubscription: (FNSubscriptionRef)subscription +{ + NSValue* subValue = [NSValue valueWithPointer: subscription]; + NSString* path = [[subscriptions allKeysForObject: subValue] objectAtIndex: 0]; + + [[[NSWorkspace sharedWorkspace] notificationCenter] postNotificationName: UKFileWatcherWriteNotification + object: self + userInfo: [NSDictionary dictionaryWithObjectsAndKeys: path, @"path", nil]]; + + [delegate watcher: self receivedNotification: UKFileWatcherWriteNotification forPath: path]; + //NSLog( @"UKFNSubscribeFileWatcher noticed change to %@", path ); // DEBUG ONLY! +} + + + +// ----------------------------------------------------------------------------- +// delegate: +// Accessor for file watcher delegate. +// ----------------------------------------------------------------------------- + +-(id) delegate +{ + return delegate; +} + + +// ----------------------------------------------------------------------------- +// setDelegate: +// Mutator for file watcher delegate. +// ----------------------------------------------------------------------------- + +-(void) setDelegate: (id)newDelegate +{ + delegate = newDelegate; +} + + +@end + + +// ----------------------------------------------------------------------------- +// UKFileSubscriptionProc: +// Callback function we hand to Carbon so it can tell us when something +// changed about our watched folders. We set the refcon to a pointer to +// our object. This simply extracts the object and hands the info off to +// sendDelegateMessage:forSubscription: which does the actual work. +// ----------------------------------------------------------------------------- + +void UKFileSubscriptionProc( FNMessage message, OptionBits flags, void *refcon, FNSubscriptionRef subscription ) +{ + UKFNSubscribeFileWatcher* obj = (UKFNSubscribeFileWatcher*) refcon; + + if( message == kFNDirectoryModifiedMessage ) // No others exist as of 10.4 + [obj sendDelegateMessage: message forSubscription: subscription]; + else + NSLog( @"UKFileSubscriptionProc: Unknown message %d", message ); +} \ No newline at end of file diff --git a/FileDrawer/UKKQueue/UKFileWatcher.h b/FileDrawer/UKKQueue/UKFileWatcher.h new file mode 100644 index 000000000..c2c4ba28c --- /dev/null +++ b/FileDrawer/UKKQueue/UKFileWatcher.h @@ -0,0 +1,62 @@ +/* ============================================================================= + FILE: UKFileWatcher.h + PROJECT: Filie + + COPYRIGHT: (c) 2005 M. Uli Kusterer, all rights reserved. + + AUTHORS: M. Uli Kusterer - UK + + LICENSES: MIT License + + REVISIONS: + 2006-03-13 UK Moved notification constants to .m file. + 2005-02-25 UK Created. + ========================================================================== */ + +/* + This is a protocol that file change notification classes should adopt. + That way, no matter whether you use Carbon's FNNotify/FNSubscribe, BSD's + kqueue or whatever, the object being notified can react to change + notifications the same way, and you can easily swap one out for the other + to cater to different OS versions, target volumes etc. +*/ + +// ----------------------------------------------------------------------------- +// Protocol: +// ----------------------------------------------------------------------------- + +@protocol UKFileWatcher + +// +(id) sharedFileWatcher; // Singleton accessor. Not officially part of the protocol, but use this name if you provide a singleton. + +-(void) addPath: (NSString*)path; +-(void) removePath: (NSString*)path; + +-(id) delegate; +-(void) setDelegate: (id)newDelegate; + +@end + +// ----------------------------------------------------------------------------- +// Methods delegates need to provide: +// ----------------------------------------------------------------------------- + +@interface NSObject (UKFileWatcherDelegate) + +-(void) watcher: (id)kq receivedNotification: (NSString*)nm forPath: (NSString*)fpath; + +@end + + +// Notifications this sends: +/* object = the file watcher object + userInfo.path = file path watched + These notifications are sent via the NSWorkspace notification center */ +extern NSString* UKFileWatcherRenameNotification; +extern NSString* UKFileWatcherWriteNotification; +extern NSString* UKFileWatcherDeleteNotification; +extern NSString* UKFileWatcherAttributeChangeNotification; +extern NSString* UKFileWatcherSizeIncreaseNotification; +extern NSString* UKFileWatcherLinkCountChangeNotification; +extern NSString* UKFileWatcherAccessRevocationNotification; + diff --git a/FileDrawer/UKKQueue/UKFileWatcher.m b/FileDrawer/UKKQueue/UKFileWatcher.m new file mode 100644 index 000000000..952cf95fd --- /dev/null +++ b/FileDrawer/UKKQueue/UKFileWatcher.m @@ -0,0 +1,38 @@ +/* ============================================================================= + FILE: UKKQueue.m + PROJECT: Filie + + COPYRIGHT: (c) 2005-06 M. Uli Kusterer, all rights reserved. + + AUTHORS: M. Uli Kusterer - UK + + LICENSES: MIT License + + REVISIONS: + 2006-03-13 UK Created, moved notification constants here as exportable + symbols. + ========================================================================== */ + +// ----------------------------------------------------------------------------- +// Headers: +// ----------------------------------------------------------------------------- + +#import +#import "UKFileWatcher.h" + + +// ----------------------------------------------------------------------------- +// Constants: +// ----------------------------------------------------------------------------- + +// Do not rely on the actual contents of these constants. They will eventually +// be changed to be more generic and less KQueue-specific. + +NSString* UKFileWatcherRenameNotification = @"UKKQueueFileRenamedNotification"; +NSString* UKFileWatcherWriteNotification = @"UKKQueueFileWrittenToNotification"; +NSString* UKFileWatcherDeleteNotification = @"UKKQueueFileDeletedNotification"; +NSString* UKFileWatcherAttributeChangeNotification = @"UKKQueueFileAttributesChangedNotification"; +NSString* UKFileWatcherSizeIncreaseNotification = @"UKKQueueFileSizeIncreasedNotification"; +NSString* UKFileWatcherLinkCountChangeNotification = @"UKKQueueFileLinkCountChangedNotification"; +NSString* UKFileWatcherAccessRevocationNotification = @"UKKQueueFileAccessRevocationNotification"; + diff --git a/FileDrawer/UKKQueue/UKKQueue Readme.txt b/FileDrawer/UKKQueue/UKKQueue Readme.txt new file mode 100644 index 000000000..582e3da96 --- /dev/null +++ b/FileDrawer/UKKQueue/UKKQueue Readme.txt @@ -0,0 +1,32 @@ +UKKQUEUE +-------- + +A wrapper class around the kqueue file change notification mechanism. + +Simply create a UKKQueue (or use the singleton), add a few paths to it and listen to the change notifications via NSWorkspace's notification center. + +LICENSE: + +(c) 2003-06 by M. Uli Kusterer. You may redistribute, modify, use in +commercial products free of charge, however distributing modified copies +requires that you clearly mark them as having been modified by you, while +maintaining the original markings and copyrights. I don't like getting bug +reports about code I wasn't involved in. + +I'd also appreciate if you gave credit in your app's about screen or a similar +place. A simple "Thanks to M. Uli Kusterer" is quite sufficient. +Also, I rarely turn down any postcards, gifts, complementary copies of +applications etc. + + +REVISION HISTORY: +0.1 - Initial release. +0.2 - Now calls delegate on main thread using UKMainThreadProxy, and checks retain count to make sure the object is released even when the thread is still holding on to it. Equivalent to SVN revision 79. +0.3 - Now adopts UKFileWatcher protocol to allow swapping out a kqueue for another scheme easily. Uses O_EVONLY instead of O_RDONLY to open the file without preventing it from being deleted or its drive ejected. +0.4 - Now includes UKFNSubscribeFileWatcher, and closes the kqueue file descriptor in a separate thread (thanks to Dominic Yu for the suggestion!) so you don't have to wait for close() to time out. +0.5 - Turns off all deprecated features. Changes the notifications to make it possible to subscribe to them more selectively. Changes notification constants to be safer for apps that expose KQueue to their plugins. FNSubscribeFileWatcher now also sends notifications (sorry, "write" only). + + +CONTACT: +Get the newest version at http://www.zathras.de +E-Mail me at witness (at) zathras (dot) de or witness (dot) of (dot) teachtext (at) gmx (dot) net diff --git a/FileDrawer/UKKQueue/UKKQueue.h b/FileDrawer/UKKQueue/UKKQueue.h new file mode 100644 index 000000000..facd513cb --- /dev/null +++ b/FileDrawer/UKKQueue/UKKQueue.h @@ -0,0 +1,122 @@ +/* ============================================================================= + FILE: UKKQueue.h + PROJECT: Filie + + COPYRIGHT: (c) 2003 M. Uli Kusterer, all rights reserved. + + AUTHORS: M. Uli Kusterer - UK + + LICENSES: MIT License + + REVISIONS: + 2006-03-13 UK Clarified license, streamlined UKFileWatcher stuff, + Changed notifications to be useful and turned off by + default some deprecated stuff. + 2003-12-21 UK Created. + ========================================================================== */ + +// ----------------------------------------------------------------------------- +// Headers: +// ----------------------------------------------------------------------------- + +#import +#include +#include +#import "UKFileWatcher.h" + + +// ----------------------------------------------------------------------------- +// Constants: +// ----------------------------------------------------------------------------- + +// Backwards compatibility constants. Don't rely on code commented out with these constants, because it may be deleted in a future version. +#ifndef UKKQUEUE_BACKWARDS_COMPATIBLE +#define UKKQUEUE_BACKWARDS_COMPATIBLE 0 // 1 to send old-style kqueue:receivedNotification:forFile: messages to objects that accept them. +#endif + +#ifndef UKKQUEUE_SEND_STUPID_NOTIFICATIONS +#define UKKQUEUE_SEND_STUPID_NOTIFICATIONS 0 // 1 to send old-style notifications that have the path as the object and no userInfo dictionary. +#endif + +#ifndef UKKQUEUE_OLD_SINGLETON_ACCESSOR_NAME +#define UKKQUEUE_OLD_SINGLETON_ACCESSOR_NAME 0 // 1 to allow use of sharedQueue instead of sharedFileWatcher. +#endif + +#ifndef UKKQUEUE_OLD_NOTIFICATION_NAMES +#define UKKQUEUE_OLD_NOTIFICATION_NAMES 0 // 1 to allow use of old KQueue-style notification names instead of the new more generic ones in UKFileWatcher. +#endif + +// Flags for notifyingAbout: +#define UKKQueueNotifyAboutRename NOTE_RENAME // Item was renamed. +#define UKKQueueNotifyAboutWrite NOTE_WRITE // Item contents changed (also folder contents changed). +#define UKKQueueNotifyAboutDelete NOTE_DELETE // item was removed. +#define UKKQueueNotifyAboutAttributeChange NOTE_ATTRIB // Item attributes changed. +#define UKKQueueNotifyAboutSizeIncrease NOTE_EXTEND // Item size increased. +#define UKKQueueNotifyAboutLinkCountChanged NOTE_LINK // Item's link count changed. +#define UKKQueueNotifyAboutAccessRevocation NOTE_REVOKE // Access to item was revoked. + +// Notifications this sends: +// (see UKFileWatcher) +// Old names: *deprecated* +#if UKKQUEUE_OLD_NOTIFICATION_NAMES +#define UKKQueueFileRenamedNotification UKFileWatcherRenameNotification +#define UKKQueueFileWrittenToNotification UKFileWatcherWriteNotification +#define UKKQueueFileDeletedNotification UKFileWatcherDeleteNotification +#define UKKQueueFileAttributesChangedNotification UKFileWatcherAttributeChangeNotification +#define UKKQueueFileSizeIncreasedNotification UKFileWatcherSizeIncreaseNotification +#define UKKQueueFileLinkCountChangedNotification UKFileWatcherLinkCountChangeNotification +#define UKKQueueFileAccessRevocationNotification UKFileWatcherAccessRevocationNotification +#endif + + +// ----------------------------------------------------------------------------- +// UKKQueue: +// ----------------------------------------------------------------------------- + +@interface UKKQueue : NSObject +{ + int queueFD; // The actual queue ID (Unix file descriptor). + NSMutableArray* watchedPaths; // List of NSStrings containing the paths we're watching. + NSMutableArray* watchedFDs; // List of NSNumbers containing the file descriptors we're watching. + id delegate; // Gets messages about changes instead of notification center, if specified. + id delegateProxy; // Proxy object to which we send messages so they reach delegate on the main thread. + BOOL alwaysNotify; // Send notifications even if we have a delegate? Defaults to NO. + BOOL keepThreadRunning; // Termination criterion of our thread. +} + ++(id) sharedFileWatcher; // Returns a singleton, a shared kqueue object Handy if you're subscribing to the notifications. Use this, or just create separate objects using alloc/init. Whatever floats your boat. + +-(int) queueFD; // I know you unix geeks want this... + +// High-level file watching: (use UKFileWatcher protocol methods instead, where possible!) +-(void) addPathToQueue: (NSString*)path; +-(void) addPathToQueue: (NSString*)path notifyingAbout: (u_int)fflags; +-(void) removePathFromQueue: (NSString*)path; + +-(id) delegate; +-(void) setDelegate: (id)newDelegate; + +-(BOOL) alwaysNotify; +-(void) setAlwaysNotify: (BOOL)n; + +#if UKKQUEUE_OLD_SINGLETON_ACCESSOR_NAME ++(UKKQueue*) sharedQueue; +#endif + +// private: +-(void) watcherThread: (id)sender; +-(void) postNotification: (NSString*)nm forFile: (NSString*)fp; // Message-posting bottleneck. + +@end + + +// ----------------------------------------------------------------------------- +// Methods delegates need to provide: +// * DEPRECATED * use UKFileWatcher delegate methods instead! +// ----------------------------------------------------------------------------- + +@interface NSObject (UKKQueueDelegate) + +-(void) kqueue: (UKKQueue*)kq receivedNotification: (NSString*)nm forFile: (NSString*)fpath; + +@end diff --git a/FileDrawer/UKKQueue/UKKQueue.m b/FileDrawer/UKKQueue/UKKQueue.m new file mode 100644 index 000000000..3217ef49f --- /dev/null +++ b/FileDrawer/UKKQueue/UKKQueue.m @@ -0,0 +1,480 @@ +/* ============================================================================= + FILE: UKKQueue.m + PROJECT: Filie + + COPYRIGHT: (c) 2003 M. Uli Kusterer, all rights reserved. + + AUTHORS: M. Uli Kusterer - UK + + LICENSES: MIT License + + REVISIONS: + 2006-03-13 UK Clarified license, streamlined UKFileWatcher stuff, + Changed notifications to be useful and turned off by + default some deprecated stuff. + 2004-12-28 UK Several threading fixes. + 2003-12-21 UK Created. + ========================================================================== */ + +// ----------------------------------------------------------------------------- +// Headers: +// ----------------------------------------------------------------------------- + +#import "UKKQueue.h" +#import "UKMainThreadProxy.h" +#import +#import + + +// ----------------------------------------------------------------------------- +// Macros: +// ----------------------------------------------------------------------------- + +// @synchronized isn't available prior to 10.3, so we use a typedef so +// this class is thread-safe on Panther but still compiles on older OSs. + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_3 +#define AT_SYNCHRONIZED(n) @synchronized(n) +#else +#define AT_SYNCHRONIZED(n) +#endif + + +// ----------------------------------------------------------------------------- +// Globals: +// ----------------------------------------------------------------------------- + +static UKKQueue * gUKKQueueSharedQueueSingleton = nil; + + +@implementation UKKQueue + +// Deprecated: +#if UKKQUEUE_OLD_SINGLETON_ACCESSOR_NAME ++(UKKQueue*) sharedQueue +{ + return [self sharedFileWatcher]; +} +#endif + +// ----------------------------------------------------------------------------- +// sharedQueue: +// Returns a singleton queue object. In many apps (especially those that +// subscribe to the notifications) there will only be one kqueue instance, +// and in that case you can use this. +// +// For all other cases, feel free to create additional instances to use +// independently. +// +// REVISIONS: +// 2006-03-13 UK Renamed from sharedQueue. +// 2005-07-02 UK Created. +// ----------------------------------------------------------------------------- + ++(id) sharedFileWatcher +{ + AT_SYNCHRONIZED( self ) + { + if( !gUKKQueueSharedQueueSingleton ) + gUKKQueueSharedQueueSingleton = [[UKKQueue alloc] init]; // This is a singleton, and thus an intentional "leak". + } + + return gUKKQueueSharedQueueSingleton; +} + + +// ----------------------------------------------------------------------------- +// * CONSTRUCTOR: +// Creates a new KQueue and starts that thread we use for our +// notifications. +// +// REVISIONS: +// 2004-11-12 UK Doesn't pass self as parameter to watcherThread anymore, +// because detachNewThreadSelector retains target and args, +// which would cause us to never be released. +// 2004-03-13 UK Documented. +// ----------------------------------------------------------------------------- + +-(id) init +{ + self = [super init]; + if( self ) + { + queueFD = kqueue(); + if( queueFD == -1 ) + { + [self release]; + return nil; + } + + watchedPaths = [[NSMutableArray alloc] init]; + watchedFDs = [[NSMutableArray alloc] init]; + + // Start new thread that fetches and processes our events: + keepThreadRunning = YES; + [NSThread detachNewThreadSelector:@selector(watcherThread:) toTarget:self withObject:nil]; + } + + return self; +} + + +// ----------------------------------------------------------------------------- +// release: +// Since NSThread retains its target, we need this method to terminate the +// thread when we reach a retain-count of two. The thread is terminated by +// setting keepThreadRunning to NO. +// +// REVISIONS: +// 2004-11-12 UK Created. +// ----------------------------------------------------------------------------- + +-(oneway void) release +{ + AT_SYNCHRONIZED(self) + { + //NSLog(@"%@ (%d)", self, [self retainCount]); + if( [self retainCount] == 2 && keepThreadRunning ) + keepThreadRunning = NO; + } + + [super release]; +} + +// ----------------------------------------------------------------------------- +// * DESTRUCTOR: +// Releases the kqueue again. +// +// REVISIONS: +// 2004-03-13 UK Documented. +// ----------------------------------------------------------------------------- + +-(void) dealloc +{ + delegate = nil; + [delegateProxy release]; + + if( keepThreadRunning ) + keepThreadRunning = NO; + + // Close all our file descriptors so the files can be deleted: + NSEnumerator* enny = [watchedFDs objectEnumerator]; + NSNumber* fdNum; + while( (fdNum = [enny nextObject]) ) + { + if( close( [fdNum intValue] ) == -1 ) + NSLog(@"dealloc: Couldn't close file descriptor (%d)", errno); + } + + [watchedPaths release]; + watchedPaths = nil; + [watchedFDs release]; + watchedFDs = nil; + + [super dealloc]; + + //NSLog(@"kqueue released."); +} + + +// ----------------------------------------------------------------------------- +// queueFD: +// Returns a Unix file descriptor for the KQueue this uses. The descriptor +// is owned by this object. Do not close it! +// +// REVISIONS: +// 2004-03-13 UK Documented. +// ----------------------------------------------------------------------------- + +-(int) queueFD +{ + return queueFD; +} + + +// ----------------------------------------------------------------------------- +// addPathToQueue: +// Tell this queue to listen for all interesting notifications sent for +// the object at the specified path. If you want more control, use the +// addPathToQueue:notifyingAbout: variant instead. +// +// REVISIONS: +// 2004-03-13 UK Documented. +// ----------------------------------------------------------------------------- + +-(void) addPathToQueue: (NSString*)path +{ + [self addPath: path]; +} + + +-(void) addPath: (NSString*)path +{ + [self addPathToQueue: path notifyingAbout: UKKQueueNotifyAboutRename + | UKKQueueNotifyAboutWrite + | UKKQueueNotifyAboutDelete + | UKKQueueNotifyAboutAttributeChange]; +} + + +// ----------------------------------------------------------------------------- +// addPathToQueue:notfyingAbout: +// Tell this queue to listen for the specified notifications sent for +// the object at the specified path. +// +// REVISIONS: +// 2005-06-29 UK Files are now opened using O_EVTONLY instead of O_RDONLY +// which allows ejecting or deleting watched files/folders. +// Thanks to Phil Hargett for finding this flag in the docs. +// 2004-03-13 UK Documented. +// ----------------------------------------------------------------------------- + +-(void) addPathToQueue: (NSString*)path notifyingAbout: (u_int)fflags +{ + struct timespec nullts = { 0, 0 }; + struct kevent ev; + int fd = open( [path fileSystemRepresentation], O_EVTONLY, 0 ); + + if( fd >= 0 ) + { + EV_SET( &ev, fd, EVFILT_VNODE, + EV_ADD | EV_ENABLE | EV_CLEAR, + fflags, 0, (void*)path ); + + AT_SYNCHRONIZED( self ) + { + [watchedPaths addObject: path]; + [watchedFDs addObject: [NSNumber numberWithInt: fd]]; + kevent( queueFD, &ev, 1, NULL, 0, &nullts ); + } + } +} + + +-(void) removePath: (NSString*)path +{ + [self removePathFromQueue: path]; +} + + +// ----------------------------------------------------------------------------- +// removePathFromQueue: +// Stop listening for changes to the specified path. This removes all +// notifications. Use this to balance both addPathToQueue:notfyingAbout: +// as well as addPathToQueue:. +// +// REVISIONS: +// 2004-03-13 UK Documented. +// ----------------------------------------------------------------------------- + +-(void) removePathFromQueue: (NSString*)path +{ + int index = 0; + int fd = -1; + + AT_SYNCHRONIZED( self ) + { + index = [watchedPaths indexOfObject: path]; + + if( index == NSNotFound ) + return; + + fd = [[watchedFDs objectAtIndex: index] intValue]; + + [watchedFDs removeObjectAtIndex: index]; + [watchedPaths removeObjectAtIndex: index]; + } + + if( close( fd ) == -1 ) + NSLog(@"removePathFromQueue: Couldn't close file descriptor (%d)", errno); +} + + +// ----------------------------------------------------------------------------- +// removeAllPathsFromQueue: +// Stop listening for changes to all paths. This removes all +// notifications. +// +// REVISIONS: +// 2004-12-28 UK Added as suggested by bbum. +// ----------------------------------------------------------------------------- + +-(void) removeAllPathsFromQueue; +{ + AT_SYNCHRONIZED( self ) + { + NSEnumerator * fdEnumerator = [watchedFDs objectEnumerator]; + NSNumber * anFD; + + while( (anFD = [fdEnumerator nextObject]) != nil ) + close( [anFD intValue] ); + + [watchedFDs removeAllObjects]; + [watchedPaths removeAllObjects]; + } +} + + +// ----------------------------------------------------------------------------- +// watcherThread: +// This method is called by our NSThread to loop and poll for any file +// changes that our kqueue wants to tell us about. This sends separate +// notifications for the different kinds of changes that can happen. +// All messages are sent via the postNotification:forFile: main bottleneck. +// +// This also calls sharedWorkspace's noteFileSystemChanged. +// +// To terminate this method (and its thread), set keepThreadRunning to NO. +// +// REVISIONS: +// 2005-08-27 UK Changed to use keepThreadRunning instead of kqueueFD +// being -1 as termination criterion, and to close the +// queue in this thread so the main thread isn't blocked. +// 2004-11-12 UK Fixed docs to include termination criterion, added +// timeout to make sure the bugger gets disposed. +// 2004-03-13 UK Documented. +// ----------------------------------------------------------------------------- + +-(void) watcherThread: (id)sender +{ + int n; + struct kevent ev; + struct timespec timeout = { 5, 0 }; // 5 seconds timeout. + int theFD = queueFD; // So we don't have to risk accessing iVars when the thread is terminated. + + while( keepThreadRunning ) + { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + NS_DURING + n = kevent( queueFD, NULL, 0, &ev, 1, &timeout ); + if( n > 0 ) + { + if( ev.filter == EVFILT_VNODE ) + { + if( ev.fflags ) + { + NSString* fpath = [[(NSString *)ev.udata retain] autorelease]; // In case one of the notified folks removes the path. + //NSLog(@"UKKQueue: Detected file change: %@", fpath); + [[NSWorkspace sharedWorkspace] noteFileSystemChanged: fpath]; + + //NSLog(@"ev.flags = %u",ev.fflags); // DEBUG ONLY! + + if( (ev.fflags & NOTE_RENAME) == NOTE_RENAME ) + [self postNotification: UKFileWatcherRenameNotification forFile: fpath]; + if( (ev.fflags & NOTE_WRITE) == NOTE_WRITE ) + [self postNotification: UKFileWatcherWriteNotification forFile: fpath]; + if( (ev.fflags & NOTE_DELETE) == NOTE_DELETE ) + [self postNotification: UKFileWatcherDeleteNotification forFile: fpath]; + if( (ev.fflags & NOTE_ATTRIB) == NOTE_ATTRIB ) + [self postNotification: UKFileWatcherAttributeChangeNotification forFile: fpath]; + if( (ev.fflags & NOTE_EXTEND) == NOTE_EXTEND ) + [self postNotification: UKFileWatcherSizeIncreaseNotification forFile: fpath]; + if( (ev.fflags & NOTE_LINK) == NOTE_LINK ) + [self postNotification: UKFileWatcherLinkCountChangeNotification forFile: fpath]; + if( (ev.fflags & NOTE_REVOKE) == NOTE_REVOKE ) + [self postNotification: UKFileWatcherAccessRevocationNotification forFile: fpath]; + } + } + } + NS_HANDLER + NSLog(@"Error in UKKQueue watcherThread: %@",localException); + NS_ENDHANDLER + + [pool release]; + } + + // Close our kqueue's file descriptor: + if( close( theFD ) == -1 ) + NSLog(@"release: Couldn't close main kqueue (%d)", errno); + + //NSLog(@"exiting kqueue watcher thread."); +} + + +// ----------------------------------------------------------------------------- +// postNotification:forFile: +// This is the main bottleneck for posting notifications. If you don't want +// the notifications to go through NSWorkspace, override this method and +// send them elsewhere. +// +// REVISIONS: +// 2004-02-27 UK Changed this to send new notification, and the old one +// only to objects that respond to it. The old category on +// NSObject could cause problems with the proxy itself. +// 2004-10-31 UK Helloween fun: Make this use a mainThreadProxy and +// allow sending the notification even if we have a +// delegate. +// 2004-03-13 UK Documented. +// ----------------------------------------------------------------------------- + +-(void) postNotification: (NSString*)nm forFile: (NSString*)fp +{ + if( delegateProxy ) + { + #if UKKQUEUE_BACKWARDS_COMPATIBLE + if( ![delegateProxy respondsToSelector: @selector(watcher:receivedNotification:forPath:)] ) + [delegateProxy kqueue: self receivedNotification: nm forFile: fp]; + else + #endif + [delegateProxy watcher: self receivedNotification: nm forPath: fp]; + } + + if( !delegateProxy || alwaysNotify ) + { + #if UKKQUEUE_SEND_STUPID_NOTIFICATIONS + [[[NSWorkspace sharedWorkspace] notificationCenter] postNotificationName: nm object: fp]; + #else + [[[NSWorkspace sharedWorkspace] notificationCenter] postNotificationName: nm object: self + userInfo: [NSDictionary dictionaryWithObjectsAndKeys: fp, @"path", nil]]; + #endif + } +} + +-(id) delegate +{ + return delegate; +} + +-(void) setDelegate: (id)newDelegate +{ + id oldProxy = delegateProxy; + delegate = newDelegate; + delegateProxy = [delegate copyMainThreadProxy]; + [oldProxy release]; +} + +// ----------------------------------------------------------------------------- +// Flag to send a notification even if we have a delegate: +// ----------------------------------------------------------------------------- + +-(BOOL) alwaysNotify +{ + return alwaysNotify; +} + + +-(void) setAlwaysNotify: (BOOL)n +{ + alwaysNotify = n; +} + + +// ----------------------------------------------------------------------------- +// description: +// This method can be used to help in debugging. It provides the value +// used by NSLog & co. when you request to print this object using the +// %@ format specifier. +// +// REVISIONS: +// 2004-11-12 UK Created. +// ----------------------------------------------------------------------------- + +-(NSString*) description +{ + return [NSString stringWithFormat: @"%@ { watchedPaths = %@, alwaysNotify = %@ }", NSStringFromClass([self class]), watchedPaths, (alwaysNotify? @"YES" : @"NO") ]; +} + +@end + + diff --git a/FileDrawer/UKKQueue/UKMainThreadProxy.h b/FileDrawer/UKKQueue/UKMainThreadProxy.h new file mode 100644 index 000000000..9fd6ff453 --- /dev/null +++ b/FileDrawer/UKKQueue/UKMainThreadProxy.h @@ -0,0 +1,56 @@ +/* ============================================================================= + FILE: UKMainThreadProxy.h + PROJECT: UKMainThreadProxy + + PURPOSE: Send a message to object theObject to [theObject mainThreadProxy] + instead and the message will be received on the main thread by + theObject. + + COPYRIGHT: (c) 2004 M. Uli Kusterer, all rights reserved. + + AUTHORS: M. Uli Kusterer - UK + + LICENSES: MIT License + + REVISIONS: + 2006-03-13 UK Clarified license. + 2004-10-14 UK Created. + ========================================================================== */ + +// ----------------------------------------------------------------------------- +// Headers: +// ----------------------------------------------------------------------------- + +#import + + +// ----------------------------------------------------------------------------- +// Categories: +// ----------------------------------------------------------------------------- + +@interface NSObject (UKMainThreadProxy) + +-(id) mainThreadProxy; // You can't init or release this object. +-(id) copyMainThreadProxy; // Gives you a retained version. + +@end + + +// ----------------------------------------------------------------------------- +// Classes: +// ----------------------------------------------------------------------------- + +/* + This object is created as a proxy in a second thread for an existing object. + All messages you send to this object will automatically be sent to the other + object on the main thread, except NSObject methods like retain/release etc. +*/ + +@interface UKMainThreadProxy : NSObject +{ + IBOutlet id target; +} + +-(id) initWithTarget: (id)targ; + +@end diff --git a/FileDrawer/UKKQueue/UKMainThreadProxy.m b/FileDrawer/UKKQueue/UKMainThreadProxy.m new file mode 100644 index 000000000..c3517980e --- /dev/null +++ b/FileDrawer/UKKQueue/UKMainThreadProxy.m @@ -0,0 +1,151 @@ +/* ============================================================================= + FILE: UKMainThreadProxy.h + PROJECT: UKMainThreadProxy + + PURPOSE: Send a message to object theObject to [theObject mainThreadProxy] + instead and the message will be received on the main thread by + theObject. + + COPYRIGHT: (c) 2004 M. Uli Kusterer, all rights reserved. + + AUTHORS: M. Uli Kusterer - UK + + LICENSES: MIT Licenseā + + REVISIONS: + 2006-03-13 UK Clarified license. + 2004-10-14 UK Created. + ========================================================================== */ + +// ----------------------------------------------------------------------------- +// Headers: +// ----------------------------------------------------------------------------- + +#import "UKMainThreadProxy.h" + + +@implementation UKMainThreadProxy + +-(id) initWithTarget: (id)targ +{ + self = [super init]; + if( self ) + target = targ; + + return self; +} + + +// ----------------------------------------------------------------------------- +// Introspection overrides: +// ----------------------------------------------------------------------------- + +-(BOOL) respondsToSelector: (SEL)itemAction +{ + BOOL does = [super respondsToSelector: itemAction]; + + return( does || [target respondsToSelector: itemAction] ); +} + + +-(id) performSelector: (SEL)itemAction +{ + BOOL does = [super respondsToSelector: itemAction]; + if( does ) + return [super performSelector: itemAction]; + + if( ![target respondsToSelector: itemAction] ) + [self doesNotRecognizeSelector: itemAction]; + + [target retain]; + [target performSelectorOnMainThread: itemAction withObject: nil waitUntilDone: YES]; + [target release]; + + return nil; +} + + +-(id) performSelector: (SEL)itemAction withObject: (id)obj +{ + BOOL does = [super respondsToSelector: itemAction]; + if( does ) + return [super performSelector: itemAction withObject: obj]; + + if( ![target respondsToSelector: itemAction] ) + [self doesNotRecognizeSelector: itemAction]; + + [target retain]; + [obj retain]; + [target performSelectorOnMainThread: itemAction withObject: obj waitUntilDone: YES]; + [obj release]; + [target release]; + + return nil; +} + + +// ----------------------------------------------------------------------------- +// Forwarding unknown methods to the target: +// ----------------------------------------------------------------------------- + +-(NSMethodSignature*) methodSignatureForSelector: (SEL)itemAction +{ + NSMethodSignature* sig = [super methodSignatureForSelector: itemAction]; + + if( sig ) + return sig; + + return [target methodSignatureForSelector: itemAction]; +} + +-(void) forwardInvocation: (NSInvocation*)invocation +{ + SEL itemAction = [invocation selector]; + + if( [target respondsToSelector: itemAction] ) + { + [invocation retainArguments]; + [target retain]; + [invocation performSelectorOnMainThread: @selector(invokeWithTarget:) withObject: target waitUntilDone: YES]; + [target release]; + } + else + [self doesNotRecognizeSelector: itemAction]; +} + + +// ----------------------------------------------------------------------------- +// Safety net: +// ----------------------------------------------------------------------------- + +-(id) mainThreadProxy // Just in case someone accidentally sends this message to a main thread proxy. +{ + return self; +} + +-(id) copyMainThreadProxy // Just in case someone accidentally sends this message to a main thread proxy. +{ + return [self retain]; +} + +@end + + +// ----------------------------------------------------------------------------- +// Shorthand notation for getting a main thread proxy: +// ----------------------------------------------------------------------------- + +@implementation NSObject (UKMainThreadProxy) + +-(id) mainThreadProxy +{ + return [[[UKMainThreadProxy alloc] initWithTarget: self] autorelease]; +} + +-(id) copyMainThreadProxy +{ + return [[UKMainThreadProxy alloc] initWithTarget: self]; +} + +@end + diff --git a/Icons/add_blue.png b/Icons/add_blue.png new file mode 100644 index 000000000..61847fd66 Binary files /dev/null and b/Icons/add_blue.png differ diff --git a/Icons/add_gray.png b/Icons/add_gray.png new file mode 100644 index 000000000..968403820 Binary files /dev/null and b/Icons/add_gray.png differ diff --git a/Icons/file_blue.png b/Icons/file_blue.png new file mode 100644 index 000000000..69496b547 Binary files /dev/null and b/Icons/file_blue.png differ diff --git a/Icons/file_gray.png b/Icons/file_gray.png new file mode 100644 index 000000000..a7d80ff92 Binary files /dev/null and b/Icons/file_gray.png differ diff --git a/Icons/info_blue.png b/Icons/info_blue.png index e1b94824f..a5ff8b468 100644 Binary files a/Icons/info_blue.png and b/Icons/info_blue.png differ diff --git a/Icons/info_gray.png b/Icons/info_gray.png index 6bb1c9673..ee25687a9 100644 Binary files a/Icons/info_gray.png and b/Icons/info_gray.png differ diff --git a/Icons/loop_blue_1.png b/Icons/loop_blue_1.png deleted file mode 100644 index 5095256fd..000000000 Binary files a/Icons/loop_blue_1.png and /dev/null differ diff --git a/Icons/loop_blue_2.png b/Icons/loop_blue_2.png deleted file mode 100644 index 51f3067b5..000000000 Binary files a/Icons/loop_blue_2.png and /dev/null differ diff --git a/Icons/loop_gray.png b/Icons/loop_gray.png deleted file mode 100644 index afe05ac7e..000000000 Binary files a/Icons/loop_gray.png and /dev/null differ diff --git a/Icons/minus_blue.png b/Icons/minus_blue.png deleted file mode 100644 index 1acbf334a..000000000 Binary files a/Icons/minus_blue.png and /dev/null differ diff --git a/Icons/minus_gray.png b/Icons/minus_gray.png deleted file mode 100644 index 5f732551a..000000000 Binary files a/Icons/minus_gray.png and /dev/null differ diff --git a/Icons/next_blue.png b/Icons/next_blue.png index 4b900360c..4c484fbe9 100644 Binary files a/Icons/next_blue.png and b/Icons/next_blue.png differ diff --git a/Icons/next_gray.png b/Icons/next_gray.png index ffe3cbadd..4bc36cec5 100644 Binary files a/Icons/next_gray.png and b/Icons/next_gray.png differ diff --git a/Icons/pause_blue.png b/Icons/pause_blue.png index 44f14b183..90e4a4f3e 100644 Binary files a/Icons/pause_blue.png and b/Icons/pause_blue.png differ diff --git a/Icons/pause_gray.png b/Icons/pause_gray.png index 3f10c61b1..58e2247c2 100644 Binary files a/Icons/pause_gray.png and b/Icons/pause_gray.png differ diff --git a/Icons/play_blue.png b/Icons/play_blue.png index 931519a96..a37af3ec7 100644 Binary files a/Icons/play_blue.png and b/Icons/play_blue.png differ diff --git a/Icons/play_gray.png b/Icons/play_gray.png index acccb65e3..938c9b90c 100644 Binary files a/Icons/play_gray.png and b/Icons/play_gray.png differ diff --git a/Icons/playlist_blue.png b/Icons/playlist_blue.png deleted file mode 100644 index 72f88e2d6..000000000 Binary files a/Icons/playlist_blue.png and /dev/null differ diff --git a/Icons/playlist_gray.png b/Icons/playlist_gray.png deleted file mode 100644 index d604fc145..000000000 Binary files a/Icons/playlist_gray.png and /dev/null differ diff --git a/Icons/plus_blue.png b/Icons/plus_blue.png deleted file mode 100644 index 5e64185db..000000000 Binary files a/Icons/plus_blue.png and /dev/null differ diff --git a/Icons/plus_gray.png b/Icons/plus_gray.png deleted file mode 100644 index 62fa3a3a5..000000000 Binary files a/Icons/plus_gray.png and /dev/null differ diff --git a/Icons/prev_blue.png b/Icons/prev_blue.png new file mode 100644 index 000000000..582d39d60 Binary files /dev/null and b/Icons/prev_blue.png differ diff --git a/Icons/prev_gray.png b/Icons/prev_gray.png new file mode 100644 index 000000000..aa1cac9dc Binary files /dev/null and b/Icons/prev_gray.png differ diff --git a/Icons/previous_blue.png b/Icons/previous_blue.png deleted file mode 100644 index 15c7c8ee4..000000000 Binary files a/Icons/previous_blue.png and /dev/null differ diff --git a/Icons/previous_gray.png b/Icons/previous_gray.png deleted file mode 100644 index 4684c3f90..000000000 Binary files a/Icons/previous_gray.png and /dev/null differ diff --git a/Icons/random_blue.png b/Icons/random_blue.png deleted file mode 100644 index 825fa4a30..000000000 Binary files a/Icons/random_blue.png and /dev/null differ diff --git a/Icons/random_gray.png b/Icons/random_gray.png deleted file mode 100644 index 103ae6f95..000000000 Binary files a/Icons/random_gray.png and /dev/null differ diff --git a/Icons/remove_blue.png b/Icons/remove_blue.png new file mode 100644 index 000000000..6c55e6d92 Binary files /dev/null and b/Icons/remove_blue.png differ diff --git a/Icons/remove_gray.png b/Icons/remove_gray.png new file mode 100644 index 000000000..28d639497 Binary files /dev/null and b/Icons/remove_gray.png differ diff --git a/Icons/repeat_off.png b/Icons/repeat_off.png new file mode 100644 index 000000000..e6b7a36d7 Binary files /dev/null and b/Icons/repeat_off.png differ diff --git a/Icons/repeat_on.png b/Icons/repeat_on.png new file mode 100644 index 000000000..7a9c58b16 Binary files /dev/null and b/Icons/repeat_on.png differ diff --git a/Icons/shuffle_off.png b/Icons/shuffle_off.png new file mode 100644 index 000000000..ed69a0c38 Binary files /dev/null and b/Icons/shuffle_off.png differ diff --git a/Icons/shuffle_on.png b/Icons/shuffle_on.png new file mode 100644 index 000000000..80d29d576 Binary files /dev/null and b/Icons/shuffle_on.png differ diff --git a/Playlist/PlaylistController.m b/Playlist/PlaylistController.m index ccf7b0000..23aad1359 100644 --- a/Playlist/PlaylistController.m +++ b/Playlist/PlaylistController.m @@ -202,7 +202,7 @@ dropOperation:(NSTableViewDropOperation)op { int i; - NSLog(@"DRAGGING?"); + NSLog(@"DROPPED"); [super tableView:tv acceptDrop:info row:row dropOperation:op]; if ([info draggingSource] == tableView) { @@ -231,7 +231,7 @@ row = 0; NSArray *files = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType]; - NSLog(@"INSERTING PATHS"); + NSLog(@"INSERTING PATHS: %@", files); [self insertPaths:files atIndex:row sort:YES]; NSLog(@"UPDATING"); diff --git a/Sound/BufferChain.m b/Sound/BufferChain.m index 31c944417..8914cad7e 100644 --- a/Sound/BufferChain.m +++ b/Sound/BufferChain.m @@ -78,8 +78,8 @@ NSLog(@"SEEKING IN BUFFERCHIAN"); [inputNode seek:time]; - [inputNode resetBuffer]; [converterNode resetBuffer]; + [inputNode resetBuffer]; } - (void)endOfInputReached diff --git a/Sound/SoundFile/CoreAudioFile.h b/Sound/SoundFile/CoreAudioFile.h index b66d5c80a..caee34a3e 100644 --- a/Sound/SoundFile/CoreAudioFile.h +++ b/Sound/SoundFile/CoreAudioFile.h @@ -23,7 +23,7 @@ #include #import "SoundFile.h" -#define _USE_WRAPPER_ +#undef _USE_WRAPPER_ @interface CoreAudioFile : SoundFile { diff --git a/Sound/SoundFile/CoreAudioFile.m b/Sound/SoundFile/CoreAudioFile.m index 5e5ce896f..553bd4778 100644 --- a/Sound/SoundFile/CoreAudioFile.m +++ b/Sound/SoundFile/CoreAudioFile.m @@ -224,7 +224,7 @@ OSStatus writeFunc(void * inRefCon, SInt64 inPosition, ByteCount requestCount, c } size = sizeof(totalFrames); -// err = ExtAudioFileGetProperty(_in, kExtAudioFileProperty_FileLengthFrames, &size, &totalFrames); + err = ExtAudioFileGetProperty(_in, kExtAudioFileProperty_FileLengthFrames, &size, &totalFrames); if(err != noErr) { err = ExtAudioFileDispose(_in); return NO; @@ -256,7 +256,7 @@ OSStatus writeFunc(void * inRefCon, SInt64 inPosition, ByteCount requestCount, c bitsPerSample = 16; } - totalSize = totalFrames * channels * (bitsPerSample / 8); + totalSize = totalFrames * channels * (bitsPerSample / 8); // Set output format AudioStreamBasicDescription result; diff --git a/Sound/SoundFile/MADFile.h b/Sound/SoundFile/MADFile.h index 8e584e478..c5256364d 100644 --- a/Sound/SoundFile/MADFile.h +++ b/Sound/SoundFile/MADFile.h @@ -28,6 +28,12 @@ FILE *_inFd; BOOL _seekSkip; + + //For gapless playback of mp3s + BOOL _gapless; + long _currentFrame; + int _startPadding; + int _endPadding; } @end diff --git a/Sound/SoundFile/MADFile.m b/Sound/SoundFile/MADFile.m index 058bc7632..b273ab029 100644 --- a/Sound/SoundFile/MADFile.m +++ b/Sound/SoundFile/MADFile.m @@ -15,6 +15,8 @@ /*XING FUN*/ #define XING_MAGIC (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g') +#define INFO_MAGIC (('I' << 24) | ('n' << 16) | ('f' << 8) | 'o') +#define LAME_MAGIC (('L' << 24) | ('A' << 16) | ('M' << 8) | 'E') struct xing { @@ -25,6 +27,11 @@ struct xing long scale; /* ?? */ }; +struct lame +{ + long flags; +}; + enum { XING_FRAMES = 0x00000001L, @@ -33,29 +40,52 @@ enum XING_SCALE = 0x00000008L }; -int xing_parse(struct xing *xing, struct mad_bitptr ptr, unsigned int bitlen) +int lame_parse(struct lame *lame, struct mad_bitptr *ptr, unsigned int bitlen) +{ + unsigned long magic; + unsigned long garbage; + + magic = mad_bit_read(ptr, 32); //4 bytes + + if (magic != LAME_MAGIC) + return 0; + + mad_bit_skip(ptr, 17*8); //17 bytes skipped + garbage = mad_bit_read(ptr, 24); //3 bytes +// _startPadding = (garbage >> 12) & 0x000FFF; +// _endPadding = garbage & 0x000FFF; + + return 1; +} + +int xing_parse(struct xing *xing, struct mad_bitptr *ptr, unsigned int bitlen) { xing->flags = 0; + unsigned long magic; - if (bitlen < 64 || mad_bit_read(&ptr, 32) != XING_MAGIC) - goto fail; + if (bitlen < 64) + return 0; + + magic = mad_bit_read(ptr, 32); + if (magic != INFO_MAGIC && magic != XING_MAGIC) + return 0; - xing->flags = mad_bit_read(&ptr, 32); + xing->flags = mad_bit_read(ptr, 32); bitlen -= 64; if (xing->flags & XING_FRAMES) { if (bitlen < 32) - goto fail; + return 0; - xing->frames = mad_bit_read(&ptr, 32); + xing->frames = mad_bit_read(ptr, 32); bitlen -= 32; } if (xing->flags & XING_BYTES) { if (bitlen < 32) - goto fail; + return 0; - xing->bytes = mad_bit_read(&ptr, 32); + xing->bytes = mad_bit_read(ptr, 32); bitlen -= 32; } @@ -63,27 +93,38 @@ int xing_parse(struct xing *xing, struct mad_bitptr ptr, unsigned int bitlen) int i; if (bitlen < 800) - goto fail; + return 0; for (i = 0; i < 100; ++i) - xing->toc[i] = mad_bit_read(&ptr, 8); + xing->toc[i] = mad_bit_read(ptr, 8); bitlen -= 800; } if (xing->flags & XING_SCALE) { if (bitlen < 32) - goto fail; + return 0; - xing->scale = mad_bit_read(&ptr, 32); + xing->scale = mad_bit_read(ptr, 32); bitlen -= 32; } - return 0; + return 1; +} + +int parse_headers(struct xing *xing, struct lame *lame, struct mad_bitptr ptr, unsigned int bitlen) +{ + xing->flags = 0; + lame->flags = 0; -fail: - xing->flags = 0; - return -1; + if (xing_parse(xing, &ptr, bitlen)) + { + lame_parse(lame, &ptr, bitlen); + + return 1; + } + + return 0; } @@ -96,6 +137,7 @@ fail: struct mad_header header; struct mad_frame frame; /* to read xing data */ struct xing xing; + struct lame lame; int remainder = 0; int data_used = 0; int len = 0; @@ -175,7 +217,7 @@ fail: if (mad_frame_decode(&frame, &stream) == -1) continue; - if (xing_parse (&xing, stream.anc_ptr, stream.anc_bitlen) == 0) + if (parse_headers(&xing, &lame, stream.anc_ptr, stream.anc_bitlen)) { has_xing = YES; vbr = YES; @@ -427,7 +469,7 @@ static inline signed int scale (mad_fixed_t sample) mad_stream_buffer(&_stream, _inputBuffer, len); _stream.error = 0; - if (_seekSkip) +/* if (_seekSkip) { int skip = 2; do @@ -444,7 +486,7 @@ static inline signed int scale (mad_fixed_t sample) _seekSkip = NO; } - +*/ } if (mad_frame_decode(&_frame, &_stream) == -1) { if (!MAD_RECOVERABLE (_stream.error)) @@ -514,12 +556,14 @@ static inline signed int scale (mad_fixed_t sample) new_position = ((double) seconds / (double) total_seconds) * _fileSize; fseek(_inFd, new_position, SEEK_SET); - mad_frame_mute(&_frame); - mad_synth_mute(&_synth); + mad_stream_sync(&_stream); _stream.error = MAD_ERROR_BUFLEN; _stream.sync = 0; _outputAvailable = 0; + mad_frame_mute(&_frame); + mad_synth_mute(&_synth); + _seekSkip = YES; return seconds*1000.0;