1/* 2 Project: MPDCon 3 4 Copyright (C) 2004 5 6 Author: Daniel Luederwald 7 8 Created: 2004-05-12 17:59:14 +0200 by flip 9 10 Playlist Controller 11 12 This application is free software; you can redistribute it and/or 13 modify it under the terms of the GNU General Public 14 License as published by the Free Software Foundation; either 15 version 2 of the License, or (at your option) any later version. 16 17 This application is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 Library General Public License for more details. 21 22 You should have received a copy of the GNU General Public 23 License along with this library; if not, write to the Free 24 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. 25*/ 26 27#include <AppKit/AppKit.h> 28#include "PlaylistController.h" 29#include "SongRatingCell.h" 30 31/* --------------------- 32 - Private Interface - 33 ---------------------*/ 34@interface PlaylistController(Private) 35- (void) _doRemove; 36- (void) _moveRows: (NSArray *)rows atRow: (NSInteger)row; 37- (void) _addSongs: (NSArray *)paths atRow: (NSInteger)row; 38- (NSString *) _playlistLength; 39 40- (void) _filterListByString: (NSString *)filterString; 41@end 42 43@implementation PlaylistController 44 45/* -------------------------- 46 - Initialization Methods - 47 --------------------------*/ 48 49+ (id) sharedPlaylistController 50{ 51 static PlaylistController *_sharedPlaylistController = nil; 52 53 if (! _sharedPlaylistController) 54 { 55 _sharedPlaylistController = [[PlaylistController allocWithZone: 56 [self zone]] init]; 57 } 58 59 return _sharedPlaylistController; 60} 61 62- (id) init 63{ 64 65 if ((self = [self initWithWindowNibName: @"PlaylistViewer"]) != nil) 66 { 67 [self setWindowFrameAutosaveName: @"PlaylistViewer"]; 68 mpdController = [MPDController sharedMPDController]; 69 [playlist autorelease]; 70 playlist = [[mpdController getPlaylist] retain]; 71 } 72 return self; 73} 74 75- (void) dealloc 76{ 77 [playlist release]; 78 [playlistTimes release]; 79 [ratingCol release]; 80 81 [super dealloc]; 82} 83 84/* --------------- 85 - Gui Methods - 86 ---------------*/ 87 88- (void) awakeFromNib 89{ 90 NSNotificationCenter *defCenter; 91 92 SongRatingCell *ratingCell; 93 94 ASSIGN(ratingCol, [playlistTable tableColumnWithIdentifier: @"rating"]); 95 96 [playlistTable setAutosaveName: @"PlaylistTable"]; 97 [playlistTable setAutosaveTableColumns: YES]; 98 [playlistTable setTarget: self]; 99 [playlistTable setDoubleAction: @selector(doubleClicked:)]; 100 101 [playlistTable registerForDraggedTypes: 102 [NSArray arrayWithObjects: PlaylistDragType, CollectionDragType, nil]]; 103 104 [lengthView setFont: [NSFont systemFontOfSize: 12]]; 105 106 defCenter = [NSNotificationCenter defaultCenter]; 107 108 [defCenter addObserver: self 109 selector: @selector(songChanged:) 110 name: SongChangedNotification 111 object: nil]; 112 113 [defCenter addObserver: self 114 selector: @selector(playlistChanged:) 115 name: PlaylistChangedNotification 116 object: nil]; 117 118 [defCenter addObserver: self 119 selector: @selector(didNotConnect:) 120 name: DidNotConnectNotification 121 object: nil]; 122 123 currentSong = [mpdController getCurrentSongNr]; 124 125 ratingCell = [[SongRatingCell alloc] init]; 126 [ratingCol setDataCell:ratingCell]; 127 128 [self playlistChanged: nil]; 129 RELEASE(ratingCell); 130} 131 132- (void) removeSongs: (id)sender 133{ 134 switch ([removeSelector indexOfSelectedItem]) 135 { 136 case 1: 137 [mpdController clearPlaylist]; 138 break; 139 case 2: 140 [self _doRemove]; 141 break; 142 } 143} 144 145- (void) managePlaylists: (id)sender 146{ 147 [[PlaylistsManagerController sharedPLManagerController] showWindow: self]; 148} 149 150- (void) doubleClicked: (id)sender 151{ 152 int row; 153 154 if ([playlistTable clickedRow] == -1) 155 { 156 return; 157 } 158 159 row = [playlistTable selectedRow]; 160 [mpdController playSong: [[playlist objectAtIndex: row] getPos]]; 161} 162 163- (void) showCurrentSong: (id)sender 164{ 165 int state; 166 167 state = [mpdController getState]; 168 if ((state == state_PAUSE) || (state == state_PLAY)) 169 { 170 if (([playlist count] >= currentSong) && 171 ([[playlist objectAtIndex: currentSong -1] getPos] == currentSong -1)) 172 { 173 [playlistTable scrollRowToVisible: currentSong-1]; 174 [playlistTable selectRow: currentSong-1 byExtendingSelection: NO]; 175 } 176 else 177 { 178 NSBeep(); 179 } 180 } 181} 182 183- (void) shuffleList: (id)sender 184{ 185 [mpdController shuffleList]; 186} 187 188- (void) browseCollection: (id)sender 189{ 190 [[CollectionController sharedCollectionController] showWindow: self]; 191} 192 193- (void) filterList: (id)sender 194{ 195 int selectedRow; 196 int selectedPos = -1; 197 198 selectedRow = [playlistTable selectedRow]; 199 200 if (selectedRow != -1) 201 { 202 selectedPos = [[playlist objectAtIndex: selectedRow] getPos]; 203 } 204 205 [self playlistChanged: nil]; 206 207 if (selectedPos != -1) 208 { 209 int i; 210 211 for (i = 0; i < [playlist count]; i++) 212 { 213 if ([[playlist objectAtIndex: i] getPos] == selectedPos) 214 { 215 [playlistTable scrollRowToVisible: i]; 216 [playlistTable selectRow: i byExtendingSelection: NO]; 217 break; 218 } 219 } 220 } 221} 222 223- (void) clearFilter: (id)sender 224{ 225 [filterField setStringValue: @""]; 226 227 [self filterList: sender]; 228 229} 230 231/* ---------------------------- 232 - some convenience Methods - 233 ---------------------------- */ 234 235- (NSString *) playlistLength 236{ 237 return [self _playlistLength]; 238} 239 240/* -------------------------------- 241 - TableView dataSource Methods - 242 --------------------------------*/ 243 244- (NSInteger) numberOfRowsInTableView: (NSTableView *)tableView 245{ 246 return [playlist count]; 247} 248 249- (id) tableView: (NSTableView *)tableView 250objectValueForTableColumn: (NSTableColumn *)tableColumn row:(NSInteger)row 251{ 252 NSString *identifier; 253 254 identifier = [tableColumn identifier]; 255 256 if ([identifier isEqual: @"pos"]) 257 { 258 return [NSString stringWithFormat: @"%d", [[playlist objectAtIndex: row] getPos]+1]; 259 } 260 else if ([identifier isEqual: @"time"]) 261 { 262 return [playlistTimes objectAtIndex: row]; 263 } 264 else if ([identifier isEqual: @"play"]) 265 { 266 if (([[playlist objectAtIndex: row] getPos] == currentSong-1)) 267 { 268 int mState = [mpdController getState]; 269 270 if ((mState == state_PAUSE) || (mState == state_PLAY)) 271 { 272 return [NSImage imageNamed: @"Current.tiff"]; 273 } 274 else 275 { 276 return nil; 277 } 278 } 279 else 280 { 281 return nil; 282 } 283 } 284 else 285 { 286 return [[playlist objectAtIndex: row] valueForKey: identifier]; 287 } 288} 289 290 291/* ------------------------------ 292 - TableView dragging Methods - 293 ------------------------------*/ 294 295- (NSDragOperation) tableView: (NSTableView *)tv 296 validateDrop: (id <NSDraggingInfo>)info 297 proposedRow: (NSInteger)row 298 proposedDropOperation: (NSTableViewDropOperation)dropOperation 299{ 300 NSArray *typeArray; 301 NSString *availableType; 302 NSPasteboard *pboard; 303 NSDragOperation dragOperation; 304 305 pboard = [info draggingPasteboard]; 306 typeArray = [NSArray arrayWithObjects: PlaylistDragType, CollectionDragType, nil]; 307 availableType = [pboard availableTypeFromArray: typeArray]; 308 dragOperation = NSDragOperationNone; 309 310 if (availableType) 311 { 312 [tv setDropRow: row dropOperation: NSTableViewDropAbove]; 313 if ([[filterField stringValue] compare: @""] == NSOrderedSame) 314 { 315 if (([availableType isEqualToString: PlaylistDragType]) || 316 ([availableType isEqualToString: CollectionDragType])) 317 { 318 dragOperation = NSDragOperationMove; 319 } 320 } 321 322 } 323 return dragOperation; 324} 325 326- (BOOL) tableView: (NSTableView *)tv 327 acceptDrop: (id <NSDraggingInfo>)info 328 row: (NSInteger)row 329 dropOperation: (NSTableViewDropOperation)dropOperation 330{ 331 NSPasteboard *pboard; 332 NSArray *objectsList, *typeArray; 333 NSString *availableType; 334 BOOL accept; 335 336 pboard = [info draggingPasteboard]; 337 typeArray = [NSArray arrayWithObjects: PlaylistDragType, CollectionDragType, nil]; 338 339 availableType = [pboard availableTypeFromArray: typeArray]; 340 objectsList = [pboard propertyListForType: availableType]; 341 342 if ([objectsList count]) 343 { 344 if ([availableType isEqualToString: PlaylistDragType]) 345 { 346 [self _moveRows: objectsList atRow: row]; 347 [tv deselectAll: nil]; 348 } 349 else if ([availableType isEqualToString: CollectionDragType]) 350 { 351 [self _addSongs: objectsList atRow: row]; 352 } 353 accept = YES; 354 } 355 else 356 { 357 accept = NO; 358 } 359 360 return accept; 361} 362 363 364- (BOOL) tableView: (NSTableView *)tv 365 writeRows: (NSArray *)rows 366 toPasteboard: (NSPasteboard*)pboard 367{ 368 NSArray *typeArray; 369 BOOL accept; 370 unsigned int count; 371 372 count = [rows count]; 373 374 if (count > 0) 375 { 376 accept = YES; 377 typeArray = [NSArray arrayWithObjects: PlaylistDragType, nil]; 378 [pboard declareTypes: typeArray owner: self]; 379 [pboard setPropertyList: rows forType: PlaylistDragType]; 380 } 381 else 382 { 383 accept = NO; 384 } 385 386 return accept; 387} 388 389/* ------------------------ 390 - Notification Methods - 391 ------------------------*/ 392 393- (void) songChanged: (NSNotification *)aNotif 394{ 395 currentSong = [mpdController getCurrentSongNr]; 396 [playlistTable reloadData]; 397} 398 399- (void) playlistChanged: (NSNotification *)aNotif 400{ 401 int j; 402 403 [playlist release]; 404 405 playlist = [[mpdController getPlaylist] retain]; 406 407 if ([[filterField stringValue] compare: @""] != NSOrderedSame) 408 { 409 [self _filterListByString: [filterField stringValue]]; 410 } 411 412 [lengthView setStringValue: [NSString stringWithFormat: 413 _(@"Playlist Length: %@"), [self _playlistLength]]]; 414 415 [playlistTimes autorelease]; 416 417 playlistTimes = [[NSMutableArray alloc] init]; 418 419 for (j = 0; j < [playlist count]; j++) 420 { 421 int time; 422 int tSecs, tMins; 423 424 time = [[playlist objectAtIndex: j] getTotalTime]; 425 426 tSecs = (time % 60); 427 tMins = (int) time/60; 428 429 [playlistTimes addObject: [NSString stringWithFormat: @"%d:%02d", 430 tMins, tSecs]]; 431 } 432 433 [playlistTable reloadData]; 434 [playlistTable deselectAll: nil]; 435 currentSong = [mpdController getCurrentSongNr]; 436 [self showCurrentSong: nil]; 437} 438 439 440- (void) didNotConnect: (NSNotification *)aNotif 441{ 442 [[self window] performClose: self]; 443} 444 445@end 446 447/* ------------------- 448 - Private Methods - 449 -------------------*/ 450 451@implementation PlaylistController(Private) 452- (NSString *)_playlistLength 453{ 454 int length = 0; 455 if ([playlist count] != 0) 456 { 457 int i; 458 int tSecs, tMins, tHours; 459 460 for (i = 0; i < [playlist count]; i++) 461 { 462 length += [[playlist objectAtIndex: i] getTotalTime]; 463 } 464 465 tSecs = (length % 60); 466 tMins = (int) (length/60) % 60; 467 tHours = (int) length/3600; 468 469 return [NSString stringWithFormat: _(@"%d:%02d:%02d"), 470 tHours, tMins, tSecs]; 471 } 472 else 473 { 474 return @"0:00:00"; 475 } 476} 477 478- (void) _doRemove 479{ 480 NSIndexSet *selectedRows; 481 NSInteger i; 482 483 if ([playlistTable numberOfSelectedRows] == [mpdController getPlaylistLength]) 484 { 485 [mpdController clearPlaylist]; 486 return; 487 } 488 selectedRows = [playlistTable selectedRowIndexes]; 489 for (i=[selectedRows lastIndex];i != NSNotFound;i = [selectedRows indexLessThanIndex: i]) 490 { 491 [mpdController removeSong: [[playlist objectAtIndex: i] getPos]]; 492 } 493 [playlistTable deselectAll: self]; 494} 495 496 497- (void) _moveRows: (NSArray *)rows atRow: (NSInteger)row 498{ 499 NSUInteger i; 500 501 NSUInteger count = [rows count]; 502 503 NSUInteger ids[count]; 504 505 for (i = 0; i < count; i++) { 506 ids[i] = [[playlist objectAtIndex: [[rows objectAtIndex: i] intValue]] getID]; 507 } 508 509 for (i = 0; i < count; i++) { 510 if ([[rows objectAtIndex: 0] intValue] < row) 511 [mpdController moveSongWithID: ids[i] to: row-1]; 512 else 513 [mpdController moveSongWithID: ids[i] to: row+i]; 514 } 515 516 [playlistTable reloadData]; 517} 518 519- (void) _addSongs: (NSArray *)songs atRow: (NSInteger)row 520{ 521 NSUInteger i; 522 523 for (i = 0; i < [songs count]; i++) { 524 [mpdController addTrack: [songs objectAtIndex: i]]; 525 [mpdController moveSongNr: [mpdController getPlaylistLength]-1 to: (row+i)]; 526 } 527} 528 529- (void) _filterListByString: (NSString *) fString 530{ 531 NSMutableArray *tmpArray; 532 NSUInteger i; 533 534 tmpArray = [[NSMutableArray alloc] init]; 535 536 for (i = 0; i < [playlist count]; i++) { 537 if ([[[playlist objectAtIndex: i] getArtist] rangeOfString: fString options: NSCaseInsensitiveSearch].location != NSNotFound) { 538 [tmpArray addObject: [playlist objectAtIndex: i]]; 539 } else if ([[[playlist objectAtIndex: i] getTitle] rangeOfString: fString options: NSCaseInsensitiveSearch].location != NSNotFound) { 540 [tmpArray addObject: [playlist objectAtIndex: i]]; 541 } else if ([[[playlist objectAtIndex: i] getAlbum] rangeOfString: fString options: NSCaseInsensitiveSearch].location != NSNotFound) { 542 [tmpArray addObject: [playlist objectAtIndex: i]]; 543 } else if ([[[playlist objectAtIndex: i] getGenre] rangeOfString: fString options: NSCaseInsensitiveSearch].location != NSNotFound) { 544 [tmpArray addObject: [playlist objectAtIndex: i]]; 545 } 546 } 547 548 [playlist release]; 549 550 playlist = tmpArray; 551} 552 553-(void) tableView: (NSTableView*) aTableView 554 setObjectValue: (id) anObj 555 forTableColumn: (NSTableColumn*) aTableColumn 556 row: (NSInteger) rowIndex 557{ 558 if (aTableColumn == ratingCol) { 559 id playlistItem; 560 561 /* We can't keep that as an assertion now, as it can easily fail when 562 * the broken GNUstep NSTableView lets you edit the string value for the cell. 563 */ 564 if ([anObj isKindOfClass: [NSNumber class]] == NO) { 565 NSLog(@"Warning: %@ is not a number value.", anObj); 566 } 567 568 playlistItem = [playlist objectAtIndex: rowIndex]; 569 [playlistItem setRating: [anObj integerValue]]; 570 } 571} 572 573@end 574