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