An audio player for macOS 10.8 and newer. https://kode54.net/cog
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1015 lines
27KB

  1. //
  2. // PlaylistController.m
  3. // Cog
  4. //
  5. // Created by Vincent Spader on 3/18/05.
  6. // Copyright 2005 Vincent Spader All rights reserved.
  7. //
  8. #import "PlaylistController.h"
  9. #import "PlaylistEntry.h"
  10. #import "PlaylistLoader.h"
  11. #import "PlaybackController.h"
  12. #import "Shuffle.h"
  13. #import "SpotlightWindowController.h"
  14. #import "RepeatTransformers.h"
  15. #import "ShuffleTransformers.h"
  16. #import "StatusImageTransformer.h"
  17. #import "ToggleQueueTitleTransformer.h"
  18. #import "Logging.h"
  19. #define UNDO_STACK_LIMIT 0
  20. @implementation PlaylistController
  21. @synthesize currentEntry;
  22. @synthesize totalTime;
  23. + (void)initialize {
  24. NSValueTransformer *repeatNoneTransformer = [[RepeatModeTransformer alloc] initWithMode:RepeatNone];
  25. [NSValueTransformer setValueTransformer:repeatNoneTransformer
  26. forName:@"RepeatNoneTransformer"];
  27. NSValueTransformer *repeatOneTransformer = [[RepeatModeTransformer alloc] initWithMode:RepeatOne];
  28. [NSValueTransformer setValueTransformer:repeatOneTransformer
  29. forName:@"RepeatOneTransformer"];
  30. NSValueTransformer *repeatAlbumTransformer = [[RepeatModeTransformer alloc] initWithMode:RepeatAlbum];
  31. [NSValueTransformer setValueTransformer:repeatAlbumTransformer
  32. forName:@"RepeatAlbumTransformer"];
  33. NSValueTransformer *repeatAllTransformer = [[RepeatModeTransformer alloc] initWithMode:RepeatAll];
  34. [NSValueTransformer setValueTransformer:repeatAllTransformer
  35. forName:@"RepeatAllTransformer"];
  36. NSValueTransformer *repeatModeImageTransformer = [[RepeatModeImageTransformer alloc] init];
  37. [NSValueTransformer setValueTransformer:repeatModeImageTransformer
  38. forName:@"RepeatModeImageTransformer"];
  39. NSValueTransformer *shuffleOffTransformer = [[ShuffleModeTransformer alloc] initWithMode:ShuffleOff];
  40. [NSValueTransformer setValueTransformer:shuffleOffTransformer
  41. forName:@"ShuffleOffTransformer"];
  42. NSValueTransformer *shuffleAlbumsTransformer = [[ShuffleModeTransformer alloc] initWithMode:ShuffleAlbums];
  43. [NSValueTransformer setValueTransformer:shuffleAlbumsTransformer
  44. forName:@"ShuffleAlbumsTransformer"];
  45. NSValueTransformer *shuffleAllTransformer = [[ShuffleModeTransformer alloc] initWithMode:ShuffleAll];
  46. [NSValueTransformer setValueTransformer:shuffleAllTransformer
  47. forName:@"ShuffleAllTransformer"];
  48. NSValueTransformer *shuffleImageTransformer = [[ShuffleImageTransformer alloc] init];
  49. [NSValueTransformer setValueTransformer:shuffleImageTransformer
  50. forName:@"ShuffleImageTransformer"];
  51. NSValueTransformer *statusImageTransformer = [[StatusImageTransformer alloc] init];
  52. [NSValueTransformer setValueTransformer:statusImageTransformer
  53. forName:@"StatusImageTransformer"];
  54. NSValueTransformer *toggleQueueTitleTransformer = [[ToggleQueueTitleTransformer alloc] init];
  55. [NSValueTransformer setValueTransformer:toggleQueueTitleTransformer
  56. forName:@"ToggleQueueTitleTransformer"];
  57. }
  58. - (void)initDefaults
  59. {
  60. NSDictionary *defaultsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
  61. [NSNumber numberWithInteger:RepeatNone], @"repeat",
  62. [NSNumber numberWithInteger:ShuffleOff], @"shuffle",
  63. nil];
  64. [[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary];
  65. }
  66. - (id)initWithCoder:(NSCoder *)decoder
  67. {
  68. self = [super initWithCoder:decoder];
  69. if (self)
  70. {
  71. shuffleList = [[NSMutableArray alloc] init];
  72. queueList = [[NSMutableArray alloc] init];
  73. undoManager = [[NSUndoManager alloc] init];
  74. [undoManager setLevelsOfUndo:UNDO_STACK_LIMIT];
  75. [self initDefaults];
  76. }
  77. return self;
  78. }
  79. - (void)awakeFromNib
  80. {
  81. [super awakeFromNib];
  82. [self addObserver:self forKeyPath:@"arrangedObjects" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
  83. }
  84. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  85. {
  86. if ([keyPath isEqualToString:@"arrangedObjects"])
  87. {
  88. [self updatePlaylistIndexes];
  89. [self updateTotalTime];
  90. }
  91. }
  92. - (void)updatePlaylistIndexes
  93. {
  94. int i;
  95. NSArray *arranged = [self arrangedObjects];
  96. for (i = 0; i < [arranged count]; i++)
  97. {
  98. PlaylistEntry *pe = [arranged objectAtIndex:i];
  99. if (pe.index != i) //Make sure we don't get into some kind of crazy observing loop...
  100. pe.index = i;
  101. }
  102. }
  103. - (void)updateTotalTime
  104. {
  105. double tt = 0;
  106. ldiv_t hoursAndMinutes;
  107. ldiv_t daysAndHours;
  108. for (PlaylistEntry *pe in [self arrangedObjects]) {
  109. if (!isnan([pe.length doubleValue]))
  110. tt += [pe.length doubleValue];
  111. }
  112. long sec = (long)(tt);
  113. hoursAndMinutes = ldiv(sec/60, 60);
  114. if ( hoursAndMinutes.quot >= 24 )
  115. {
  116. daysAndHours = ldiv(hoursAndMinutes.quot, 24);
  117. [self setTotalTime:[NSString stringWithFormat:@"%ld days %ld hours %ld minutes %ld seconds", daysAndHours.quot, daysAndHours.rem, hoursAndMinutes.rem, sec%60]];
  118. }
  119. else
  120. [self setTotalTime:[NSString stringWithFormat:@"%ld hours %ld minutes %ld seconds", hoursAndMinutes.quot, hoursAndMinutes.rem, sec%60]];
  121. }
  122. - (void)tableView:(NSTableView *)tableView
  123. didClickTableColumn:(NSTableColumn *)tableColumn
  124. {
  125. if ([self shuffle] != ShuffleOff)
  126. [self resetShuffleList];
  127. }
  128. - (NSString *)tableView:(NSTableView *)tv toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tc row:(int)row mouseLocation:(NSPoint)mouseLocation
  129. {
  130. DLog(@"GETTING STATUS FOR ROW: %i: %@!", row, [[[self arrangedObjects] objectAtIndex:row] statusMessage]);
  131. return [[[self arrangedObjects] objectAtIndex:row] statusMessage];
  132. }
  133. -(void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet*)indexSet
  134. toIndex:(unsigned int)insertIndex
  135. {
  136. [super moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:insertIndex];
  137. NSUInteger lowerIndex = insertIndex;
  138. NSUInteger index = insertIndex;
  139. while (NSNotFound != lowerIndex) {
  140. lowerIndex = [indexSet indexLessThanIndex:lowerIndex];
  141. if (lowerIndex != NSNotFound)
  142. index = lowerIndex;
  143. }
  144. [playbackController playlistDidChange:self];
  145. }
  146. - (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
  147. {
  148. [super tableView:aTableView writeRowsWithIndexes:rowIndexes toPasteboard:pboard];
  149. NSMutableArray *filenames = [NSMutableArray array];
  150. NSInteger row;
  151. for (row = [rowIndexes firstIndex];
  152. row <= [rowIndexes lastIndex];
  153. row = [rowIndexes indexGreaterThanIndex:row])
  154. {
  155. PlaylistEntry *song = [[self arrangedObjects] objectAtIndex:row];
  156. [filenames addObject:[[song path] stringByExpandingTildeInPath]];
  157. }
  158. [pboard addTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:self];
  159. [pboard setPropertyList:filenames forType:NSFilenamesPboardType];
  160. return YES;
  161. }
  162. - (BOOL)tableView:(NSTableView*)tv
  163. acceptDrop:(id <NSDraggingInfo>)info
  164. row:(int)row
  165. dropOperation:(NSTableViewDropOperation)op
  166. {
  167. //Check if DNDArrayController handles it.
  168. if ([super tableView:tv acceptDrop:info row:row dropOperation:op])
  169. return YES;
  170. if (row < 0)
  171. row = 0;
  172. // Determine the type of object that was dropped
  173. NSArray *supportedTypes = [NSArray arrayWithObjects:CogUrlsPboardType, NSFilenamesPboardType, iTunesDropType, nil];
  174. NSPasteboard *pboard = [info draggingPasteboard];
  175. NSString *bestType = [pboard availableTypeFromArray:supportedTypes];
  176. NSMutableArray *acceptedURLs = [[NSMutableArray alloc] init];
  177. // Get files from an file drawer drop
  178. if ([bestType isEqualToString:CogUrlsPboardType]) {
  179. NSArray *urls = [NSUnarchiver unarchiveObjectWithData:[[info draggingPasteboard] dataForType:CogUrlsPboardType]];
  180. DLog(@"URLS: %@", urls);
  181. //[playlistLoader insertURLs: urls atIndex:row sort:YES];
  182. [acceptedURLs addObjectsFromArray:urls];
  183. }
  184. // Get files from a normal file drop (such as from Finder)
  185. if ([bestType isEqualToString:NSFilenamesPboardType]) {
  186. NSMutableArray *urls = [[NSMutableArray alloc] init];
  187. for (NSString *file in [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType])
  188. {
  189. [urls addObject:[NSURL fileURLWithPath:file]];
  190. }
  191. //[playlistLoader insertURLs:urls atIndex:row sort:YES];
  192. [acceptedURLs addObjectsFromArray:urls];
  193. }
  194. // Get files from an iTunes drop
  195. if ([bestType isEqualToString:iTunesDropType]) {
  196. NSDictionary *iTunesDict = [pboard propertyListForType:iTunesDropType];
  197. NSDictionary *tracks = [iTunesDict valueForKey:@"Tracks"];
  198. // Convert the iTunes URLs to URLs....MWAHAHAH!
  199. NSMutableArray *urls = [[NSMutableArray alloc] init];
  200. for (NSDictionary *trackInfo in [tracks allValues]) {
  201. [urls addObject:[NSURL URLWithString:[trackInfo valueForKey:@"Location"]]];
  202. }
  203. //[playlistLoader insertURLs:urls atIndex:row sort:YES];
  204. [acceptedURLs addObjectsFromArray:urls];
  205. }
  206. if ([acceptedURLs count])
  207. {
  208. [self willInsertURLs:acceptedURLs origin:URLOriginInternal];
  209. if (![[self content] count]) {
  210. row = 0;
  211. }
  212. NSArray* entries = [playlistLoader insertURLs:acceptedURLs atIndex:row sort:YES];
  213. [self didInsertURLs:entries origin:URLOriginInternal];
  214. }
  215. if ([self shuffle] != ShuffleOff)
  216. [self resetShuffleList];
  217. return YES;
  218. }
  219. - (NSUndoManager *)undoManager
  220. {
  221. return undoManager;
  222. }
  223. - (NSIndexSet *)disarrangeIndexes:(NSIndexSet *)indexes
  224. {
  225. if ([[self arrangedObjects] count] <= [indexes lastIndex])
  226. return indexes;
  227. NSMutableIndexSet *disarrangedIndexes = [[NSMutableIndexSet alloc] init];
  228. NSUInteger index = [indexes firstIndex];
  229. while (index != NSNotFound)
  230. {
  231. [disarrangedIndexes addIndex:[[self content] indexOfObject:[[self arrangedObjects] objectAtIndex:index]]];
  232. index = [indexes indexGreaterThanIndex:index];
  233. }
  234. return disarrangedIndexes;
  235. }
  236. - (NSArray *)disarrangeObjects:(NSArray *)objects
  237. {
  238. NSMutableArray *disarrangedObjects = [[NSMutableArray alloc] init];
  239. for (PlaylistEntry *pe in [self content])
  240. {
  241. if ([objects containsObject:pe])
  242. [disarrangedObjects addObject:pe];
  243. }
  244. return disarrangedObjects;
  245. }
  246. - (NSIndexSet *)rearrangeIndexes:(NSIndexSet *)indexes
  247. {
  248. if ([[self content] count] <= [indexes lastIndex])
  249. return indexes;
  250. NSMutableIndexSet *rearrangedIndexes = [[NSMutableIndexSet alloc] init];
  251. NSUInteger index = [indexes firstIndex];
  252. while (index != NSNotFound)
  253. {
  254. [rearrangedIndexes addIndex:[[self arrangedObjects] indexOfObject:[[self content] objectAtIndex:index]]];
  255. index = [indexes indexGreaterThanIndex:index];
  256. }
  257. return rearrangedIndexes;
  258. }
  259. - (void)insertObjects:(NSArray *)objects atIndexes:(NSIndexSet *)indexes
  260. {
  261. [self insertObjects:objects atArrangedObjectIndexes:indexes];
  262. [self rearrangeObjects];
  263. }
  264. - (void)insertObjects:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes
  265. {
  266. [[[self undoManager] prepareWithInvocationTarget:self] removeObjectsAtIndexes:[self disarrangeIndexes:indexes]];
  267. NSString *actionName = [NSString stringWithFormat:@"Adding %lu entries", (unsigned long)[objects count]];
  268. [[self undoManager] setActionName:actionName];
  269. [super insertObjects:objects atArrangedObjectIndexes:indexes];
  270. if ([self shuffle] != ShuffleOff)
  271. [self resetShuffleList];
  272. }
  273. - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes
  274. {
  275. [self removeObjectsAtArrangedObjectIndexes:[self rearrangeIndexes:indexes]];
  276. }
  277. - (void)removeObjectsAtArrangedObjectIndexes:(NSIndexSet *)indexes
  278. {
  279. NSArray *objects = [[self arrangedObjects] objectsAtIndexes:indexes];
  280. [[[self undoManager] prepareWithInvocationTarget:self] insertObjects:[self disarrangeObjects:objects] atIndexes:[self disarrangeIndexes:indexes]];
  281. NSString *actionName = [NSString stringWithFormat:@"Removing %lu entries", (unsigned long)[indexes count]];
  282. [[self undoManager] setActionName:actionName];
  283. DLog(@"Removing indexes: %@", indexes);
  284. DLog(@"Current index: %i", currentEntry.index);
  285. NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init];
  286. for (PlaylistEntry *pe in objects)
  287. {
  288. [unarrangedIndexes addIndex:[pe index]];
  289. }
  290. if (currentEntry.index >= 0 && [unarrangedIndexes containsIndex:currentEntry.index])
  291. {
  292. currentEntry.index = -currentEntry.index - 1;
  293. DLog(@"Current removed: %i", currentEntry.index);
  294. }
  295. if (currentEntry.index < 0) //Need to update the negative index
  296. {
  297. int i = -currentEntry.index - 1;
  298. DLog(@"I is %i", i);
  299. int j;
  300. for (j = i - 1; j >= 0; j--)
  301. {
  302. if ([unarrangedIndexes containsIndex:j]) {
  303. DLog(@"Removing 1");
  304. i--;
  305. }
  306. }
  307. currentEntry.index = -i - 1;
  308. }
  309. [super removeObjectsAtArrangedObjectIndexes:indexes];
  310. if ([self shuffle] != ShuffleOff)
  311. [self resetShuffleList];
  312. [playbackController playlistDidChange:self];
  313. }
  314. - (void)setSortDescriptors:(NSArray *)sortDescriptors
  315. {
  316. DLog(@"Current: %@, setting: %@", [self sortDescriptors], sortDescriptors);
  317. //Cheap hack so the index column isn't sorted
  318. if (([sortDescriptors count] != 0) && [[[sortDescriptors objectAtIndex:0] key] caseInsensitiveCompare:@"index"] == NSOrderedSame)
  319. {
  320. //Remove the sort descriptors
  321. [super setSortDescriptors:nil];
  322. [self rearrangeObjects];
  323. return;
  324. }
  325. [super setSortDescriptors:sortDescriptors];
  326. [self rearrangeObjects];
  327. [playbackController playlistDidChange:self];
  328. }
  329. - (IBAction)randomizeList:(id)sender
  330. {
  331. [self setSortDescriptors:nil];
  332. NSArray *unrandomized = [self content];
  333. [[[self undoManager] prepareWithInvocationTarget:self] unrandomizeList:unrandomized];
  334. [self setContent:[Shuffle shuffleList:[self content]]];
  335. if ([self shuffle] != ShuffleOff)
  336. [self resetShuffleList];
  337. [[self undoManager] setActionName:@"Playlist Randomization"];
  338. }
  339. - (void)unrandomizeList:(NSArray *)entries
  340. {
  341. [[[self undoManager] prepareWithInvocationTarget:self] randomizeList:self];
  342. [self setContent:entries];
  343. }
  344. - (IBAction)toggleShuffle:(id)sender
  345. {
  346. ShuffleMode shuffle = [self shuffle];
  347. if (shuffle == ShuffleOff) {
  348. [self setShuffle: ShuffleAlbums];
  349. }
  350. else if (shuffle == ShuffleAlbums) {
  351. [self setShuffle: ShuffleAll];
  352. }
  353. else if (shuffle == ShuffleAll) {
  354. [self setShuffle: ShuffleOff];
  355. }
  356. }
  357. - (IBAction)toggleRepeat:(id)sender
  358. {
  359. RepeatMode repeat = [self repeat];
  360. if (repeat == RepeatNone) {
  361. [self setRepeat: RepeatOne];
  362. }
  363. else if (repeat == RepeatOne) {
  364. [self setRepeat: RepeatAlbum];
  365. }
  366. else if (repeat == RepeatAlbum) {
  367. [self setRepeat: RepeatAll];
  368. }
  369. else if (repeat == RepeatAll) {
  370. [self setRepeat: RepeatNone];
  371. }
  372. }
  373. - (PlaylistEntry *)entryAtIndex:(int)i
  374. {
  375. RepeatMode repeat = [self repeat];
  376. if (i < 0 || i >= [[self arrangedObjects] count] ) {
  377. if ( repeat != RepeatAll )
  378. return nil;
  379. while ( i < 0 )
  380. i += [[self arrangedObjects] count];
  381. if ( i >= [[self arrangedObjects] count])
  382. i %= [[self arrangedObjects] count];
  383. }
  384. return [[self arrangedObjects] objectAtIndex:i];
  385. }
  386. - (void)remove:(id)sender {
  387. // It's a kind of magic.
  388. // Plain old NSArrayController's remove: isn't working properly for some reason.
  389. // The method is definitely called but (overridden) removeObjectsAtArrangedObjectIndexes: isn't called
  390. // and no entries are removed.
  391. // Putting explicit call to removeObjectsAtArrangedObjectIndexes: here for now.
  392. // TODO: figure it out
  393. NSIndexSet *selected = [self selectionIndexes];
  394. if ([selected count] > 0)
  395. {
  396. [self removeObjectsAtArrangedObjectIndexes:selected];
  397. }
  398. }
  399. - (IBAction)removeDuplicates:(id)sender {
  400. NSMutableArray *originals = [[NSMutableArray alloc] init];
  401. NSMutableArray *duplicates = [[NSMutableArray alloc] init];
  402. for (PlaylistEntry *pe in [self content])
  403. {
  404. if ([originals containsObject:[pe URL]])
  405. [duplicates addObject:pe];
  406. else
  407. [originals addObject:[pe URL]];
  408. }
  409. if ([duplicates count] > 0)
  410. {
  411. NSArray * arrangedContent = [self arrangedObjects];
  412. NSMutableIndexSet * duplicatesIndex = [[NSMutableIndexSet alloc] init];
  413. for (PlaylistEntry *pe in duplicates)
  414. {
  415. [duplicatesIndex addIndex:[arrangedContent indexOfObject:pe]];
  416. }
  417. [self removeObjectsAtArrangedObjectIndexes:duplicatesIndex];
  418. }
  419. }
  420. - (IBAction)removeDeadItems:(id)sender {
  421. NSMutableArray *deadItems = [[NSMutableArray alloc] init];
  422. for (PlaylistEntry *pe in [self content])
  423. {
  424. NSURL *url = [pe URL];
  425. if ([url isFileURL])
  426. if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]])
  427. [deadItems addObject:pe];
  428. }
  429. if ([deadItems count] > 0)
  430. {
  431. NSArray * arrangedContent = [self arrangedObjects];
  432. NSMutableIndexSet * deadItemsIndex = [[NSMutableIndexSet alloc] init];
  433. for (PlaylistEntry *pe in deadItems)
  434. {
  435. [deadItemsIndex addIndex:[arrangedContent indexOfObject:pe]];
  436. }
  437. [self removeObjectsAtArrangedObjectIndexes:deadItemsIndex];
  438. }
  439. }
  440. - (PlaylistEntry *)shuffledEntryAtIndex:(int)i
  441. {
  442. RepeatMode repeat = [self repeat];
  443. while (i < 0)
  444. {
  445. if (repeat == RepeatAll)
  446. {
  447. [self addShuffledListToFront];
  448. //change i appropriately
  449. i += [[self arrangedObjects] count];
  450. }
  451. else
  452. {
  453. return nil;
  454. }
  455. }
  456. while (i >= [shuffleList count])
  457. {
  458. if (repeat == RepeatAll)
  459. {
  460. [self addShuffledListToBack];
  461. }
  462. else
  463. {
  464. return nil;
  465. }
  466. }
  467. return [shuffleList objectAtIndex:i];
  468. }
  469. - (PlaylistEntry *)getNextEntry:(PlaylistEntry *)pe
  470. {
  471. return [self getNextEntry:pe ignoreRepeatOne:NO];
  472. }
  473. - (PlaylistEntry *)getNextEntry:(PlaylistEntry *)pe ignoreRepeatOne:(BOOL)ignoreRepeatOne
  474. {
  475. if (!ignoreRepeatOne && [self repeat] == RepeatOne)
  476. {
  477. return pe;
  478. }
  479. if ([queueList count] > 0)
  480. {
  481. pe = [queueList objectAtIndex:0];
  482. [queueList removeObjectAtIndex:0];
  483. pe.queued = NO;
  484. [pe setQueuePosition:-1];
  485. int i;
  486. for (i = 0; i < [queueList count]; i++)
  487. {
  488. PlaylistEntry *queueItem = [queueList objectAtIndex:i];
  489. [queueItem setQueuePosition: i];
  490. }
  491. return pe;
  492. }
  493. if ([self shuffle] != ShuffleOff)
  494. {
  495. return [self shuffledEntryAtIndex:(pe.shuffleIndex + 1)];
  496. }
  497. else
  498. {
  499. int i;
  500. if (pe.index < 0) //Was a current entry, now removed.
  501. {
  502. i = -pe.index - 1;
  503. }
  504. else
  505. {
  506. i = pe.index + 1;
  507. }
  508. if ([self repeat] == RepeatAlbum)
  509. {
  510. PlaylistEntry *next = [self entryAtIndex:i];
  511. if ((i > [[self arrangedObjects] count]-1) || ([[next album] caseInsensitiveCompare:[pe album]]) || ([next album] == nil))
  512. {
  513. NSArray *filtered = [self filterPlaylistOnAlbum:[pe album]];
  514. if ([pe album] == nil)
  515. i--;
  516. else
  517. i = [(PlaylistEntry *)[filtered objectAtIndex:0] index];
  518. }
  519. }
  520. return [self entryAtIndex:i];
  521. }
  522. }
  523. - (NSArray *)filterPlaylistOnAlbum:(NSString *)album
  524. {
  525. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"album like %@",
  526. album];
  527. return [[self arrangedObjects] filteredArrayUsingPredicate:predicate];
  528. }
  529. - (PlaylistEntry *)getPrevEntry:(PlaylistEntry *)pe
  530. {
  531. return [self getPrevEntry:pe ignoreRepeatOne:NO];
  532. }
  533. - (PlaylistEntry *)getPrevEntry:(PlaylistEntry *)pe ignoreRepeatOne:(BOOL)ignoreRepeatOne
  534. {
  535. if (!ignoreRepeatOne && [self repeat] == RepeatOne)
  536. {
  537. return pe;
  538. }
  539. if ([self shuffle] != ShuffleOff)
  540. {
  541. return [self shuffledEntryAtIndex:(pe.shuffleIndex - 1)];
  542. }
  543. else
  544. {
  545. int i;
  546. if (pe.index < 0) //Was a current entry, now removed.
  547. {
  548. i = -pe.index - 2;
  549. }
  550. else
  551. {
  552. i = pe.index - 1;
  553. }
  554. return [self entryAtIndex:i];
  555. }
  556. }
  557. - (BOOL)next
  558. {
  559. PlaylistEntry *pe;
  560. pe = [self getNextEntry:[self currentEntry] ignoreRepeatOne:YES];
  561. if (pe == nil)
  562. return NO;
  563. [self setCurrentEntry:pe];
  564. return YES;
  565. }
  566. - (BOOL)prev
  567. {
  568. PlaylistEntry *pe;
  569. pe = [self getPrevEntry:[self currentEntry] ignoreRepeatOne:YES];
  570. if (pe == nil)
  571. return NO;
  572. [self setCurrentEntry:pe];
  573. return YES;
  574. }
  575. - (void)addShuffledListToFront
  576. {
  577. NSArray *newList = [Shuffle shuffleList:[self arrangedObjects]];
  578. NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newList count])];
  579. [shuffleList insertObjects:newList atIndexes:indexSet];
  580. int i;
  581. for (i = 0; i < [shuffleList count]; i++)
  582. {
  583. [[shuffleList objectAtIndex:i] setShuffleIndex:i];
  584. }
  585. }
  586. - (void)addShuffledListToBack
  587. {
  588. NSArray *newList = [Shuffle shuffleList:[self arrangedObjects]];
  589. NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange([shuffleList count], [newList count])];
  590. [shuffleList insertObjects:newList atIndexes:indexSet];
  591. int i;
  592. for (i = ([shuffleList count] - [newList count]); i < [shuffleList count]; i++)
  593. {
  594. [[shuffleList objectAtIndex:i] setShuffleIndex:i];
  595. }
  596. }
  597. - (void)resetShuffleList
  598. {
  599. [shuffleList removeAllObjects];
  600. [self addShuffledListToFront];
  601. if (currentEntry && currentEntry.index >= 0)
  602. {
  603. [shuffleList insertObject:currentEntry atIndex:0];
  604. [currentEntry setShuffleIndex:0];
  605. //Need to rejigger so the current entry is at the start now...
  606. int i;
  607. BOOL found = NO;
  608. for (i = 1; i < [shuffleList count] && !found; i++)
  609. {
  610. if ([shuffleList objectAtIndex:i] == currentEntry)
  611. {
  612. found = YES;
  613. [shuffleList removeObjectAtIndex:i];
  614. }
  615. else {
  616. [[shuffleList objectAtIndex:i] setShuffleIndex: i];
  617. }
  618. }
  619. }
  620. }
  621. - (void)setCurrentEntry:(PlaylistEntry *)pe
  622. {
  623. currentEntry.current = NO;
  624. currentEntry.stopAfter = NO;
  625. pe.current = YES;
  626. if (pe != nil)
  627. [tableView scrollRowToVisible:pe.index];
  628. currentEntry = pe;
  629. }
  630. - (void)setShuffle:(ShuffleMode)s
  631. {
  632. [[NSUserDefaults standardUserDefaults] setInteger:s forKey:@"shuffle"];
  633. if (s != ShuffleOff)
  634. [self resetShuffleList];
  635. [playbackController playlistDidChange:self];
  636. }
  637. - (ShuffleMode)shuffle
  638. {
  639. return [[NSUserDefaults standardUserDefaults] integerForKey:@"shuffle"];
  640. }
  641. - (void)setRepeat:(RepeatMode)r
  642. {
  643. [[NSUserDefaults standardUserDefaults] setInteger:r forKey:@"repeat"];
  644. [playbackController playlistDidChange:self];
  645. }
  646. - (RepeatMode)repeat
  647. {
  648. return [[NSUserDefaults standardUserDefaults] integerForKey:@"repeat"];
  649. }
  650. - (IBAction)clear:(id)sender
  651. {
  652. [self setFilterPredicate:nil];
  653. [self removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[self arrangedObjects] count])]];
  654. }
  655. - (IBAction)clearFilterPredicate:(id)sender
  656. {
  657. [self setFilterPredicate:nil];
  658. }
  659. - (void)setFilterPredicate:(NSPredicate *)filterPredicate
  660. {
  661. [super setFilterPredicate:filterPredicate];
  662. }
  663. - (IBAction)showEntryInFinder:(id)sender
  664. {
  665. NSWorkspace* ws = [NSWorkspace sharedWorkspace];
  666. NSURL *url = [[[self selectedObjects] objectAtIndex:0] URL];
  667. if ([url isFileURL])
  668. [ws selectFile:[url path] inFileViewerRootedAtPath:[url path]];
  669. }
  670. /*
  671. - (IBAction)showTagEditor:(id)sender
  672. {
  673. // call the editor & pass the url
  674. if ([self selectionIndex] < 0)
  675. return;
  676. NSURL *url = [[[self selectedObjects] objectAtIndex:0] URL];
  677. if ([url isFileURL])
  678. [TagEditorController openTagEditor:url sender:sender];
  679. }
  680. */
  681. - (IBAction)searchByArtist:(id)sender;
  682. {
  683. PlaylistEntry *entry = [[self arrangedObjects] objectAtIndex:[self selectionIndex]];
  684. [spotlightWindowController searchForArtist:[entry artist]];
  685. }
  686. - (IBAction)searchByAlbum:(id)sender;
  687. {
  688. PlaylistEntry *entry = [[self arrangedObjects] objectAtIndex:[self selectionIndex]];
  689. [spotlightWindowController searchForAlbum:[entry album]];
  690. }
  691. - (NSMutableArray *)queueList
  692. {
  693. return queueList;
  694. }
  695. - (IBAction)emptyQueueList:(id)sender
  696. {
  697. for (PlaylistEntry *queueItem in queueList)
  698. {
  699. queueItem.queued = NO;
  700. [queueItem setQueuePosition:-1];
  701. }
  702. [queueList removeAllObjects];
  703. }
  704. - (IBAction)toggleQueued:(id)sender
  705. {
  706. for (PlaylistEntry *queueItem in [self selectedObjects])
  707. {
  708. if (queueItem.queued)
  709. {
  710. queueItem.queued = NO;
  711. queueItem.queuePosition = -1;
  712. [queueList removeObject:queueItem];
  713. }
  714. else
  715. {
  716. queueItem.queued = YES;
  717. queueItem.queuePosition = [queueList count];
  718. [queueList addObject:queueItem];
  719. }
  720. DLog(@"TOGGLE QUEUED: %i", queueItem.queued);
  721. }
  722. int i = 0;
  723. for (PlaylistEntry *cur in queueList)
  724. {
  725. cur.queuePosition = i++;
  726. }
  727. }
  728. - (IBAction)stopAfterCurrent:(id)sender
  729. {
  730. currentEntry.stopAfter = !currentEntry.stopAfter;
  731. }
  732. -(BOOL)validateMenuItem:(NSMenuItem*)menuItem
  733. {
  734. SEL action = [menuItem action];
  735. if (action == @selector(removeFromQueue:))
  736. {
  737. for (PlaylistEntry *q in [self selectedObjects])
  738. if (q.queuePosition >= 0)
  739. return YES;
  740. return NO;
  741. }
  742. if (action == @selector(emptyQueueList:) && ([queueList count] < 1))
  743. return NO;
  744. if (action == @selector(stopAfterCurrent:) && currentEntry.stopAfter)
  745. return NO;
  746. // if nothing is selected, gray out these
  747. if ([[self selectedObjects] count] < 1)
  748. {
  749. if (action == @selector(remove:))
  750. return NO;
  751. if (action == @selector(addToQueue:))
  752. return NO;
  753. if (action == @selector(searchByArtist:))
  754. return NO;
  755. if (action == @selector(searchByAlbum:))
  756. return NO;
  757. }
  758. return YES;
  759. }
  760. // Event inlets:
  761. - (void)willInsertURLs:(NSArray*)urls origin:(URLOrigin)origin
  762. {
  763. if (![urls count])
  764. return;
  765. CGEventRef event = CGEventCreate(NULL /*default event source*/);
  766. CGEventFlags mods = CGEventGetFlags(event);
  767. CFRelease(event);
  768. BOOL modifierPressed = ((mods & kCGEventFlagMaskCommand)!=0)&((mods & kCGEventFlagMaskControl)!=0);
  769. modifierPressed |= ((mods & kCGEventFlagMaskShift)!=0);
  770. NSString *behavior = [[NSUserDefaults standardUserDefaults] valueForKey:@"openingFilesBehavior"];
  771. if (modifierPressed) {
  772. behavior = [[NSUserDefaults standardUserDefaults] valueForKey:@"openingFilesAlteredBehavior"];
  773. }
  774. BOOL shouldClear = modifierPressed; // By default, internal sources should not clear the playlist
  775. if (origin == URLOriginExternal) { // For external insertions, we look at the preference
  776. //possible settings are "clearAndPlay", "enqueue", "enqueueAndPlay"
  777. shouldClear = [behavior isEqualToString:@"clearAndPlay"];
  778. }
  779. if (shouldClear) {
  780. [self clear:self];
  781. }
  782. }
  783. - (void)didInsertURLs:(NSArray*)urls origin:(URLOrigin)origin
  784. {
  785. if (![urls count])
  786. return;
  787. CGEventRef event = CGEventCreate(NULL);
  788. CGEventFlags mods = CGEventGetFlags(event);
  789. CFRelease(event);
  790. BOOL modifierPressed = ((mods & kCGEventFlagMaskCommand)!=0)&((mods & kCGEventFlagMaskControl)!=0);
  791. modifierPressed |= ((mods & kCGEventFlagMaskShift)!=0);
  792. NSString *behavior = [[NSUserDefaults standardUserDefaults] valueForKey:@"openingFilesBehavior"];
  793. if (modifierPressed) {
  794. behavior = [[NSUserDefaults standardUserDefaults] valueForKey:@"openingFilesAlteredBehavior"];
  795. }
  796. BOOL shouldPlay = modifierPressed; // The default is NO for internal insertions
  797. if (origin == URLOriginExternal) { // For external insertions, we look at the preference
  798. shouldPlay = [behavior isEqualToString:@"clearAndPlay"] || [behavior isEqualToString:@"enqueueAndPlay"];;
  799. }
  800. //Auto start playback
  801. if (shouldPlay && [[self content] count] > 0) {
  802. [playbackController playEntry: [urls objectAtIndex:0]];
  803. }
  804. }
  805. @end