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