2005-06-02 18:16:43 +00:00
|
|
|
//
|
2008-02-10 16:16:45 +00:00
|
|
|
// PlaylistController.m
|
|
|
|
// Cog
|
2005-06-02 18:16:43 +00:00
|
|
|
//
|
2008-02-10 16:16:45 +00:00
|
|
|
// Created by Vincent Spader on 3/18/05.
|
|
|
|
// Copyright 2005 Vincent Spader All rights reserved.
|
2005-06-02 18:16:43 +00:00
|
|
|
//
|
|
|
|
|
2013-10-18 07:17:03 +00:00
|
|
|
#import "PlaylistController.h"
|
2013-10-11 19:33:58 +00:00
|
|
|
#import "PlaylistEntry.h"
|
2007-03-09 01:16:06 +00:00
|
|
|
#import "PlaylistLoader.h"
|
2008-05-09 21:24:49 +00:00
|
|
|
#import "PlaybackController.h"
|
2006-01-20 15:22:03 +00:00
|
|
|
#import "Shuffle.h"
|
2008-02-16 16:13:21 +00:00
|
|
|
#import "SpotlightWindowController.h"
|
2008-02-19 03:39:43 +00:00
|
|
|
#import "RepeatTransformers.h"
|
2009-02-28 06:40:50 +00:00
|
|
|
#import "ShuffleTransformers.h"
|
2008-02-22 02:19:46 +00:00
|
|
|
#import "StatusImageTransformer.h"
|
2008-03-08 23:57:54 +00:00
|
|
|
#import "ToggleQueueTitleTransformer.h"
|
2006-05-07 13:19:23 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
#import "Logging.h"
|
|
|
|
|
2013-10-11 19:33:58 +00:00
|
|
|
|
|
|
|
#define UNDO_STACK_LIMIT 0
|
|
|
|
|
2005-06-02 18:16:43 +00:00
|
|
|
@implementation PlaylistController
|
|
|
|
|
2008-02-23 19:46:23 +00:00
|
|
|
@synthesize currentEntry;
|
2008-02-24 17:32:50 +00:00
|
|
|
@synthesize totalTime;
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2008-02-19 03:39:43 +00:00
|
|
|
+ (void)initialize {
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *repeatNoneTransformer = [[RepeatModeTransformer alloc] initWithMode:RepeatNone];
|
2008-02-19 03:39:43 +00:00
|
|
|
[NSValueTransformer setValueTransformer:repeatNoneTransformer
|
|
|
|
forName:@"RepeatNoneTransformer"];
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *repeatOneTransformer = [[RepeatModeTransformer alloc] initWithMode:RepeatOne];
|
2008-02-19 03:39:43 +00:00
|
|
|
[NSValueTransformer setValueTransformer:repeatOneTransformer
|
|
|
|
forName:@"RepeatOneTransformer"];
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *repeatAlbumTransformer = [[RepeatModeTransformer alloc] initWithMode:RepeatAlbum];
|
2008-02-19 04:02:05 +00:00
|
|
|
[NSValueTransformer setValueTransformer:repeatAlbumTransformer
|
|
|
|
forName:@"RepeatAlbumTransformer"];
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *repeatAllTransformer = [[RepeatModeTransformer alloc] initWithMode:RepeatAll];
|
2008-02-19 03:39:43 +00:00
|
|
|
[NSValueTransformer setValueTransformer:repeatAllTransformer
|
|
|
|
forName:@"RepeatAllTransformer"];
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *repeatModeImageTransformer = [[RepeatModeImageTransformer alloc] init];
|
2008-02-19 03:39:43 +00:00
|
|
|
[NSValueTransformer setValueTransformer:repeatModeImageTransformer
|
|
|
|
forName:@"RepeatModeImageTransformer"];
|
2009-02-28 06:40:50 +00:00
|
|
|
|
2009-03-10 04:04:46 +00:00
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *shuffleOffTransformer = [[ShuffleModeTransformer alloc] initWithMode:ShuffleOff];
|
2009-03-10 04:04:46 +00:00
|
|
|
[NSValueTransformer setValueTransformer:shuffleOffTransformer
|
|
|
|
forName:@"ShuffleOffTransformer"];
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *shuffleAlbumsTransformer = [[ShuffleModeTransformer alloc] initWithMode:ShuffleAlbums];
|
2009-03-10 04:04:46 +00:00
|
|
|
[NSValueTransformer setValueTransformer:shuffleAlbumsTransformer
|
|
|
|
forName:@"ShuffleAlbumsTransformer"];
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *shuffleAllTransformer = [[ShuffleModeTransformer alloc] initWithMode:ShuffleAll];
|
2009-03-10 04:04:46 +00:00
|
|
|
[NSValueTransformer setValueTransformer:shuffleAllTransformer
|
|
|
|
forName:@"ShuffleAllTransformer"];
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *shuffleImageTransformer = [[ShuffleImageTransformer alloc] init];
|
2009-02-28 06:40:50 +00:00
|
|
|
[NSValueTransformer setValueTransformer:shuffleImageTransformer
|
|
|
|
forName:@"ShuffleImageTransformer"];
|
|
|
|
|
2009-03-10 04:04:46 +00:00
|
|
|
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *statusImageTransformer = [[StatusImageTransformer alloc] init];
|
2008-02-22 02:19:46 +00:00
|
|
|
[NSValueTransformer setValueTransformer:statusImageTransformer
|
|
|
|
forName:@"StatusImageTransformer"];
|
2008-03-08 23:57:54 +00:00
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSValueTransformer *toggleQueueTitleTransformer = [[ToggleQueueTitleTransformer alloc] init];
|
2008-03-08 23:57:54 +00:00
|
|
|
[NSValueTransformer setValueTransformer:toggleQueueTitleTransformer
|
|
|
|
forName:@"ToggleQueueTitleTransformer"];
|
2008-02-19 03:39:43 +00:00
|
|
|
}
|
|
|
|
|
2008-03-08 23:57:54 +00:00
|
|
|
|
2009-02-28 18:18:56 +00:00
|
|
|
- (void)initDefaults
|
|
|
|
{
|
|
|
|
NSDictionary *defaultsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
|
2009-03-10 04:04:46 +00:00
|
|
|
[NSNumber numberWithInteger:RepeatNone], @"repeat",
|
|
|
|
[NSNumber numberWithInteger:ShuffleOff], @"shuffle",
|
2009-02-28 18:18:56 +00:00
|
|
|
nil];
|
|
|
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-06-02 18:16:43 +00:00
|
|
|
- (id)initWithCoder:(NSCoder *)decoder
|
|
|
|
{
|
|
|
|
self = [super initWithCoder:decoder];
|
|
|
|
|
|
|
|
if (self)
|
|
|
|
{
|
|
|
|
shuffleList = [[NSMutableArray alloc] init];
|
2008-02-21 07:30:28 +00:00
|
|
|
queueList = [[NSMutableArray alloc] init];
|
2013-10-11 19:33:58 +00:00
|
|
|
|
|
|
|
undoManager = [[NSUndoManager alloc] init];
|
|
|
|
|
|
|
|
[undoManager setLevelsOfUndo:UNDO_STACK_LIMIT];
|
|
|
|
|
2009-02-28 18:18:56 +00:00
|
|
|
[self initDefaults];
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2009-02-28 18:18:56 +00:00
|
|
|
|
2008-02-24 17:16:19 +00:00
|
|
|
- (void)awakeFromNib
|
|
|
|
{
|
|
|
|
[super awakeFromNib];
|
|
|
|
|
|
|
|
[self addObserver:self forKeyPath:@"arrangedObjects" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
2008-02-24 17:32:50 +00:00
|
|
|
{
|
2008-03-03 02:09:56 +00:00
|
|
|
if ([keyPath isEqualToString:@"arrangedObjects"])
|
2008-02-24 17:32:50 +00:00
|
|
|
{
|
|
|
|
[self updatePlaylistIndexes];
|
|
|
|
[self updateTotalTime];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updatePlaylistIndexes
|
2008-02-24 17:16:19 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
NSArray *arranged = [self arrangedObjects];
|
|
|
|
for (i = 0; i < [arranged count]; i++)
|
|
|
|
{
|
|
|
|
PlaylistEntry *pe = [arranged objectAtIndex:i];
|
|
|
|
if (pe.index != i) //Make sure we don't get into some kind of crazy observing loop...
|
|
|
|
pe.index = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-24 17:32:50 +00:00
|
|
|
- (void)updateTotalTime
|
|
|
|
{
|
|
|
|
double tt = 0;
|
|
|
|
ldiv_t hoursAndMinutes;
|
2013-10-21 04:16:07 +00:00
|
|
|
ldiv_t daysAndHours;
|
2008-02-24 17:32:50 +00:00
|
|
|
|
|
|
|
for (PlaylistEntry *pe in [self arrangedObjects]) {
|
2008-02-29 20:09:59 +00:00
|
|
|
if (!isnan([pe.length doubleValue]))
|
|
|
|
tt += [pe.length doubleValue];
|
2008-02-24 17:32:50 +00:00
|
|
|
}
|
|
|
|
|
2013-10-21 04:16:07 +00:00
|
|
|
long sec = (long)(tt);
|
2008-02-24 17:32:50 +00:00
|
|
|
hoursAndMinutes = ldiv(sec/60, 60);
|
|
|
|
|
2013-10-21 04:16:07 +00:00
|
|
|
if ( hoursAndMinutes.quot >= 24 )
|
|
|
|
{
|
|
|
|
daysAndHours = ldiv(hoursAndMinutes.quot, 24);
|
|
|
|
[self setTotalTime:[NSString stringWithFormat:@"%ld days %ld hours %ld minutes %ld seconds", daysAndHours.quot, daysAndHours.rem, hoursAndMinutes.rem, sec%60]];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
[self setTotalTime:[NSString stringWithFormat:@"%ld hours %ld minutes %ld seconds", hoursAndMinutes.quot, hoursAndMinutes.rem, sec%60]];
|
2008-02-24 17:32:50 +00:00
|
|
|
}
|
|
|
|
|
2006-04-29 00:03:28 +00:00
|
|
|
- (void)tableView:(NSTableView *)tableView
|
|
|
|
didClickTableColumn:(NSTableColumn *)tableColumn
|
|
|
|
{
|
2009-03-25 03:12:52 +00:00
|
|
|
if ([self shuffle] != ShuffleOff)
|
2006-04-30 13:01:33 +00:00
|
|
|
[self resetShuffleList];
|
2006-04-29 00:03:28 +00:00
|
|
|
}
|
|
|
|
|
2008-02-22 02:19:46 +00:00
|
|
|
- (NSString *)tableView:(NSTableView *)tv toolTipForCell:(NSCell *)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tc row:(int)row mouseLocation:(NSPoint)mouseLocation
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"GETTING STATUS FOR ROW: %i: %@!", row, [[[self arrangedObjects] objectAtIndex:row] statusMessage]);
|
2008-02-22 02:19:46 +00:00
|
|
|
return [[[self arrangedObjects] objectAtIndex:row] statusMessage];
|
|
|
|
}
|
|
|
|
|
2008-02-24 15:47:04 +00:00
|
|
|
-(void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet*)indexSet
|
|
|
|
toIndex:(unsigned int)insertIndex
|
2008-02-10 19:35:58 +00:00
|
|
|
{
|
2008-02-24 15:47:04 +00:00
|
|
|
[super moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:insertIndex];
|
2008-02-10 19:35:58 +00:00
|
|
|
|
2008-02-24 15:47:04 +00:00
|
|
|
NSUInteger lowerIndex = insertIndex;
|
|
|
|
NSUInteger index = insertIndex;
|
2008-02-10 19:35:58 +00:00
|
|
|
|
2008-02-24 15:47:04 +00:00
|
|
|
while (NSNotFound != lowerIndex) {
|
|
|
|
lowerIndex = [indexSet indexLessThanIndex:lowerIndex];
|
|
|
|
|
|
|
|
if (lowerIndex != NSNotFound)
|
|
|
|
index = lowerIndex;
|
2008-02-10 19:35:58 +00:00
|
|
|
}
|
2009-02-28 18:57:21 +00:00
|
|
|
|
|
|
|
[playbackController playlistDidChange:self];
|
2008-02-24 15:47:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard
|
|
|
|
{
|
|
|
|
[super tableView:aTableView writeRowsWithIndexes:rowIndexes toPasteboard:pboard];
|
|
|
|
|
|
|
|
NSMutableArray *filenames = [NSMutableArray array];
|
|
|
|
NSInteger row;
|
|
|
|
for (row = [rowIndexes firstIndex];
|
|
|
|
row <= [rowIndexes lastIndex];
|
|
|
|
row = [rowIndexes indexGreaterThanIndex:row])
|
|
|
|
{
|
|
|
|
PlaylistEntry *song = [[self arrangedObjects] objectAtIndex:row];
|
|
|
|
[filenames addObject:[[song path] stringByExpandingTildeInPath]];
|
|
|
|
}
|
|
|
|
|
|
|
|
[pboard addTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:self];
|
|
|
|
[pboard setPropertyList:filenames forType:NSFilenamesPboardType];
|
|
|
|
|
|
|
|
return YES;
|
2008-02-10 19:35:58 +00:00
|
|
|
}
|
|
|
|
|
2005-06-02 18:16:43 +00:00
|
|
|
- (BOOL)tableView:(NSTableView*)tv
|
|
|
|
acceptDrop:(id <NSDraggingInfo>)info
|
|
|
|
row:(int)row
|
|
|
|
dropOperation:(NSTableViewDropOperation)op
|
|
|
|
{
|
2008-02-24 15:47:04 +00:00
|
|
|
//Check if DNDArrayController handles it.
|
|
|
|
if ([super tableView:tv acceptDrop:info row:row dropOperation:op])
|
2005-06-02 18:16:43 +00:00
|
|
|
return YES;
|
|
|
|
|
|
|
|
if (row < 0)
|
|
|
|
row = 0;
|
2008-05-09 21:24:49 +00:00
|
|
|
|
|
|
|
|
2007-02-17 15:58:39 +00:00
|
|
|
// Determine the type of object that was dropped
|
2009-02-28 22:22:33 +00:00
|
|
|
NSArray *supportedTypes = [NSArray arrayWithObjects:CogUrlsPboardType, NSFilenamesPboardType, iTunesDropType, nil];
|
2007-02-17 15:58:39 +00:00
|
|
|
NSPasteboard *pboard = [info draggingPasteboard];
|
2009-02-28 22:22:33 +00:00
|
|
|
NSString *bestType = [pboard availableTypeFromArray:supportedTypes];
|
2008-05-09 21:24:49 +00:00
|
|
|
|
2009-02-28 22:22:33 +00:00
|
|
|
NSMutableArray *acceptedURLs = [[NSMutableArray alloc] init];
|
2007-02-17 15:58:39 +00:00
|
|
|
|
2007-10-16 01:22:57 +00:00
|
|
|
// Get files from an file drawer drop
|
2008-02-13 17:14:19 +00:00
|
|
|
if ([bestType isEqualToString:CogUrlsPboardType]) {
|
|
|
|
NSArray *urls = [NSUnarchiver unarchiveObjectWithData:[[info draggingPasteboard] dataForType:CogUrlsPboardType]];
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"URLS: %@", urls);
|
2008-05-09 21:24:49 +00:00
|
|
|
//[playlistLoader insertURLs: urls atIndex:row sort:YES];
|
2009-02-28 22:22:33 +00:00
|
|
|
[acceptedURLs addObjectsFromArray:urls];
|
2007-10-16 01:22:57 +00:00
|
|
|
}
|
|
|
|
|
2007-02-17 15:58:39 +00:00
|
|
|
// Get files from a normal file drop (such as from Finder)
|
|
|
|
if ([bestType isEqualToString:NSFilenamesPboardType]) {
|
2007-03-09 01:16:06 +00:00
|
|
|
NSMutableArray *urls = [[NSMutableArray alloc] init];
|
|
|
|
|
2008-02-20 00:54:45 +00:00
|
|
|
for (NSString *file in [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType])
|
2007-03-09 01:16:06 +00:00
|
|
|
{
|
|
|
|
[urls addObject:[NSURL fileURLWithPath:file]];
|
|
|
|
}
|
2007-02-17 15:58:39 +00:00
|
|
|
|
2008-05-09 21:24:49 +00:00
|
|
|
//[playlistLoader insertURLs:urls atIndex:row sort:YES];
|
2009-02-28 22:22:33 +00:00
|
|
|
[acceptedURLs addObjectsFromArray:urls];
|
2007-02-17 15:58:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get files from an iTunes drop
|
|
|
|
if ([bestType isEqualToString:iTunesDropType]) {
|
|
|
|
NSDictionary *iTunesDict = [pboard propertyListForType:iTunesDropType];
|
|
|
|
NSDictionary *tracks = [iTunesDict valueForKey:@"Tracks"];
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
// Convert the iTunes URLs to URLs....MWAHAHAH!
|
|
|
|
NSMutableArray *urls = [[NSMutableArray alloc] init];
|
|
|
|
|
2008-02-20 00:54:45 +00:00
|
|
|
for (NSDictionary *trackInfo in [tracks allValues]) {
|
2007-03-09 01:16:06 +00:00
|
|
|
[urls addObject:[NSURL URLWithString:[trackInfo valueForKey:@"Location"]]];
|
2007-02-17 15:58:39 +00:00
|
|
|
}
|
|
|
|
|
2008-05-09 21:24:49 +00:00
|
|
|
//[playlistLoader insertURLs:urls atIndex:row sort:YES];
|
2009-02-28 22:22:33 +00:00
|
|
|
[acceptedURLs addObjectsFromArray:urls];
|
2007-02-17 15:58:39 +00:00
|
|
|
}
|
|
|
|
|
2009-02-28 22:22:33 +00:00
|
|
|
if ([acceptedURLs count])
|
|
|
|
{
|
|
|
|
[self willInsertURLs:acceptedURLs origin:URLOriginInternal];
|
|
|
|
|
2013-10-11 19:33:58 +00:00
|
|
|
if (![[self content] count]) {
|
2008-05-09 21:24:49 +00:00
|
|
|
row = 0;
|
|
|
|
}
|
|
|
|
|
2009-02-28 22:22:33 +00:00
|
|
|
NSArray* entries = [playlistLoader insertURLs:acceptedURLs atIndex:row sort:YES];
|
|
|
|
[self didInsertURLs:entries origin:URLOriginInternal];
|
|
|
|
}
|
|
|
|
|
2009-03-25 03:12:52 +00:00
|
|
|
if ([self shuffle] != ShuffleOff)
|
2006-01-20 15:22:03 +00:00
|
|
|
[self resetShuffleList];
|
2005-06-02 18:16:43 +00:00
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2008-02-10 19:35:58 +00:00
|
|
|
- (NSUndoManager *)undoManager
|
2008-02-10 16:16:45 +00:00
|
|
|
{
|
2013-10-11 19:33:58 +00:00
|
|
|
return undoManager;
|
2008-02-10 16:16:45 +00:00
|
|
|
}
|
|
|
|
|
2013-10-18 08:47:48 +00:00
|
|
|
- (NSIndexSet *)disarrangeIndexes:(NSIndexSet *)indexes
|
|
|
|
{
|
|
|
|
if ([[self arrangedObjects] count] <= [indexes lastIndex])
|
|
|
|
return indexes;
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSMutableIndexSet *disarrangedIndexes = [[NSMutableIndexSet alloc] init];
|
2013-10-18 08:47:48 +00:00
|
|
|
|
|
|
|
NSUInteger index = [indexes firstIndex];
|
|
|
|
while (index != NSNotFound)
|
|
|
|
{
|
|
|
|
[disarrangedIndexes addIndex:[[self content] indexOfObject:[[self arrangedObjects] objectAtIndex:index]]];
|
|
|
|
index = [indexes indexGreaterThanIndex:index];
|
|
|
|
}
|
|
|
|
|
|
|
|
return disarrangedIndexes;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *)disarrangeObjects:(NSArray *)objects
|
|
|
|
{
|
2016-05-05 20:05:39 +00:00
|
|
|
NSMutableArray *disarrangedObjects = [[NSMutableArray alloc] init];
|
2013-10-18 08:47:48 +00:00
|
|
|
|
|
|
|
for (PlaylistEntry *pe in [self content])
|
|
|
|
{
|
|
|
|
if ([objects containsObject:pe])
|
|
|
|
[disarrangedObjects addObject:pe];
|
|
|
|
}
|
|
|
|
|
|
|
|
return disarrangedObjects;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSIndexSet *)rearrangeIndexes:(NSIndexSet *)indexes
|
|
|
|
{
|
|
|
|
if ([[self content] count] <= [indexes lastIndex])
|
|
|
|
return indexes;
|
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
NSMutableIndexSet *rearrangedIndexes = [[NSMutableIndexSet alloc] init];
|
2013-10-18 08:47:48 +00:00
|
|
|
|
|
|
|
NSUInteger index = [indexes firstIndex];
|
|
|
|
while (index != NSNotFound)
|
|
|
|
{
|
|
|
|
[rearrangedIndexes addIndex:[[self arrangedObjects] indexOfObject:[[self content] objectAtIndex:index]]];
|
|
|
|
index = [indexes indexGreaterThanIndex:index];
|
|
|
|
}
|
|
|
|
|
|
|
|
return rearrangedIndexes;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)insertObjects:(NSArray *)objects atIndexes:(NSIndexSet *)indexes
|
|
|
|
{
|
|
|
|
[self insertObjects:objects atArrangedObjectIndexes:indexes];
|
|
|
|
[self rearrangeObjects];
|
|
|
|
}
|
|
|
|
|
2008-02-10 19:35:58 +00:00
|
|
|
- (void)insertObjects:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes
|
2008-02-10 16:16:45 +00:00
|
|
|
{
|
2013-10-18 08:47:48 +00:00
|
|
|
[[[self undoManager] prepareWithInvocationTarget:self] removeObjectsAtIndexes:[self disarrangeIndexes:indexes]];
|
2013-10-13 02:16:47 +00:00
|
|
|
NSString *actionName = [NSString stringWithFormat:@"Adding %lu entries", (unsigned long)[objects count]];
|
2013-10-11 19:33:58 +00:00
|
|
|
[[self undoManager] setActionName:actionName];
|
|
|
|
|
|
|
|
[super insertObjects:objects atArrangedObjectIndexes:indexes];
|
|
|
|
|
|
|
|
if ([self shuffle] != ShuffleOff)
|
|
|
|
[self resetShuffleList];
|
2008-02-10 16:16:45 +00:00
|
|
|
}
|
|
|
|
|
2013-10-18 08:47:48 +00:00
|
|
|
- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes
|
|
|
|
{
|
|
|
|
[self removeObjectsAtArrangedObjectIndexes:[self rearrangeIndexes:indexes]];
|
|
|
|
}
|
|
|
|
|
2005-06-02 18:16:43 +00:00
|
|
|
- (void)removeObjectsAtArrangedObjectIndexes:(NSIndexSet *)indexes
|
|
|
|
{
|
2013-10-18 07:17:03 +00:00
|
|
|
NSArray *objects = [[self arrangedObjects] objectsAtIndexes:indexes];
|
2013-10-18 08:47:48 +00:00
|
|
|
[[[self undoManager] prepareWithInvocationTarget:self] insertObjects:[self disarrangeObjects:objects] atIndexes:[self disarrangeIndexes:indexes]];
|
2013-10-13 02:16:47 +00:00
|
|
|
NSString *actionName = [NSString stringWithFormat:@"Removing %lu entries", (unsigned long)[indexes count]];
|
2013-10-11 19:33:58 +00:00
|
|
|
[[self undoManager] setActionName:actionName];
|
|
|
|
|
|
|
|
DLog(@"Removing indexes: %@", indexes);
|
|
|
|
DLog(@"Current index: %i", currentEntry.index);
|
|
|
|
|
2013-10-18 07:17:03 +00:00
|
|
|
NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init];
|
|
|
|
for (PlaylistEntry *pe in objects)
|
|
|
|
{
|
|
|
|
[unarrangedIndexes addIndex:[pe index]];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentEntry.index >= 0 && [unarrangedIndexes containsIndex:currentEntry.index])
|
2013-10-11 19:33:58 +00:00
|
|
|
{
|
|
|
|
currentEntry.index = -currentEntry.index - 1;
|
|
|
|
DLog(@"Current removed: %i", currentEntry.index);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentEntry.index < 0) //Need to update the negative index
|
|
|
|
{
|
|
|
|
int i = -currentEntry.index - 1;
|
|
|
|
DLog(@"I is %i", i);
|
|
|
|
int j;
|
|
|
|
for (j = i - 1; j >= 0; j--)
|
|
|
|
{
|
2013-10-18 07:17:03 +00:00
|
|
|
if ([unarrangedIndexes containsIndex:j]) {
|
2013-10-11 19:33:58 +00:00
|
|
|
DLog(@"Removing 1");
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currentEntry.index = -i - 1;
|
|
|
|
|
|
|
|
}
|
2013-10-18 07:17:03 +00:00
|
|
|
|
2013-10-11 19:33:58 +00:00
|
|
|
[super removeObjectsAtArrangedObjectIndexes:indexes];
|
|
|
|
|
|
|
|
if ([self shuffle] != ShuffleOff)
|
|
|
|
[self resetShuffleList];
|
|
|
|
|
|
|
|
[playbackController playlistDidChange:self];
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2006-05-12 19:23:17 +00:00
|
|
|
- (void)setSortDescriptors:(NSArray *)sortDescriptors
|
|
|
|
{
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Current: %@, setting: %@", [self sortDescriptors], sortDescriptors);
|
2008-02-10 20:32:46 +00:00
|
|
|
|
2006-05-12 19:23:17 +00:00
|
|
|
//Cheap hack so the index column isn't sorted
|
2007-03-14 03:16:37 +00:00
|
|
|
if (([sortDescriptors count] != 0) && [[[sortDescriptors objectAtIndex:0] key] caseInsensitiveCompare:@"index"] == NSOrderedSame)
|
2006-05-12 19:23:17 +00:00
|
|
|
{
|
2007-02-18 22:27:55 +00:00
|
|
|
//Remove the sort descriptors
|
2018-06-28 10:59:59 +00:00
|
|
|
[super setSortDescriptors:[NSArray array]];
|
2007-02-18 22:27:55 +00:00
|
|
|
[self rearrangeObjects];
|
|
|
|
|
2006-05-29 23:03:58 +00:00
|
|
|
return;
|
2006-05-12 19:23:17 +00:00
|
|
|
}
|
2006-05-29 23:03:58 +00:00
|
|
|
|
|
|
|
[super setSortDescriptors:sortDescriptors];
|
2008-02-10 20:32:46 +00:00
|
|
|
[self rearrangeObjects];
|
2009-02-28 18:57:21 +00:00
|
|
|
|
|
|
|
[playbackController playlistDidChange:self];
|
2006-05-12 19:23:17 +00:00
|
|
|
}
|
|
|
|
|
2013-10-11 19:33:58 +00:00
|
|
|
- (IBAction)randomizeList:(id)sender
|
2006-04-30 13:01:33 +00:00
|
|
|
{
|
2018-06-28 10:59:59 +00:00
|
|
|
[self setSortDescriptors:[NSArray array]];
|
2007-02-18 22:27:55 +00:00
|
|
|
|
2013-10-11 19:33:58 +00:00
|
|
|
NSArray *unrandomized = [self content];
|
|
|
|
[[[self undoManager] prepareWithInvocationTarget:self] unrandomizeList:unrandomized];
|
2006-04-30 13:01:33 +00:00
|
|
|
|
2013-10-11 19:33:58 +00:00
|
|
|
[self setContent:[Shuffle shuffleList:[self content]]];
|
|
|
|
|
|
|
|
if ([self shuffle] != ShuffleOff)
|
|
|
|
[self resetShuffleList];
|
|
|
|
|
|
|
|
[[self undoManager] setActionName:@"Playlist Randomization"];
|
2006-04-30 13:01:33 +00:00
|
|
|
}
|
|
|
|
|
2013-10-11 19:33:58 +00:00
|
|
|
- (void)unrandomizeList:(NSArray *)entries
|
2006-04-30 13:01:33 +00:00
|
|
|
{
|
2013-10-11 19:33:58 +00:00
|
|
|
[[[self undoManager] prepareWithInvocationTarget:self] randomizeList:self];
|
|
|
|
[self setContent:entries];
|
2006-04-30 13:01:33 +00:00
|
|
|
}
|
|
|
|
|
2009-02-28 06:40:50 +00:00
|
|
|
- (IBAction)toggleShuffle:(id)sender
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2009-03-10 04:04:46 +00:00
|
|
|
ShuffleMode shuffle = [self shuffle];
|
|
|
|
|
|
|
|
if (shuffle == ShuffleOff) {
|
|
|
|
[self setShuffle: ShuffleAlbums];
|
|
|
|
}
|
|
|
|
else if (shuffle == ShuffleAlbums) {
|
|
|
|
[self setShuffle: ShuffleAll];
|
|
|
|
}
|
|
|
|
else if (shuffle == ShuffleAll) {
|
|
|
|
[self setShuffle: ShuffleOff];
|
|
|
|
}
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
2008-02-19 04:02:05 +00:00
|
|
|
|
2008-02-19 03:39:43 +00:00
|
|
|
- (IBAction)toggleRepeat:(id)sender
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2009-02-28 18:06:21 +00:00
|
|
|
RepeatMode repeat = [self repeat];
|
|
|
|
|
2008-02-19 03:39:43 +00:00
|
|
|
if (repeat == RepeatNone) {
|
|
|
|
[self setRepeat: RepeatOne];
|
|
|
|
}
|
|
|
|
else if (repeat == RepeatOne) {
|
2008-02-19 04:02:05 +00:00
|
|
|
[self setRepeat: RepeatAlbum];
|
|
|
|
}
|
|
|
|
else if (repeat == RepeatAlbum) {
|
2008-02-19 03:39:43 +00:00
|
|
|
[self setRepeat: RepeatAll];
|
|
|
|
}
|
|
|
|
else if (repeat == RepeatAll) {
|
|
|
|
[self setRepeat: RepeatNone];
|
|
|
|
}
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2006-04-13 02:51:22 +00:00
|
|
|
- (PlaylistEntry *)entryAtIndex:(int)i
|
|
|
|
{
|
2009-02-28 18:06:21 +00:00
|
|
|
RepeatMode repeat = [self repeat];
|
|
|
|
|
2015-01-07 12:10:33 +00:00
|
|
|
if (i < 0 || i >= [[self arrangedObjects] count] ) {
|
2015-02-08 07:38:16 +00:00
|
|
|
if ( repeat != RepeatAll )
|
2006-04-15 13:51:40 +00:00
|
|
|
return nil;
|
2015-01-07 12:10:33 +00:00
|
|
|
|
|
|
|
while ( i < 0 )
|
|
|
|
i += [[self arrangedObjects] count];
|
|
|
|
if ( i >= [[self arrangedObjects] count])
|
|
|
|
i %= [[self arrangedObjects] count];
|
2006-04-15 13:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return [[self arrangedObjects] objectAtIndex:i];
|
|
|
|
}
|
2007-03-09 01:16:06 +00:00
|
|
|
|
2013-10-11 19:39:05 +00:00
|
|
|
- (void)remove:(id)sender {
|
|
|
|
// It's a kind of magic.
|
|
|
|
// Plain old NSArrayController's remove: isn't working properly for some reason.
|
|
|
|
// The method is definitely called but (overridden) removeObjectsAtArrangedObjectIndexes: isn't called
|
|
|
|
// and no entries are removed.
|
|
|
|
// Putting explicit call to removeObjectsAtArrangedObjectIndexes: here for now.
|
|
|
|
// TODO: figure it out
|
|
|
|
|
|
|
|
NSIndexSet *selected = [self selectionIndexes];
|
|
|
|
if ([selected count] > 0)
|
|
|
|
{
|
|
|
|
[self removeObjectsAtArrangedObjectIndexes:selected];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-18 07:13:27 +00:00
|
|
|
- (IBAction)removeDuplicates:(id)sender {
|
|
|
|
NSMutableArray *originals = [[NSMutableArray alloc] init];
|
2013-10-18 07:25:37 +00:00
|
|
|
NSMutableArray *duplicates = [[NSMutableArray alloc] init];
|
2013-10-18 07:13:27 +00:00
|
|
|
|
|
|
|
for (PlaylistEntry *pe in [self content])
|
|
|
|
{
|
|
|
|
if ([originals containsObject:[pe URL]])
|
2013-10-18 07:25:37 +00:00
|
|
|
[duplicates addObject:pe];
|
2013-10-18 07:13:27 +00:00
|
|
|
else
|
|
|
|
[originals addObject:[pe URL]];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([duplicates count] > 0)
|
2013-10-18 07:25:37 +00:00
|
|
|
{
|
|
|
|
NSArray * arrangedContent = [self arrangedObjects];
|
|
|
|
NSMutableIndexSet * duplicatesIndex = [[NSMutableIndexSet alloc] init];
|
|
|
|
for (PlaylistEntry *pe in duplicates)
|
|
|
|
{
|
|
|
|
[duplicatesIndex addIndex:[arrangedContent indexOfObject:pe]];
|
|
|
|
}
|
|
|
|
[self removeObjectsAtArrangedObjectIndexes:duplicatesIndex];
|
|
|
|
}
|
2013-10-18 07:13:27 +00:00
|
|
|
}
|
|
|
|
|
2013-10-18 07:14:53 +00:00
|
|
|
- (IBAction)removeDeadItems:(id)sender {
|
2013-10-18 07:25:37 +00:00
|
|
|
NSMutableArray *deadItems = [[NSMutableArray alloc] init];
|
2013-10-18 07:14:53 +00:00
|
|
|
|
|
|
|
for (PlaylistEntry *pe in [self content])
|
|
|
|
{
|
|
|
|
NSURL *url = [pe URL];
|
|
|
|
if ([url isFileURL])
|
|
|
|
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]])
|
2013-10-18 07:25:37 +00:00
|
|
|
[deadItems addObject:pe];
|
2013-10-18 07:14:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ([deadItems count] > 0)
|
2013-10-18 07:25:37 +00:00
|
|
|
{
|
|
|
|
NSArray * arrangedContent = [self arrangedObjects];
|
|
|
|
NSMutableIndexSet * deadItemsIndex = [[NSMutableIndexSet alloc] init];
|
|
|
|
for (PlaylistEntry *pe in deadItems)
|
|
|
|
{
|
|
|
|
[deadItemsIndex addIndex:[arrangedContent indexOfObject:pe]];
|
|
|
|
}
|
|
|
|
[self removeObjectsAtArrangedObjectIndexes:deadItemsIndex];
|
|
|
|
}
|
2013-10-18 07:14:53 +00:00
|
|
|
}
|
|
|
|
|
2006-04-15 13:51:40 +00:00
|
|
|
- (PlaylistEntry *)shuffledEntryAtIndex:(int)i
|
|
|
|
{
|
2009-02-28 18:06:21 +00:00
|
|
|
RepeatMode repeat = [self repeat];
|
|
|
|
|
2006-04-15 13:51:40 +00:00
|
|
|
while (i < 0)
|
|
|
|
{
|
2008-02-19 23:49:51 +00:00
|
|
|
if (repeat == RepeatAll)
|
2006-04-14 17:28:20 +00:00
|
|
|
{
|
2006-04-15 13:51:40 +00:00
|
|
|
[self addShuffledListToFront];
|
|
|
|
//change i appropriately
|
|
|
|
i += [[self arrangedObjects] count];
|
2006-04-14 17:28:20 +00:00
|
|
|
}
|
2006-04-15 13:51:40 +00:00
|
|
|
else
|
2006-04-14 17:28:20 +00:00
|
|
|
{
|
2006-04-15 13:51:40 +00:00
|
|
|
return nil;
|
2006-04-14 17:28:20 +00:00
|
|
|
}
|
2006-04-13 02:51:22 +00:00
|
|
|
}
|
2006-04-15 13:51:40 +00:00
|
|
|
while (i >= [shuffleList count])
|
2006-04-13 02:51:22 +00:00
|
|
|
{
|
2008-02-19 23:49:51 +00:00
|
|
|
if (repeat == RepeatAll)
|
2006-04-14 17:28:20 +00:00
|
|
|
{
|
2006-04-15 13:51:40 +00:00
|
|
|
[self addShuffledListToBack];
|
2006-04-14 17:28:20 +00:00
|
|
|
}
|
2006-04-15 13:51:40 +00:00
|
|
|
else
|
2006-04-14 17:28:20 +00:00
|
|
|
{
|
2006-04-15 13:51:40 +00:00
|
|
|
return nil;
|
2006-04-14 17:28:20 +00:00
|
|
|
}
|
2006-04-15 13:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return [shuffleList objectAtIndex:i];
|
2006-04-13 02:51:22 +00:00
|
|
|
}
|
|
|
|
|
2006-04-15 13:51:40 +00:00
|
|
|
- (PlaylistEntry *)getNextEntry:(PlaylistEntry *)pe
|
|
|
|
{
|
2017-12-24 07:55:33 +00:00
|
|
|
return [self getNextEntry:pe ignoreRepeatOne:NO];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (PlaylistEntry *)getNextEntry:(PlaylistEntry *)pe ignoreRepeatOne:(BOOL)ignoreRepeatOne
|
|
|
|
{
|
|
|
|
if (!ignoreRepeatOne && [self repeat] == RepeatOne)
|
|
|
|
{
|
|
|
|
return pe;
|
|
|
|
}
|
|
|
|
|
2008-02-21 07:30:28 +00:00
|
|
|
if ([queueList count] > 0)
|
|
|
|
{
|
|
|
|
|
|
|
|
pe = [queueList objectAtIndex:0];
|
|
|
|
[queueList removeObjectAtIndex:0];
|
2008-03-01 03:29:21 +00:00
|
|
|
pe.queued = NO;
|
2008-02-22 15:34:09 +00:00
|
|
|
[pe setQueuePosition:-1];
|
2008-02-22 02:19:46 +00:00
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < [queueList count]; i++)
|
|
|
|
{
|
|
|
|
PlaylistEntry *queueItem = [queueList objectAtIndex:i];
|
2008-03-01 03:29:21 +00:00
|
|
|
[queueItem setQueuePosition: i];
|
2008-02-22 02:19:46 +00:00
|
|
|
}
|
|
|
|
|
2008-02-21 07:30:28 +00:00
|
|
|
return pe;
|
|
|
|
}
|
|
|
|
|
2009-03-25 03:12:52 +00:00
|
|
|
if ([self shuffle] != ShuffleOff)
|
2006-04-15 13:51:40 +00:00
|
|
|
{
|
2008-02-23 19:46:23 +00:00
|
|
|
return [self shuffledEntryAtIndex:(pe.shuffleIndex + 1)];
|
2006-04-15 13:51:40 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2007-10-22 00:04:34 +00:00
|
|
|
int i;
|
2008-02-23 19:46:23 +00:00
|
|
|
if (pe.index < 0) //Was a current entry, now removed.
|
2007-10-22 00:04:34 +00:00
|
|
|
{
|
2008-02-23 19:46:23 +00:00
|
|
|
i = -pe.index - 1;
|
2007-10-22 00:04:34 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-02-23 19:46:23 +00:00
|
|
|
i = pe.index + 1;
|
2007-10-22 00:04:34 +00:00
|
|
|
}
|
|
|
|
|
2009-02-28 18:06:21 +00:00
|
|
|
if ([self repeat] == RepeatAlbum)
|
2008-02-21 19:14:20 +00:00
|
|
|
{
|
|
|
|
PlaylistEntry *next = [self entryAtIndex:i];
|
|
|
|
|
|
|
|
if ((i > [[self arrangedObjects] count]-1) || ([[next album] caseInsensitiveCompare:[pe album]]) || ([next album] == nil))
|
|
|
|
{
|
|
|
|
NSArray *filtered = [self filterPlaylistOnAlbum:[pe album]];
|
|
|
|
if ([pe album] == nil)
|
|
|
|
i--;
|
|
|
|
else
|
2013-10-03 08:00:58 +00:00
|
|
|
i = [(PlaylistEntry *)[filtered objectAtIndex:0] index];
|
2008-02-21 19:14:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2007-10-22 00:04:34 +00:00
|
|
|
return [self entryAtIndex:i];
|
2006-04-15 13:51:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-21 19:14:20 +00:00
|
|
|
- (NSArray *)filterPlaylistOnAlbum:(NSString *)album
|
|
|
|
{
|
2019-10-13 01:27:02 +00:00
|
|
|
NSPredicate *predicate;
|
|
|
|
if ([album length] > 0)
|
|
|
|
predicate = [NSPredicate predicateWithFormat:@"album like %@",
|
|
|
|
album];
|
|
|
|
else
|
|
|
|
predicate = [NSPredicate predicateWithFormat:@"album == nil"];
|
2008-02-21 19:14:20 +00:00
|
|
|
return [[self arrangedObjects] filteredArrayUsingPredicate:predicate];
|
|
|
|
}
|
|
|
|
|
2006-04-15 13:51:40 +00:00
|
|
|
- (PlaylistEntry *)getPrevEntry:(PlaylistEntry *)pe
|
|
|
|
{
|
2017-12-24 07:55:33 +00:00
|
|
|
return [self getPrevEntry:pe ignoreRepeatOne:NO];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (PlaylistEntry *)getPrevEntry:(PlaylistEntry *)pe ignoreRepeatOne:(BOOL)ignoreRepeatOne
|
|
|
|
{
|
2019-10-13 01:27:02 +00:00
|
|
|
if (!ignoreRepeatOne && [self repeat] == RepeatOne)
|
|
|
|
{
|
|
|
|
return pe;
|
|
|
|
}
|
2017-12-24 07:55:33 +00:00
|
|
|
|
2009-03-25 03:12:52 +00:00
|
|
|
if ([self shuffle] != ShuffleOff)
|
2006-04-15 13:51:40 +00:00
|
|
|
{
|
2008-02-23 19:46:23 +00:00
|
|
|
return [self shuffledEntryAtIndex:(pe.shuffleIndex - 1)];
|
2006-04-15 13:51:40 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2007-10-22 00:04:34 +00:00
|
|
|
int i;
|
2008-02-23 19:46:23 +00:00
|
|
|
if (pe.index < 0) //Was a current entry, now removed.
|
2006-04-15 14:17:46 +00:00
|
|
|
{
|
2008-02-23 19:46:23 +00:00
|
|
|
i = -pe.index - 2;
|
2006-04-15 14:17:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-02-23 19:46:23 +00:00
|
|
|
i = pe.index - 1;
|
2006-04-15 14:17:46 +00:00
|
|
|
}
|
2007-10-22 00:04:34 +00:00
|
|
|
|
|
|
|
return [self entryAtIndex:i];
|
2006-04-15 13:51:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-01-29 14:57:48 +00:00
|
|
|
- (BOOL)next
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
|
|
|
PlaylistEntry *pe;
|
|
|
|
|
2017-12-24 07:55:33 +00:00
|
|
|
pe = [self getNextEntry:[self currentEntry] ignoreRepeatOne:YES];
|
2008-02-19 19:59:35 +00:00
|
|
|
|
2005-06-02 18:16:43 +00:00
|
|
|
if (pe == nil)
|
2008-02-19 23:49:51 +00:00
|
|
|
return NO;
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2006-01-20 15:22:03 +00:00
|
|
|
[self setCurrentEntry:pe];
|
2006-01-29 14:57:48 +00:00
|
|
|
|
|
|
|
return YES;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2006-01-29 14:57:48 +00:00
|
|
|
- (BOOL)prev
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
|
|
|
PlaylistEntry *pe;
|
|
|
|
|
2017-12-24 07:55:33 +00:00
|
|
|
pe = [self getPrevEntry:[self currentEntry] ignoreRepeatOne:YES];
|
2005-06-02 18:16:43 +00:00
|
|
|
if (pe == nil)
|
2008-02-19 23:49:51 +00:00
|
|
|
return NO;
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2006-01-20 15:22:03 +00:00
|
|
|
[self setCurrentEntry:pe];
|
2006-01-29 14:57:48 +00:00
|
|
|
|
|
|
|
return YES;
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
2006-04-14 20:34:14 +00:00
|
|
|
|
2019-10-13 01:27:02 +00:00
|
|
|
- (NSArray *)shuffleAlbums
|
|
|
|
{
|
|
|
|
NSArray * newList = [self arrangedObjects];
|
|
|
|
NSMutableArray * temp = [[NSMutableArray alloc] init];
|
|
|
|
NSMutableArray * albums = [[NSMutableArray alloc] init];
|
|
|
|
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"track" ascending:YES];
|
|
|
|
for (unsigned long i = 0, j = [newList count]; i < j; ++i) {
|
|
|
|
PlaylistEntry * pe = [newList objectAtIndex:i];
|
|
|
|
NSString * album = [pe album];
|
|
|
|
if (!album)
|
|
|
|
album = @"";
|
|
|
|
if ([albums containsObject:album]) continue;
|
|
|
|
[albums addObject:album];
|
|
|
|
NSArray * albumContent = [self filterPlaylistOnAlbum:album];
|
|
|
|
NSArray * sortedContent = [albumContent sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
|
|
|
|
[temp addObject:[sortedContent objectAtIndex:0]];
|
|
|
|
}
|
|
|
|
NSArray * tempList = [Shuffle shuffleList:temp];
|
|
|
|
temp = [[NSMutableArray alloc] init];
|
|
|
|
for (unsigned long i = 0, j = [tempList count]; i < j; ++i) {
|
|
|
|
PlaylistEntry * pe = [tempList objectAtIndex:i];
|
|
|
|
NSString * album = [pe album];
|
|
|
|
NSArray * albumContent = [self filterPlaylistOnAlbum:album];
|
|
|
|
NSArray * sortedContent = [albumContent sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
|
|
|
|
[temp addObjectsFromArray:sortedContent];
|
|
|
|
}
|
|
|
|
return temp;
|
|
|
|
}
|
|
|
|
|
2006-04-15 13:51:40 +00:00
|
|
|
- (void)addShuffledListToFront
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2019-10-13 01:27:02 +00:00
|
|
|
NSArray *newList;
|
|
|
|
NSIndexSet *indexSet;
|
|
|
|
|
|
|
|
if ([self shuffle] == ShuffleAlbums) {
|
|
|
|
newList = [self shuffleAlbums];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
newList = [Shuffle shuffleList:[self arrangedObjects]];
|
|
|
|
}
|
|
|
|
|
|
|
|
indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newList count])];
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2006-04-05 17:25:51 +00:00
|
|
|
[shuffleList insertObjects:newList atIndexes:indexSet];
|
2006-04-15 13:51:40 +00:00
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < [shuffleList count]; i++)
|
|
|
|
{
|
2008-02-23 19:46:23 +00:00
|
|
|
[[shuffleList objectAtIndex:i] setShuffleIndex:i];
|
2006-04-15 13:51:40 +00:00
|
|
|
}
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2006-04-15 13:51:40 +00:00
|
|
|
- (void)addShuffledListToBack
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2019-10-13 01:27:02 +00:00
|
|
|
NSArray *newList;
|
|
|
|
NSIndexSet *indexSet;
|
|
|
|
|
|
|
|
if ([self shuffle] == ShuffleAlbums) {
|
|
|
|
newList = [self shuffleAlbums];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
newList = [Shuffle shuffleList:[self arrangedObjects]];
|
|
|
|
}
|
|
|
|
|
|
|
|
indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange([shuffleList count], [newList count])];
|
|
|
|
|
2006-04-05 17:25:51 +00:00
|
|
|
[shuffleList insertObjects:newList atIndexes:indexSet];
|
2019-10-13 01:27:02 +00:00
|
|
|
|
2018-06-28 10:59:59 +00:00
|
|
|
unsigned long i;
|
2006-04-15 13:51:40 +00:00
|
|
|
for (i = ([shuffleList count] - [newList count]); i < [shuffleList count]; i++)
|
|
|
|
{
|
2018-06-28 10:59:59 +00:00
|
|
|
[[shuffleList objectAtIndex:i] setShuffleIndex:(int)i];
|
2006-04-15 13:51:40 +00:00
|
|
|
}
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2006-01-20 15:22:03 +00:00
|
|
|
- (void)resetShuffleList
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2006-01-20 15:22:03 +00:00
|
|
|
[shuffleList removeAllObjects];
|
2006-04-15 13:51:40 +00:00
|
|
|
|
2006-04-05 17:25:51 +00:00
|
|
|
[self addShuffledListToFront];
|
2006-04-15 13:51:40 +00:00
|
|
|
|
2008-02-23 19:46:23 +00:00
|
|
|
if (currentEntry && currentEntry.index >= 0)
|
2006-04-15 13:51:40 +00:00
|
|
|
{
|
2006-04-15 14:17:46 +00:00
|
|
|
[shuffleList insertObject:currentEntry atIndex:0];
|
|
|
|
[currentEntry setShuffleIndex:0];
|
|
|
|
|
|
|
|
//Need to rejigger so the current entry is at the start now...
|
|
|
|
int i;
|
|
|
|
BOOL found = NO;
|
2008-02-20 00:12:25 +00:00
|
|
|
for (i = 1; i < [shuffleList count] && !found; i++)
|
2006-04-15 13:51:40 +00:00
|
|
|
{
|
2008-02-20 00:12:25 +00:00
|
|
|
if ([shuffleList objectAtIndex:i] == currentEntry)
|
2006-04-15 14:17:46 +00:00
|
|
|
{
|
|
|
|
found = YES;
|
|
|
|
[shuffleList removeObjectAtIndex:i];
|
|
|
|
}
|
2008-02-20 00:12:25 +00:00
|
|
|
else {
|
2008-02-23 19:46:23 +00:00
|
|
|
[[shuffleList objectAtIndex:i] setShuffleIndex: i];
|
2008-02-20 00:12:25 +00:00
|
|
|
}
|
2006-04-15 14:17:46 +00:00
|
|
|
}
|
2006-04-15 13:51:40 +00:00
|
|
|
}
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2007-02-18 21:41:47 +00:00
|
|
|
- (void)setCurrentEntry:(PlaylistEntry *)pe
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2008-03-01 03:29:21 +00:00
|
|
|
currentEntry.current = NO;
|
2008-03-01 03:35:27 +00:00
|
|
|
currentEntry.stopAfter = NO;
|
2008-02-22 02:19:46 +00:00
|
|
|
|
2008-03-01 03:29:21 +00:00
|
|
|
pe.current = YES;
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2008-03-04 01:14:51 +00:00
|
|
|
if (pe != nil)
|
|
|
|
[tableView scrollRowToVisible:pe.index];
|
2005-06-02 18:16:43 +00:00
|
|
|
|
|
|
|
currentEntry = pe;
|
2013-09-30 00:27:55 +00:00
|
|
|
}
|
2005-06-02 18:16:43 +00:00
|
|
|
|
2009-03-10 04:04:46 +00:00
|
|
|
- (void)setShuffle:(ShuffleMode)s
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2009-03-10 04:04:46 +00:00
|
|
|
[[NSUserDefaults standardUserDefaults] setInteger:s forKey:@"shuffle"];
|
|
|
|
if (s != ShuffleOff)
|
2006-01-20 15:22:03 +00:00
|
|
|
[self resetShuffleList];
|
2009-02-28 20:26:43 +00:00
|
|
|
|
2009-02-28 20:53:24 +00:00
|
|
|
[playbackController playlistDidChange:self];
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
2009-03-10 04:04:46 +00:00
|
|
|
- (ShuffleMode)shuffle
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2018-06-28 10:59:59 +00:00
|
|
|
return (ShuffleMode) [[NSUserDefaults standardUserDefaults] integerForKey:@"shuffle"];
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
2008-02-19 03:39:43 +00:00
|
|
|
- (void)setRepeat:(RepeatMode)r
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2009-03-10 04:04:46 +00:00
|
|
|
[[NSUserDefaults standardUserDefaults] setInteger:r forKey:@"repeat"];
|
2009-02-28 20:53:24 +00:00
|
|
|
[playbackController playlistDidChange:self];
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
2008-02-19 03:39:43 +00:00
|
|
|
- (RepeatMode)repeat
|
2005-06-02 18:16:43 +00:00
|
|
|
{
|
2018-06-28 10:59:59 +00:00
|
|
|
return (RepeatMode) [[NSUserDefaults standardUserDefaults] integerForKey:@"repeat"];
|
2005-06-02 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2007-05-26 14:09:03 +00:00
|
|
|
- (IBAction)clear:(id)sender
|
|
|
|
{
|
2008-02-10 19:46:45 +00:00
|
|
|
[self setFilterPredicate:nil];
|
2007-06-05 00:33:30 +00:00
|
|
|
|
2008-02-10 19:46:45 +00:00
|
|
|
[self removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[self arrangedObjects] count])]];
|
2007-05-26 14:09:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)clearFilterPredicate:(id)sender
|
|
|
|
{
|
|
|
|
[self setFilterPredicate:nil];
|
|
|
|
}
|
|
|
|
|
2006-04-30 15:31:57 +00:00
|
|
|
- (void)setFilterPredicate:(NSPredicate *)filterPredicate
|
|
|
|
{
|
|
|
|
[super setFilterPredicate:filterPredicate];
|
|
|
|
}
|
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
- (IBAction)showEntryInFinder:(id)sender
|
2006-05-23 15:12:24 +00:00
|
|
|
{
|
|
|
|
NSWorkspace* ws = [NSWorkspace sharedWorkspace];
|
|
|
|
|
2008-02-20 00:44:40 +00:00
|
|
|
NSURL *url = [[[self selectedObjects] objectAtIndex:0] URL];
|
2007-03-09 01:16:06 +00:00
|
|
|
if ([url isFileURL])
|
|
|
|
[ws selectFile:[url path] inFileViewerRootedAtPath:[url path]];
|
2006-05-23 15:12:24 +00:00
|
|
|
}
|
2008-11-21 15:14:23 +00:00
|
|
|
/*
|
|
|
|
- (IBAction)showTagEditor:(id)sender
|
|
|
|
{
|
|
|
|
// call the editor & pass the url
|
|
|
|
if ([self selectionIndex] < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
NSURL *url = [[[self selectedObjects] objectAtIndex:0] URL];
|
|
|
|
if ([url isFileURL])
|
|
|
|
[TagEditorController openTagEditor:url sender:sender];
|
|
|
|
|
|
|
|
}
|
|
|
|
*/
|
2008-02-16 16:13:21 +00:00
|
|
|
- (IBAction)searchByArtist:(id)sender;
|
|
|
|
{
|
|
|
|
PlaylistEntry *entry = [[self arrangedObjects] objectAtIndex:[self selectionIndex]];
|
|
|
|
[spotlightWindowController searchForArtist:[entry artist]];
|
|
|
|
}
|
|
|
|
- (IBAction)searchByAlbum:(id)sender;
|
|
|
|
{
|
|
|
|
PlaylistEntry *entry = [[self arrangedObjects] objectAtIndex:[self selectionIndex]];
|
|
|
|
[spotlightWindowController searchForAlbum:[entry album]];
|
|
|
|
}
|
|
|
|
|
2008-02-21 09:05:06 +00:00
|
|
|
- (NSMutableArray *)queueList
|
|
|
|
{
|
|
|
|
return queueList;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)emptyQueueList:(id)sender
|
|
|
|
{
|
2008-02-22 02:21:08 +00:00
|
|
|
for (PlaylistEntry *queueItem in queueList)
|
|
|
|
{
|
2008-03-01 03:29:21 +00:00
|
|
|
queueItem.queued = NO;
|
2008-02-22 15:26:46 +00:00
|
|
|
[queueItem setQueuePosition:-1];
|
2008-02-22 02:21:08 +00:00
|
|
|
}
|
|
|
|
|
2008-02-21 09:05:06 +00:00
|
|
|
[queueList removeAllObjects];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-03-08 23:57:54 +00:00
|
|
|
- (IBAction)toggleQueued:(id)sender
|
2008-02-21 07:30:28 +00:00
|
|
|
{
|
|
|
|
for (PlaylistEntry *queueItem in [self selectedObjects])
|
2008-02-22 02:19:46 +00:00
|
|
|
{
|
2008-03-08 23:57:54 +00:00
|
|
|
if (queueItem.queued)
|
|
|
|
{
|
|
|
|
queueItem.queued = NO;
|
|
|
|
queueItem.queuePosition = -1;
|
2008-02-21 07:30:28 +00:00
|
|
|
|
2008-03-08 23:57:54 +00:00
|
|
|
[queueList removeObject:queueItem];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
queueItem.queued = YES;
|
2018-06-28 10:59:59 +00:00
|
|
|
queueItem.queuePosition = (int) [queueList count];
|
2008-03-08 23:57:54 +00:00
|
|
|
|
|
|
|
[queueList addObject:queueItem];
|
|
|
|
}
|
2008-02-23 20:08:27 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"TOGGLE QUEUED: %i", queueItem.queued);
|
2008-02-23 20:08:27 +00:00
|
|
|
}
|
|
|
|
|
2008-03-01 03:29:21 +00:00
|
|
|
int i = 0;
|
2008-02-23 20:08:27 +00:00
|
|
|
for (PlaylistEntry *cur in queueList)
|
|
|
|
{
|
2008-03-08 23:57:54 +00:00
|
|
|
cur.queuePosition = i++;
|
2008-02-22 15:26:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-28 10:59:59 +00:00
|
|
|
- (IBAction)removeFromQueue:(id)sender
|
|
|
|
{
|
|
|
|
for (PlaylistEntry *queueItem in [self selectedObjects])
|
|
|
|
{
|
|
|
|
queueItem.queued = NO;
|
|
|
|
queueItem.queuePosition = -1;
|
|
|
|
|
|
|
|
[queueList removeObject:queueItem];
|
|
|
|
}
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
for (PlaylistEntry *cur in queueList)
|
|
|
|
{
|
|
|
|
cur.queuePosition = i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)addToQueue:(id)sender
|
|
|
|
{
|
|
|
|
for (PlaylistEntry *queueItem in [self selectedObjects])
|
|
|
|
{
|
|
|
|
queueItem.queued = YES;
|
|
|
|
queueItem.queuePosition = (int) [queueList count];
|
|
|
|
|
|
|
|
[queueList addObject:queueItem];
|
|
|
|
}
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
for (PlaylistEntry *cur in queueList)
|
|
|
|
{
|
|
|
|
cur.queuePosition = i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-25 17:02:06 +00:00
|
|
|
- (IBAction)stopAfterCurrent:(id)sender
|
|
|
|
{
|
2008-03-01 03:29:21 +00:00
|
|
|
currentEntry.stopAfter = !currentEntry.stopAfter;
|
2008-02-25 17:02:06 +00:00
|
|
|
}
|
|
|
|
|
2008-02-23 22:20:14 +00:00
|
|
|
-(BOOL)validateMenuItem:(NSMenuItem*)menuItem
|
|
|
|
{
|
|
|
|
SEL action = [menuItem action];
|
|
|
|
|
|
|
|
if (action == @selector(removeFromQueue:))
|
|
|
|
{
|
|
|
|
for (PlaylistEntry *q in [self selectedObjects])
|
2008-03-01 03:29:21 +00:00
|
|
|
if (q.queuePosition >= 0)
|
2008-02-23 22:20:14 +00:00
|
|
|
return YES;
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (action == @selector(emptyQueueList:) && ([queueList count] < 1))
|
|
|
|
return NO;
|
|
|
|
|
2008-03-01 03:35:27 +00:00
|
|
|
if (action == @selector(stopAfterCurrent:) && currentEntry.stopAfter)
|
2008-02-25 17:02:06 +00:00
|
|
|
return NO;
|
|
|
|
|
2008-02-24 18:46:32 +00:00
|
|
|
// if nothing is selected, gray out these
|
|
|
|
if ([[self selectedObjects] count] < 1)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (action == @selector(remove:))
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
if (action == @selector(addToQueue:))
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
if (action == @selector(searchByArtist:))
|
|
|
|
return NO;
|
|
|
|
|
|
|
|
if (action == @selector(searchByAlbum:))
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2008-02-23 22:20:14 +00:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2008-05-09 21:24:49 +00:00
|
|
|
// Event inlets:
|
2009-02-28 22:22:33 +00:00
|
|
|
- (void)willInsertURLs:(NSArray*)urls origin:(URLOrigin)origin
|
2008-05-09 21:24:49 +00:00
|
|
|
{
|
|
|
|
if (![urls count])
|
|
|
|
return;
|
2009-02-28 22:40:30 +00:00
|
|
|
|
2008-05-09 21:24:49 +00:00
|
|
|
CGEventRef event = CGEventCreate(NULL /*default event source*/);
|
|
|
|
CGEventFlags mods = CGEventGetFlags(event);
|
|
|
|
CFRelease(event);
|
|
|
|
|
2009-02-28 22:40:30 +00:00
|
|
|
BOOL modifierPressed = ((mods & kCGEventFlagMaskCommand)!=0)&((mods & kCGEventFlagMaskControl)!=0);
|
|
|
|
modifierPressed |= ((mods & kCGEventFlagMaskShift)!=0);
|
2008-05-18 09:38:53 +00:00
|
|
|
|
2009-02-28 22:40:30 +00:00
|
|
|
NSString *behavior = [[NSUserDefaults standardUserDefaults] valueForKey:@"openingFilesBehavior"];
|
|
|
|
if (modifierPressed) {
|
|
|
|
behavior = [[NSUserDefaults standardUserDefaults] valueForKey:@"openingFilesAlteredBehavior"];
|
2009-02-28 22:22:33 +00:00
|
|
|
}
|
2009-02-28 22:40:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
BOOL shouldClear = modifierPressed; // By default, internal sources should not clear the playlist
|
|
|
|
if (origin == URLOriginExternal) { // For external insertions, we look at the preference
|
|
|
|
//possible settings are "clearAndPlay", "enqueue", "enqueueAndPlay"
|
|
|
|
shouldClear = [behavior isEqualToString:@"clearAndPlay"];
|
2009-02-28 22:22:33 +00:00
|
|
|
}
|
|
|
|
|
2009-02-28 22:40:30 +00:00
|
|
|
if (shouldClear) {
|
2008-05-09 21:24:49 +00:00
|
|
|
[self clear:self];
|
2009-02-28 22:22:33 +00:00
|
|
|
}
|
2008-05-09 21:24:49 +00:00
|
|
|
}
|
|
|
|
|
2009-02-28 22:40:30 +00:00
|
|
|
- (void)didInsertURLs:(NSArray*)urls origin:(URLOrigin)origin
|
2008-05-09 21:24:49 +00:00
|
|
|
{
|
2009-02-28 22:40:30 +00:00
|
|
|
if (![urls count])
|
2008-05-09 21:24:49 +00:00
|
|
|
return;
|
2009-02-28 22:40:30 +00:00
|
|
|
|
2008-05-09 21:24:49 +00:00
|
|
|
CGEventRef event = CGEventCreate(NULL);
|
|
|
|
CGEventFlags mods = CGEventGetFlags(event);
|
|
|
|
CFRelease(event);
|
|
|
|
|
2009-02-28 22:40:30 +00:00
|
|
|
BOOL modifierPressed = ((mods & kCGEventFlagMaskCommand)!=0)&((mods & kCGEventFlagMaskControl)!=0);
|
|
|
|
modifierPressed |= ((mods & kCGEventFlagMaskShift)!=0);
|
2008-05-09 21:24:49 +00:00
|
|
|
|
2009-02-28 22:40:30 +00:00
|
|
|
NSString *behavior = [[NSUserDefaults standardUserDefaults] valueForKey:@"openingFilesBehavior"];
|
|
|
|
if (modifierPressed) {
|
|
|
|
behavior = [[NSUserDefaults standardUserDefaults] valueForKey:@"openingFilesAlteredBehavior"];
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL shouldPlay = modifierPressed; // The default is NO for internal insertions
|
|
|
|
if (origin == URLOriginExternal) { // For external insertions, we look at the preference
|
|
|
|
shouldPlay = [behavior isEqualToString:@"clearAndPlay"] || [behavior isEqualToString:@"enqueueAndPlay"];;
|
|
|
|
}
|
|
|
|
|
2008-05-09 21:24:49 +00:00
|
|
|
//Auto start playback
|
2013-10-11 19:33:58 +00:00
|
|
|
if (shouldPlay && [[self content] count] > 0) {
|
2009-02-28 22:40:30 +00:00
|
|
|
[playbackController playEntry: [urls objectAtIndex:0]];
|
2009-02-28 22:22:33 +00:00
|
|
|
}
|
2008-05-09 21:24:49 +00:00
|
|
|
}
|
|
|
|
|
2008-02-22 15:26:46 +00:00
|
|
|
|
2005-06-02 18:16:43 +00:00
|
|
|
@end
|