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