xref: /haiku/src/apps/mediaplayer/playlist/PlaylistListView.cpp (revision b08627f310bb2e80bca50176e7a758182384735a)
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