1 /* 2 * Copyright 2007-2008, Haiku. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 #include "PlaylistListView.h" 9 10 #include <new> 11 #include <stdio.h> 12 13 #include <Autolock.h> 14 #include <Message.h> 15 #include <ScrollBar.h> 16 #include <ScrollView.h> 17 #include <Window.h> 18 19 #include "CommandStack.h" 20 #include "Controller.h" 21 #include "ControllerObserver.h" 22 #include "CopyPLItemsCommand.h" 23 #include "ImportPLItemsCommand.h" 24 #include "ListViews.h" 25 #include "MovePLItemsCommand.h" 26 #include "PlaybackState.h" 27 #include "Playlist.h" 28 #include "PlaylistObserver.h" 29 #include "RandomizePLItemsCommand.h" 30 #include "RemovePLItemsCommand.h" 31 32 using std::nothrow; 33 34 35 enum { 36 DISPLAY_NAME = 0, 37 DISPLAY_PATH = 1 38 }; 39 40 41 static float 42 playback_mark_size(const font_height& fh) 43 { 44 return ceilf(fh.ascent * 0.7); 45 } 46 47 48 static float 49 text_offset(const font_height& fh) 50 { 51 return ceilf(fh.ascent * 0.8); 52 } 53 54 55 class PlaylistItem : public SimpleItem { 56 public: 57 PlaylistItem(const entry_ref& ref); 58 virtual ~PlaylistItem(); 59 60 void Draw(BView* owner, BRect frame, 61 const font_height& fh, 62 bool tintedLine, uint32 mode, 63 bool active, 64 uint32 playbackState); 65 66 private: 67 entry_ref fRef; 68 69 }; 70 71 72 PlaylistItem::PlaylistItem(const entry_ref& ref) 73 : SimpleItem(ref.name), 74 fRef(ref) 75 { 76 } 77 78 79 PlaylistItem::~PlaylistItem() 80 { 81 } 82 83 84 void 85 PlaylistItem::Draw(BView* owner, BRect frame, const font_height& fh, 86 bool tintedLine, uint32 mode, bool active, uint32 playbackState) 87 { 88 rgb_color color = (rgb_color){ 255, 255, 255, 255 }; 89 if (tintedLine) 90 color = tint_color(color, 1.04); 91 // background 92 if (IsSelected()) 93 color = tint_color(color, B_DARKEN_2_TINT); 94 owner->SetLowColor(color); 95 owner->FillRect(frame, B_SOLID_LOW); 96 // label 97 rgb_color black = (rgb_color){ 0, 0, 0, 255 }; 98 owner->SetHighColor(black); 99 const char* text = Text(); 100 switch (mode) { 101 case DISPLAY_NAME: 102 // TODO 103 break; 104 case DISPLAY_PATH: 105 // TODO 106 break; 107 default: 108 break; 109 } 110 111 float playbackMarkSize = playback_mark_size(fh); 112 float textOffset = text_offset(fh); 113 114 BString truncatedString(text); 115 owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE, 116 frame.Width() - playbackMarkSize - textOffset); 117 owner->DrawString(truncatedString.String(), 118 BPoint(frame.left + playbackMarkSize + textOffset, 119 floorf(frame.top + frame.bottom + fh.ascent) / 2 - 1)); 120 121 // playmark 122 if (active) { 123 rgb_color green = (rgb_color){ 0, 255, 0, 255 }; 124 if (playbackState != PLAYBACK_STATE_PLAYING) 125 green = tint_color(color, B_DARKEN_1_TINT); 126 127 BRect r(0, 0, playbackMarkSize, playbackMarkSize); 128 r.OffsetTo(frame.left + 4, 129 ceilf((frame.top + frame.bottom) / 2) - 5); 130 131 BPoint arrow[3]; 132 arrow[0] = r.LeftTop(); 133 arrow[1] = r.LeftBottom(); 134 arrow[2].x = r.right; 135 arrow[2].y = (r.top + r.bottom) / 2; 136 #ifdef __HAIKU__ 137 owner->SetPenSize(2); 138 owner->StrokePolygon(arrow, 3); 139 owner->SetPenSize(1); 140 #else 141 rgb_color lightGreen = tint_color(green, B_LIGHTEN_2_TINT); 142 rgb_color darkGreen = tint_color(green, B_DARKEN_2_TINT); 143 owner->BeginLineArray(6); 144 // black outline 145 owner->AddLine(arrow[0], arrow[1], black); 146 owner->AddLine(BPoint(arrow[1].x + 1.0, arrow[1].y - 1.0), 147 arrow[2], black); 148 owner->AddLine(arrow[0], arrow[2], black); 149 // inset arrow 150 arrow[0].x += 1.0; 151 arrow[0].y += 2.0; 152 arrow[1].x += 1.0; 153 arrow[1].y -= 2.0; 154 arrow[2].x -= 2.0; 155 // highlights and shadow 156 owner->AddLine(arrow[1], arrow[2], darkGreen); 157 owner->AddLine(arrow[0], arrow[2], lightGreen); 158 owner->AddLine(arrow[0], arrow[1], lightGreen); 159 owner->EndLineArray(); 160 // fill green 161 arrow[0].x += 1.0; 162 arrow[0].y += 1.0; 163 arrow[1].x += 1.0; 164 arrow[1].y -= 1.0; 165 arrow[2].x -= 2.0; 166 #endif // __HAIKU__ 167 owner->SetLowColor(owner->HighColor()); 168 owner->SetHighColor(green); 169 owner->FillPolygon(arrow, 3); 170 } 171 } 172 173 174 // #pragma mark - 175 176 177 PlaylistListView::PlaylistListView(BRect frame, Playlist* playlist, 178 Controller* controller, CommandStack* stack) 179 : SimpleListView(frame, "playlist listview", NULL) 180 181 , fPlaylist(playlist) 182 , fPlaylistObserver(new PlaylistObserver(this)) 183 184 , fController(controller) 185 , fControllerObserver(new ControllerObserver(this, 186 OBSERVE_PLAYBACK_STATE_CHANGES)) 187 188 , fCommandStack(stack) 189 190 , fCurrentPlaylistIndex(-1) 191 , fPlaybackState(PLAYBACK_STATE_STOPPED) 192 193 , fLastClickedItem(NULL) 194 { 195 fPlaylist->AddListener(fPlaylistObserver); 196 fController->AddListener(fControllerObserver); 197 198 #ifdef __HAIKU__ 199 SetFlags(Flags() | B_SUBPIXEL_PRECISE); 200 #endif 201 } 202 203 204 PlaylistListView::~PlaylistListView() 205 { 206 fPlaylist->RemoveListener(fPlaylistObserver); 207 delete fPlaylistObserver; 208 fController->RemoveListener(fControllerObserver); 209 delete fControllerObserver; 210 } 211 212 213 void 214 PlaylistListView::AttachedToWindow() 215 { 216 _FullSync(); 217 SimpleListView::AttachedToWindow(); 218 219 GetFontHeight(&fFontHeight); 220 MakeFocus(true); 221 } 222 223 224 void 225 PlaylistListView::MessageReceived(BMessage* message) 226 { 227 switch (message->what) { 228 // PlaylistObserver messages 229 case MSG_PLAYLIST_REF_ADDED: { 230 entry_ref ref; 231 int32 index; 232 if (message->FindRef("refs", &ref) == B_OK 233 && message->FindInt32("index", &index) == B_OK) 234 _AddItem(ref, index); 235 break; 236 } 237 case MSG_PLAYLIST_REF_REMOVED: { 238 int32 index; 239 if (message->FindInt32("index", &index) == B_OK) 240 _RemoveItem(index); 241 break; 242 } 243 case MSG_PLAYLIST_REFS_SORTED: 244 _FullSync(); 245 break; 246 case MSG_PLAYLIST_CURRENT_REF_CHANGED: { 247 int32 index; 248 if (message->FindInt32("index", &index) == B_OK) 249 _SetCurrentPlaylistIndex(index); 250 break; 251 } 252 253 // ControllerObserver messages 254 case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED: { 255 uint32 state; 256 if (message->FindInt32("state", (int32*)&state) == B_OK) 257 _SetPlaybackState(state); 258 break; 259 } 260 261 case B_SIMPLE_DATA: 262 if (message->HasRef("refs")) 263 RefsReceived(message, fDropIndex); 264 else if (message->HasPointer("list")) 265 SimpleListView::MessageReceived(message); 266 break; 267 case B_REFS_RECEIVED: 268 RefsReceived(message, fDropIndex); 269 break; 270 271 default: 272 SimpleListView::MessageReceived(message); 273 break; 274 } 275 } 276 277 278 void 279 PlaylistListView::MouseDown(BPoint where) 280 { 281 if (!IsFocus()) 282 MakeFocus(true); 283 284 int32 clicks; 285 if (Window()->CurrentMessage()->FindInt32("clicks", &clicks) < B_OK) 286 clicks = 1; 287 288 bool handled = false; 289 290 float playbackMarkSize = playback_mark_size(fFontHeight); 291 float textOffset = text_offset(fFontHeight); 292 293 for (int32 i = 0; 294 PlaylistItem* item = dynamic_cast<PlaylistItem*>(ItemAt(i)); i++) { 295 BRect r = ItemFrame(i); 296 if (r.Contains(where)) { 297 if (clicks == 2) { 298 // only do something if user clicked the same item twice 299 if (fLastClickedItem == item) { 300 BAutolock _(fPlaylist); 301 fPlaylist->SetCurrentRefIndex(i); 302 handled = true; 303 } 304 } else { 305 // remember last clicked item 306 fLastClickedItem = item; 307 if (i == fCurrentPlaylistIndex) { 308 r.right = r.left + playbackMarkSize + textOffset; 309 if (r.Contains (where)) { 310 fController->TogglePlaying(); 311 handled = true; 312 } 313 } 314 } 315 break; 316 } 317 } 318 319 if (!handled) 320 SimpleListView::MouseDown(where); 321 } 322 323 324 void 325 PlaylistListView::KeyDown(const char* bytes, int32 numBytes) 326 { 327 if (numBytes < 1) 328 return; 329 330 if ((bytes[0] == B_BACKSPACE) || (bytes[0] == B_DELETE)) 331 RemoveSelected(); 332 333 DragSortableListView::KeyDown(bytes, numBytes); 334 } 335 336 337 void 338 PlaylistListView::MoveItems(BList& indices, int32 toIndex) 339 { 340 fCommandStack->Perform(new (nothrow) MovePLItemsCommand(fPlaylist, 341 (int32*)indices.Items(), indices.CountItems(), toIndex)); 342 } 343 344 345 void 346 PlaylistListView::CopyItems(BList& indices, int32 toIndex) 347 { 348 fCommandStack->Perform(new (nothrow) CopyPLItemsCommand(fPlaylist, 349 (int32*)indices.Items(), indices.CountItems(), toIndex)); 350 } 351 352 353 void 354 PlaylistListView::RemoveItemList(BList& indices) 355 { 356 fCommandStack->Perform(new (nothrow) RemovePLItemsCommand(fPlaylist, 357 (int32*)indices.Items(), indices.CountItems())); 358 } 359 360 361 void 362 PlaylistListView::DrawListItem(BView* owner, int32 index, BRect frame) const 363 { 364 if (PlaylistItem* item = dynamic_cast<PlaylistItem*>(ItemAt(index))) { 365 item->Draw(owner, frame, fFontHeight, index % 2, 366 DISPLAY_NAME, index == fCurrentPlaylistIndex, fPlaybackState); 367 } 368 } 369 370 371 void 372 PlaylistListView::RefsReceived(BMessage* message, int32 appendIndex) 373 { 374 fCommandStack->Perform(new (nothrow) ImportPLItemsCommand(fPlaylist, 375 message, appendIndex)); 376 } 377 378 379 void 380 PlaylistListView::Randomize() 381 { 382 int32 count = CountItems(); 383 if (count == 0) 384 return; 385 386 BList indices; 387 388 // add current selection 389 count = 0; 390 while (true) { 391 int32 index = CurrentSelection(count); 392 if (index < 0) 393 break; 394 if (!indices.AddItem((void*)index)) 395 return; 396 count++; 397 } 398 399 // was anything selected? 400 if (count == 0) { 401 // no selection, simply add all items 402 count = CountItems(); 403 for (int32 i = 0; i < count; i++) { 404 if (!indices.AddItem((void*)i)) 405 return; 406 } 407 } 408 409 fCommandStack->Perform(new (nothrow) RandomizePLItemsCommand(fPlaylist, 410 (int32*)indices.Items(), indices.CountItems())); 411 } 412 413 414 // #pragma mark - 415 416 417 void 418 PlaylistListView::_FullSync() 419 { 420 if (!fPlaylist->Lock()) 421 return; 422 423 // detaching the scrollbar temporarily will 424 // make this much quicker 425 BScrollBar* scrollBar = ScrollBar(B_VERTICAL); 426 if (scrollBar) { 427 if (Window()) 428 Window()->UpdateIfNeeded(); 429 scrollBar->SetTarget((BView*)NULL); 430 } 431 432 MakeEmpty(); 433 434 int32 count = fPlaylist->CountItems(); 435 for (int32 i = 0; i < count; i++) { 436 entry_ref ref; 437 if (fPlaylist->GetRefAt(i, &ref) == B_OK) 438 _AddItem(ref, i); 439 } 440 441 _SetCurrentPlaylistIndex(fPlaylist->CurrentRefIndex()); 442 _SetPlaybackState(fController->PlaybackState()); 443 444 // reattach scrollbar and sync it by calling FrameResized() 445 if (scrollBar) { 446 scrollBar->SetTarget(this); 447 FrameResized(Bounds().Width(), Bounds().Height()); 448 } 449 450 fPlaylist->Unlock(); 451 } 452 453 454 void 455 PlaylistListView::_AddItem(const entry_ref& ref, int32 index) 456 { 457 PlaylistItem* item = new (nothrow) PlaylistItem(ref); 458 if (item) 459 AddItem(item, index); 460 } 461 462 463 void 464 PlaylistListView::_RemoveItem(int32 index) 465 { 466 delete RemoveItem(index); 467 } 468 469 470 void 471 PlaylistListView::_SetCurrentPlaylistIndex(int32 index) 472 { 473 if (fCurrentPlaylistIndex == index) 474 return; 475 476 InvalidateItem(fCurrentPlaylistIndex); 477 fCurrentPlaylistIndex = index; 478 InvalidateItem(fCurrentPlaylistIndex); 479 } 480 481 482 void 483 PlaylistListView::_SetPlaybackState(uint32 state) 484 { 485 if (fPlaybackState == state) 486 return; 487 488 fPlaybackState = state; 489 InvalidateItem(fCurrentPlaylistIndex); 490 } 491 492 493