1 /* 2 * Copyright 2007-2009 Stephan Aßmus <superstippi@gmx.de>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "PlaylistListView.h" 8 9 #include <new> 10 #include <stdio.h> 11 12 #include <Autolock.h> 13 #include <GradientLinear.h> 14 #include <Message.h> 15 #include <ScrollBar.h> 16 #include <ScrollView.h> 17 #include <Shape.h> 18 #include <Window.h> 19 20 #include "CommandStack.h" 21 #include "Controller.h" 22 #include "ControllerObserver.h" 23 #include "CopyPLItemsCommand.h" 24 #include "DurationToString.h" 25 #include "ImportPLItemsCommand.h" 26 #include "ListViews.h" 27 #include "MovePLItemsCommand.h" 28 #include "PlaybackState.h" 29 #include "Playlist.h" 30 #include "PlaylistItem.h" 31 #include "PlaylistObserver.h" 32 #include "RandomizePLItemsCommand.h" 33 #include "RemovePLItemsCommand.h" 34 35 36 using std::nothrow; 37 38 39 enum { 40 DISPLAY_NAME = 0, 41 DISPLAY_PATH = 1 42 }; 43 44 45 static float 46 playback_mark_size(const font_height& fh) 47 { 48 return ceilf(fh.ascent * 0.7); 49 } 50 51 52 static float 53 text_offset(const font_height& fh) 54 { 55 return ceilf(fh.ascent * 0.8); 56 } 57 58 59 class PlaylistListView::Item : public SimpleItem, 60 public PlaylistItem::Listener { 61 public: 62 Item(PlaylistItem* item); 63 virtual ~Item(); 64 65 void Draw(BView* owner, BRect frame, 66 const font_height& fh, 67 bool tintedLine, uint32 mode, 68 bool active, 69 uint32 playbackState); 70 71 virtual void ItemChanged(const PlaylistItem* item); 72 73 #if __GNUC__ == 2 74 virtual void Draw(BView* owner, BRect frame, uint32 flags); 75 #else 76 using SimpleItem::Draw; 77 #endif 78 79 private: 80 PlaylistItemRef fItem; 81 82 }; 83 84 85 // #pragma mark - 86 87 88 PlaylistListView::Item::Item(PlaylistItem* item) 89 : 90 SimpleItem(item->Name().String()), 91 fItem(item) 92 { 93 fItem->AddListener(this); 94 } 95 96 97 PlaylistListView::Item::~Item() 98 { 99 fItem->RemoveListener(this); 100 } 101 102 103 void 104 PlaylistListView::Item::Draw(BView* owner, BRect frame, const font_height& fh, 105 bool tintedLine, uint32 mode, bool active, uint32 playbackState) 106 { 107 rgb_color color = ui_color(B_LIST_BACKGROUND_COLOR); 108 109 if (IsSelected()) 110 color = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR); 111 if (tintedLine) 112 color = tint_color(color, 1.04); 113 // background 114 owner->SetLowColor(color); 115 owner->FillRect(frame, B_SOLID_LOW); 116 // label 117 if (IsSelected()) 118 owner->SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR)); 119 else 120 owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); 121 const char* text = Text(); 122 switch (mode) { 123 case DISPLAY_NAME: 124 // TODO 125 break; 126 case DISPLAY_PATH: 127 // TODO 128 break; 129 default: 130 break; 131 } 132 133 float playbackMarkSize = playback_mark_size(fh); 134 float textOffset = text_offset(fh); 135 136 char buffer[64]; 137 bigtime_t duration = fItem->Duration(); 138 duration /= 1000000; 139 duration_to_string(duration, buffer, sizeof(buffer)); 140 141 BString truncatedDuration(buffer); 142 owner->TruncateString(&truncatedDuration, B_TRUNCATE_END, 143 frame.Width() - playbackMarkSize - textOffset); 144 float truncatedWidth = owner->StringWidth(truncatedDuration.String()); 145 owner->DrawString(truncatedDuration.String(), 146 BPoint(frame.right - truncatedWidth, 147 floorf(frame.top + frame.bottom + fh.ascent) / 2 - 1)); 148 149 BString truncatedString(text); 150 owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE, 151 frame.Width() - playbackMarkSize - textOffset - truncatedWidth); 152 owner->DrawString(truncatedString.String(), 153 BPoint(frame.left + playbackMarkSize + textOffset, 154 floorf(frame.top + frame.bottom + fh.ascent) / 2 - 1)); 155 156 // playmark 157 if (active) { 158 rgb_color green = (rgb_color){ 0, 255, 0, 255 }; 159 if (playbackState != PLAYBACK_STATE_PLAYING) 160 green = tint_color(color, B_DARKEN_1_TINT); 161 162 BRect r(0, 0, playbackMarkSize, playbackMarkSize); 163 r.OffsetTo(frame.left + 4, 164 ceilf((frame.top + frame.bottom - playbackMarkSize) / 2)); 165 166 uint32 flags = owner->Flags(); 167 owner->SetFlags(flags | B_SUBPIXEL_PRECISE); 168 169 BShape shape; 170 shape.MoveTo(r.LeftTop()); 171 shape.LineTo(r.LeftBottom()); 172 shape.LineTo(BPoint(r.right, (r.top + r.bottom) / 2)); 173 shape.Close(); 174 175 owner->MovePenTo(B_ORIGIN); 176 owner->FillShape(&shape); 177 178 shape.Clear(); 179 r.InsetBy(1, 1); 180 shape.MoveTo(r.LeftTop()); 181 shape.LineTo(r.LeftBottom()); 182 shape.LineTo(BPoint(r.right, (r.top + r.bottom) / 2)); 183 shape.Close(); 184 185 BGradientLinear gradient; 186 gradient.SetStart(r.LeftTop()); 187 gradient.SetEnd(r.LeftBottom()); 188 gradient.AddColor(tint_color(green, B_LIGHTEN_1_TINT), 0); 189 gradient.AddColor(tint_color(green, B_DARKEN_1_TINT), 255.0); 190 191 owner->FillShape(&shape, gradient); 192 193 owner->SetFlags(flags); 194 } 195 } 196 197 198 void 199 PlaylistListView::Item::ItemChanged(const PlaylistItem* item) 200 { 201 // TODO: Invalidate 202 } 203 204 205 #if __GNUC__ == 2 206 207 void 208 PlaylistListView::Item::Draw(BView* owner, BRect frame, uint32 flags) 209 { 210 SimpleItem::Draw(owner, frame, flags); 211 } 212 213 #endif 214 215 216 // #pragma mark - 217 218 219 PlaylistListView::PlaylistListView(BRect frame, Playlist* playlist, 220 Controller* controller, CommandStack* stack) 221 : 222 SimpleListView(frame, "playlist listview", NULL), 223 224 fPlaylist(playlist), 225 fPlaylistObserver(new PlaylistObserver(this)), 226 227 fController(controller), 228 fControllerObserver(new ControllerObserver(this, 229 OBSERVE_PLAYBACK_STATE_CHANGES)), 230 231 fCommandStack(stack), 232 233 fCurrentPlaylistIndex(-1), 234 fPlaybackState(PLAYBACK_STATE_STOPPED), 235 236 fLastClickedItem(NULL) 237 { 238 fPlaylist->AddListener(fPlaylistObserver); 239 fController->AddListener(fControllerObserver); 240 241 SetFlags(Flags() | B_SUBPIXEL_PRECISE); 242 } 243 244 245 PlaylistListView::~PlaylistListView() 246 { 247 for (int32 i = CountItems() - 1; i >= 0; i--) 248 _RemoveItem(i); 249 fPlaylist->RemoveListener(fPlaylistObserver); 250 delete fPlaylistObserver; 251 fController->RemoveListener(fControllerObserver); 252 delete fControllerObserver; 253 } 254 255 256 void 257 PlaylistListView::AttachedToWindow() 258 { 259 _FullSync(); 260 SimpleListView::AttachedToWindow(); 261 262 GetFontHeight(&fFontHeight); 263 MakeFocus(true); 264 } 265 266 267 void 268 PlaylistListView::MessageReceived(BMessage* message) 269 { 270 // message->PrintToStream(); 271 switch (message->what) { 272 // PlaylistObserver messages 273 case MSG_PLAYLIST_ITEM_ADDED: 274 { 275 PlaylistItem* item; 276 int32 index; 277 if (message->FindPointer("item", (void**)&item) == B_OK 278 && message->FindInt32("index", &index) == B_OK) 279 _AddItem(item, index); 280 break; 281 } 282 case MSG_PLAYLIST_ITEM_REMOVED: 283 { 284 int32 index; 285 if (message->FindInt32("index", &index) == B_OK) 286 _RemoveItem(index); 287 break; 288 } 289 case MSG_PLAYLIST_ITEMS_SORTED: 290 _FullSync(); 291 break; 292 case MSG_PLAYLIST_CURRENT_ITEM_CHANGED: 293 { 294 int32 index; 295 if (message->FindInt32("index", &index) == B_OK) 296 _SetCurrentPlaylistIndex(index); 297 break; 298 } 299 case MSG_PLAYLIST_IMPORT_FAILED: 300 break; 301 302 // ControllerObserver messages 303 case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED: 304 { 305 uint32 state; 306 if (message->FindInt32("state", (int32*)&state) == B_OK) 307 _SetPlaybackState(state); 308 break; 309 } 310 311 case B_SIMPLE_DATA: 312 if (message->HasRef("refs")) 313 ItemsReceived(message, fDropIndex); 314 else if (message->HasPointer("list")) 315 SimpleListView::MessageReceived(message); 316 break; 317 case B_REFS_RECEIVED: 318 ItemsReceived(message, fDropIndex); 319 break; 320 321 default: 322 SimpleListView::MessageReceived(message); 323 break; 324 } 325 } 326 327 328 void 329 PlaylistListView::MouseDown(BPoint where) 330 { 331 if (!IsFocus()) 332 MakeFocus(true); 333 334 int32 clicks; 335 if (Window()->CurrentMessage()->FindInt32("clicks", &clicks) < B_OK) 336 clicks = 1; 337 338 bool handled = false; 339 340 float playbackMarkSize = playback_mark_size(fFontHeight); 341 float textOffset = text_offset(fFontHeight); 342 343 for (int32 i = 0; 344 Item* item = dynamic_cast<Item*>(ItemAt(i)); i++) { 345 BRect r = ItemFrame(i); 346 if (r.Contains(where)) { 347 if (clicks == 2) { 348 // only do something if user clicked the same item twice 349 if (fLastClickedItem == item) { 350 BAutolock _(fPlaylist); 351 fPlaylist->SetCurrentItemIndex(i, true); 352 handled = true; 353 } 354 } else { 355 // remember last clicked item 356 fLastClickedItem = item; 357 if (i == fCurrentPlaylistIndex) { 358 r.right = r.left + playbackMarkSize + textOffset; 359 if (r.Contains (where)) { 360 fController->TogglePlaying(); 361 handled = true; 362 } 363 } 364 } 365 break; 366 } 367 } 368 369 if (!handled) 370 SimpleListView::MouseDown(where); 371 } 372 373 374 void 375 PlaylistListView::KeyDown(const char* bytes, int32 numBytes) 376 { 377 if (numBytes < 1) 378 return; 379 380 BMessage* msg = Window()->CurrentMessage(); 381 uint32 modifier = msg->FindInt32("modifiers"); 382 383 int32 count; 384 int32 index; 385 386 switch (bytes[0]) { 387 case B_SPACE: 388 fController->TogglePlaying(); 389 break; 390 391 case B_BACKSPACE: 392 case B_DELETE: 393 RemoveSelected(); 394 break; 395 396 case B_ENTER: 397 count = CountItems(); 398 if (count == 0) 399 break; 400 index = CurrentSelection(0); 401 if (index < 0) 402 break; 403 fPlaylist->SetCurrentItemIndex(index, true); 404 fController->Play(); 405 break; 406 407 case B_ESCAPE: 408 fController->Stop(); 409 break; 410 411 case B_RIGHT_ARROW: 412 if ((modifier & B_SHIFT_KEY) != 0) 413 _Wind(30000000LL, 5); 414 else 415 _Wind(5000000LL, 1); 416 break; 417 418 case B_LEFT_ARROW: 419 if ((modifier & B_SHIFT_KEY) != 0) 420 _Wind(-30000000LL, -5); 421 else 422 _Wind(-5000000LL, -1); 423 break; 424 default: 425 DragSortableListView::KeyDown(bytes, numBytes); 426 } 427 } 428 429 430 void 431 PlaylistListView::SkipBackward() 432 { 433 BAutolock _(fPlaylist); 434 int32 index = fPlaylist->CurrentItemIndex() - 1; 435 if (index < 0) 436 index = 0; 437 fPlaylist->SetCurrentItemIndex(index, true); 438 } 439 440 441 void 442 PlaylistListView::SkipForward() 443 { 444 BAutolock _(fPlaylist); 445 int32 index = fPlaylist->CurrentItemIndex() + 1; 446 if (index >= fPlaylist->CountItems()) 447 index = fPlaylist->CountItems() - 1; 448 fPlaylist->SetCurrentItemIndex(index, true); 449 } 450 451 452 void 453 PlaylistListView::_Wind(bigtime_t howMuch, int64 frames) 454 { 455 if (!fController->Lock()) 456 return; 457 458 if (frames != 0 && !fController->IsPlaying()) { 459 int64 newFrame = fController->CurrentFrame() + frames; 460 fController->SetFramePosition(newFrame); 461 } else { 462 bigtime_t seekTime = fController->TimePosition() + howMuch; 463 if (seekTime < 0) { 464 SkipBackward(); 465 } else if (seekTime > fController->TimeDuration()) { 466 SkipForward(); 467 } else 468 fController->SetTimePosition(seekTime); 469 } 470 471 fController->Unlock(); 472 } 473 474 475 void 476 PlaylistListView::MoveItems(const BList& indices, int32 toIndex) 477 { 478 fCommandStack->Perform(new (nothrow) MovePLItemsCommand(fPlaylist, 479 (int32*)indices.Items(), indices.CountItems(), toIndex)); 480 } 481 482 483 void 484 PlaylistListView::CopyItems(const BList& indices, int32 toIndex) 485 { 486 fCommandStack->Perform(new (nothrow) CopyPLItemsCommand(fPlaylist, 487 (int32*)indices.Items(), indices.CountItems(), toIndex)); 488 } 489 490 491 void 492 PlaylistListView::RemoveItemList(const BList& indices) 493 { 494 RemoveItemList(indices, false); 495 } 496 497 498 void 499 PlaylistListView::DrawListItem(BView* owner, int32 index, BRect frame) const 500 { 501 if (Item* item = dynamic_cast<Item*>(ItemAt(index))) { 502 item->Draw(owner, frame, fFontHeight, index % 2, 503 DISPLAY_NAME, index == fCurrentPlaylistIndex, fPlaybackState); 504 } 505 } 506 507 508 void 509 PlaylistListView::ItemsReceived(const BMessage* message, int32 appendIndex) 510 { 511 if (fCommandStack->Perform(new (nothrow) ImportPLItemsCommand(fPlaylist, 512 message, appendIndex)) != B_OK) { 513 fPlaylist->NotifyImportFailed(); 514 } 515 } 516 517 518 void 519 PlaylistListView::Randomize() 520 { 521 int32 count = CountItems(); 522 if (count == 0) 523 return; 524 525 BList indices; 526 527 // add current selection 528 count = 0; 529 while (true) { 530 int32 index = CurrentSelection(count); 531 if (index < 0) 532 break; 533 if (!indices.AddItem((void*)(addr_t)index)) 534 return; 535 count++; 536 } 537 538 // was anything selected? 539 if (count == 0) { 540 // no selection, simply add all items 541 count = CountItems(); 542 for (int32 i = 0; i < count; i++) { 543 if (!indices.AddItem((void*)(addr_t)i)) 544 return; 545 } 546 } 547 548 fCommandStack->Perform(new (nothrow) RandomizePLItemsCommand(fPlaylist, 549 (int32*)indices.Items(), indices.CountItems())); 550 } 551 552 553 void 554 PlaylistListView::RemoveSelectionToTrash() 555 { 556 BList indices; 557 GetSelectedItems(indices); 558 RemoveItemList(indices, true); 559 } 560 561 562 void 563 PlaylistListView::RemoveToTrash(int32 index) 564 { 565 BList indices; 566 indices.AddItem((void*)(addr_t)index); 567 RemoveItemList(indices, true); 568 } 569 570 571 void 572 PlaylistListView::RemoveItemList(const BList& indices, bool intoTrash) 573 { 574 fCommandStack->Perform(new (nothrow) RemovePLItemsCommand(fPlaylist, 575 (int32*)indices.Items(), indices.CountItems(), intoTrash)); 576 } 577 578 579 // #pragma mark - 580 581 582 void 583 PlaylistListView::_FullSync() 584 { 585 if (!fPlaylist->Lock()) 586 return; 587 588 // detaching the scrollbar temporarily will 589 // make this much quicker 590 BScrollBar* scrollBar = ScrollBar(B_VERTICAL); 591 if (scrollBar) { 592 if (Window()) 593 Window()->UpdateIfNeeded(); 594 scrollBar->SetTarget((BView*)NULL); 595 } 596 597 for (int32 i = CountItems() - 1; i >= 0; i--) 598 _RemoveItem(i); 599 600 int32 count = fPlaylist->CountItems(); 601 for (int32 i = 0; i < count; i++) 602 _AddItem(fPlaylist->ItemAt(i), i); 603 604 _SetCurrentPlaylistIndex(fPlaylist->CurrentItemIndex()); 605 _SetPlaybackState(fController->PlaybackState()); 606 607 // reattach scrollbar and sync it by calling FrameResized() 608 if (scrollBar) { 609 scrollBar->SetTarget(this); 610 FrameResized(Bounds().Width(), Bounds().Height()); 611 } 612 613 fPlaylist->Unlock(); 614 } 615 616 617 void 618 PlaylistListView::_AddItem(PlaylistItem* _item, int32 index) 619 { 620 if (_item == NULL) 621 return; 622 623 Item* item = new (nothrow) Item(_item); 624 if (item != NULL) 625 AddItem(item, index); 626 } 627 628 629 void 630 PlaylistListView::_RemoveItem(int32 index) 631 { 632 delete RemoveItem(index); 633 } 634 635 636 void 637 PlaylistListView::_SetCurrentPlaylistIndex(int32 index) 638 { 639 if (fCurrentPlaylistIndex == index) 640 return; 641 642 InvalidateItem(fCurrentPlaylistIndex); 643 fCurrentPlaylistIndex = index; 644 InvalidateItem(fCurrentPlaylistIndex); 645 } 646 647 648 void 649 PlaylistListView::_SetPlaybackState(uint32 state) 650 { 651 if (fPlaybackState == state) 652 return; 653 654 fPlaybackState = state; 655 InvalidateItem(fCurrentPlaylistIndex); 656 } 657 658 659