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
35using std::nothrow;
36
37
38enum {
39	DISPLAY_NAME	= 0,
40	DISPLAY_PATH	= 1
41};
42
43
44static float
45playback_mark_size(const font_height& fh)
46{
47	return ceilf(fh.ascent * 0.7);
48}
49
50
51static float
52text_offset(const font_height& fh)
53{
54	return ceilf(fh.ascent * 0.8);
55}
56
57
58class PlaylistListView::Item : public SimpleItem,
59	public PlaylistItem::Listener {
60public:
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
78private:
79			PlaylistItemRef		fItem;
80
81};
82
83
84// #pragma mark -
85
86
87PlaylistListView::Item::Item(PlaylistItem* item)
88	:
89	SimpleItem(item->Name().String()),
90	fItem(item)
91{
92	fItem->AddListener(this);
93}
94
95
96PlaylistListView::Item::~Item()
97{
98	fItem->RemoveListener(this);
99}
100
101
102void
103PlaylistListView::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
181void
182PlaylistListView::Item::ItemChanged(const PlaylistItem* item)
183{
184	// TODO: Invalidate
185}
186
187
188#if __GNUC__ == 2
189
190void
191PlaylistListView::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
202PlaylistListView::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
228PlaylistListView::~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
239void
240PlaylistListView::AttachedToWindow()
241{
242	_FullSync();
243	SimpleListView::AttachedToWindow();
244
245	GetFontHeight(&fFontHeight);
246	MakeFocus(true);
247}
248
249
250void
251PlaylistListView::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
311void
312PlaylistListView::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
357void
358PlaylistListView::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
370void
371PlaylistListView::MoveItems(const BList& indices, int32 toIndex)
372{
373	fCommandStack->Perform(new (nothrow) MovePLItemsCommand(fPlaylist,
374		(int32*)indices.Items(), indices.CountItems(), toIndex));
375}
376
377
378void
379PlaylistListView::CopyItems(const BList& indices, int32 toIndex)
380{
381	fCommandStack->Perform(new (nothrow) CopyPLItemsCommand(fPlaylist,
382		(int32*)indices.Items(), indices.CountItems(), toIndex));
383}
384
385
386void
387PlaylistListView::RemoveItemList(const BList& indices)
388{
389	RemoveItemList(indices, false);
390}
391
392
393void
394PlaylistListView::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
403void
404PlaylistListView::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
413void
414PlaylistListView::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*)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*)i))
439				return;
440		}
441	}
442
443	fCommandStack->Perform(new (nothrow) RandomizePLItemsCommand(fPlaylist,
444		(int32*)indices.Items(), indices.CountItems()));
445}
446
447
448void
449PlaylistListView::RemoveSelectionToTrash()
450{
451	BList indices;
452	GetSelectedItems(indices);
453	RemoveItemList(indices, true);
454}
455
456
457void
458PlaylistListView::RemoveToTrash(int32 index)
459{
460	BList indices;
461	indices.AddItem((void*)index);
462	RemoveItemList(indices, true);
463}
464
465
466void
467PlaylistListView::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
477void
478PlaylistListView::_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
512void
513PlaylistListView::_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
524void
525PlaylistListView::_RemoveItem(int32 index)
526{
527	delete RemoveItem(index);
528}
529
530
531void
532PlaylistListView::_SetCurrentPlaylistIndex(int32 index)
533{
534	if (fCurrentPlaylistIndex == index)
535		return;
536
537	InvalidateItem(fCurrentPlaylistIndex);
538	fCurrentPlaylistIndex = index;
539	InvalidateItem(fCurrentPlaylistIndex);
540}
541
542
543void
544PlaylistListView::_SetPlaybackState(uint32 state)
545{
546	if (fPlaybackState == state)
547		return;
548
549	fPlaybackState = state;
550	InvalidateItem(fCurrentPlaylistIndex);
551}
552
553
554