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