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