xref: /haiku/src/apps/mediaplayer/playlist/PlaylistListView.cpp (revision 746cac055adc6ac3308c7bc2d29040fb95689cc9)
1 /*
2  * Copyright 2007-2008, Haiku. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  */
8 #include "PlaylistListView.h"
9 
10 #include <new>
11 #include <stdio.h>
12 
13 #include <Autolock.h>
14 #include <Message.h>
15 #include <ScrollBar.h>
16 #include <ScrollView.h>
17 #include <Window.h>
18 
19 #include "CommandStack.h"
20 #include "Controller.h"
21 #include "ControllerObserver.h"
22 #include "CopyPLItemsCommand.h"
23 #include "ImportPLItemsCommand.h"
24 #include "ListViews.h"
25 #include "MovePLItemsCommand.h"
26 #include "PlaybackState.h"
27 #include "Playlist.h"
28 #include "PlaylistObserver.h"
29 #include "RandomizePLItemsCommand.h"
30 #include "RemovePLItemsCommand.h"
31 
32 using std::nothrow;
33 
34 
35 enum {
36 	DISPLAY_NAME	= 0,
37 	DISPLAY_PATH	= 1
38 };
39 
40 
41 static float
42 playback_mark_size(const font_height& fh)
43 {
44 	return ceilf(fh.ascent * 0.7);
45 }
46 
47 
48 static float
49 text_offset(const font_height& fh)
50 {
51 	return ceilf(fh.ascent * 0.8);
52 }
53 
54 
55 class PlaylistItem : public SimpleItem {
56  public:
57 								PlaylistItem(const entry_ref& ref);
58 		virtual					~PlaylistItem();
59 
60 				void			Draw(BView* owner, BRect frame,
61 									const font_height& fh,
62 									bool tintedLine, uint32 mode,
63 									bool active,
64 									uint32 playbackState);
65 
66  private:
67 				entry_ref		fRef;
68 
69 };
70 
71 
72 PlaylistItem::PlaylistItem(const entry_ref& ref)
73 	: SimpleItem(ref.name),
74 	  fRef(ref)
75 {
76 }
77 
78 
79 PlaylistItem::~PlaylistItem()
80 {
81 }
82 
83 
84 void
85 PlaylistItem::Draw(BView* owner, BRect frame, const font_height& fh,
86 	bool tintedLine, uint32 mode, bool active, uint32 playbackState)
87 {
88 	rgb_color color = (rgb_color){ 255, 255, 255, 255 };
89 	if (tintedLine)
90 		color = tint_color(color, 1.04);
91 	// background
92 	if (IsSelected())
93 		color = tint_color(color, B_DARKEN_2_TINT);
94 	owner->SetLowColor(color);
95 	owner->FillRect(frame, B_SOLID_LOW);
96 	// label
97 	rgb_color black = (rgb_color){ 0, 0, 0, 255 };
98 	owner->SetHighColor(black);
99 	const char* text = Text();
100 	switch (mode) {
101 		case DISPLAY_NAME:
102 			// TODO
103 			break;
104 		case DISPLAY_PATH:
105 			// TODO
106 			break;
107 		default:
108 			break;
109 	}
110 
111 	float playbackMarkSize = playback_mark_size(fh);
112 	float textOffset = text_offset(fh);
113 
114 	BString truncatedString(text);
115 	owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE,
116 		frame.Width() - playbackMarkSize - textOffset);
117 	owner->DrawString(truncatedString.String(),
118 		BPoint(frame.left + playbackMarkSize + textOffset,
119 			floorf(frame.top + frame.bottom + fh.ascent) / 2 - 1));
120 
121 	// playmark
122 	if (active) {
123 		rgb_color green = (rgb_color){ 0, 255, 0, 255 };
124 		if (playbackState != PLAYBACK_STATE_PLAYING)
125 			green = tint_color(color, B_DARKEN_1_TINT);
126 
127 		BRect r(0, 0, playbackMarkSize, playbackMarkSize);
128 		r.OffsetTo(frame.left + 4,
129 			ceilf((frame.top + frame.bottom) / 2) - 5);
130 
131 		BPoint arrow[3];
132 		arrow[0] = r.LeftTop();
133 		arrow[1] = r.LeftBottom();
134 		arrow[2].x = r.right;
135 		arrow[2].y = (r.top + r.bottom) / 2;
136 #ifdef __HAIKU__
137 		owner->SetPenSize(2);
138 		owner->StrokePolygon(arrow, 3);
139 		owner->SetPenSize(1);
140 #else
141 		rgb_color lightGreen = tint_color(green, B_LIGHTEN_2_TINT);
142 		rgb_color darkGreen = tint_color(green, B_DARKEN_2_TINT);
143  		owner->BeginLineArray(6);
144 			// black outline
145 			owner->AddLine(arrow[0], arrow[1], black);
146 			owner->AddLine(BPoint(arrow[1].x + 1.0, arrow[1].y - 1.0),
147 				arrow[2], black);
148 			owner->AddLine(arrow[0], arrow[2], black);
149 			// inset arrow
150 			arrow[0].x += 1.0;
151 			arrow[0].y += 2.0;
152 			arrow[1].x += 1.0;
153 			arrow[1].y -= 2.0;
154 			arrow[2].x -= 2.0;
155 			// highlights and shadow
156 			owner->AddLine(arrow[1], arrow[2], darkGreen);
157 			owner->AddLine(arrow[0], arrow[2], lightGreen);
158 			owner->AddLine(arrow[0], arrow[1], lightGreen);
159 		owner->EndLineArray();
160 		// fill green
161 		arrow[0].x += 1.0;
162 		arrow[0].y += 1.0;
163 		arrow[1].x += 1.0;
164 		arrow[1].y -= 1.0;
165 		arrow[2].x -= 2.0;
166 #endif // __HAIKU__
167 		owner->SetLowColor(owner->HighColor());
168 		owner->SetHighColor(green);
169 		owner->FillPolygon(arrow, 3);
170 	}
171 }
172 
173 
174 // #pragma mark -
175 
176 
177 PlaylistListView::PlaylistListView(BRect frame, Playlist* playlist,
178 		Controller* controller, CommandStack* stack)
179 	: SimpleListView(frame, "playlist listview", NULL)
180 
181 	, fPlaylist(playlist)
182 	, fPlaylistObserver(new PlaylistObserver(this))
183 
184 	, fController(controller)
185 	, fControllerObserver(new ControllerObserver(this,
186 			OBSERVE_PLAYBACK_STATE_CHANGES))
187 
188 	, fCommandStack(stack)
189 
190 	, fCurrentPlaylistIndex(-1)
191 	, fPlaybackState(PLAYBACK_STATE_STOPPED)
192 
193 	, fLastClickedItem(NULL)
194 {
195 	fPlaylist->AddListener(fPlaylistObserver);
196 	fController->AddListener(fControllerObserver);
197 
198 #ifdef __HAIKU__
199 	SetFlags(Flags() | B_SUBPIXEL_PRECISE);
200 #endif
201 }
202 
203 
204 PlaylistListView::~PlaylistListView()
205 {
206 	fPlaylist->RemoveListener(fPlaylistObserver);
207 	delete fPlaylistObserver;
208 	fController->RemoveListener(fControllerObserver);
209 	delete fControllerObserver;
210 }
211 
212 
213 void
214 PlaylistListView::AttachedToWindow()
215 {
216 	_FullSync();
217 	SimpleListView::AttachedToWindow();
218 
219 	GetFontHeight(&fFontHeight);
220 	MakeFocus(true);
221 }
222 
223 
224 void
225 PlaylistListView::MessageReceived(BMessage* message)
226 {
227 	switch (message->what) {
228 		// PlaylistObserver messages
229 		case MSG_PLAYLIST_REF_ADDED: {
230 			entry_ref ref;
231 			int32 index;
232 			if (message->FindRef("refs", &ref) == B_OK
233 				&& message->FindInt32("index", &index) == B_OK)
234 				_AddItem(ref, index);
235 			break;
236 		}
237 		case MSG_PLAYLIST_REF_REMOVED: {
238 			int32 index;
239 			if (message->FindInt32("index", &index) == B_OK)
240 				_RemoveItem(index);
241 			break;
242 		}
243 		case MSG_PLAYLIST_REFS_SORTED:
244 			_FullSync();
245 			break;
246 		case MSG_PLAYLIST_CURRENT_REF_CHANGED: {
247 			int32 index;
248 			if (message->FindInt32("index", &index) == B_OK)
249 				_SetCurrentPlaylistIndex(index);
250 			break;
251 		}
252 
253 		// ControllerObserver messages
254 		case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED: {
255 			uint32 state;
256 			if (message->FindInt32("state", (int32*)&state) == B_OK)
257 				_SetPlaybackState(state);
258 			break;
259 		}
260 
261 		case B_SIMPLE_DATA:
262 			if (message->HasRef("refs"))
263 				RefsReceived(message, fDropIndex);
264 			else if (message->HasPointer("list"))
265 				SimpleListView::MessageReceived(message);
266 			break;
267 		case B_REFS_RECEIVED:
268 			RefsReceived(message, fDropIndex);
269 			break;
270 
271 		default:
272 			SimpleListView::MessageReceived(message);
273 			break;
274 	}
275 }
276 
277 
278 void
279 PlaylistListView::MouseDown(BPoint where)
280 {
281 	if (!IsFocus())
282 		MakeFocus(true);
283 
284 	int32 clicks;
285 	if (Window()->CurrentMessage()->FindInt32("clicks", &clicks) < B_OK)
286 		clicks = 1;
287 
288 	bool handled = false;
289 
290 	float playbackMarkSize = playback_mark_size(fFontHeight);
291 	float textOffset = text_offset(fFontHeight);
292 
293 	for (int32 i = 0;
294 		PlaylistItem* item = dynamic_cast<PlaylistItem*>(ItemAt(i)); i++) {
295 		BRect r = ItemFrame(i);
296 		if (r.Contains(where)) {
297 			if (clicks == 2) {
298 				// only do something if user clicked the same item twice
299 				if (fLastClickedItem == item) {
300 					BAutolock _(fPlaylist);
301 					fPlaylist->SetCurrentRefIndex(i);
302 					handled = true;
303 				}
304 			} else {
305 				// remember last clicked item
306 				fLastClickedItem = item;
307 				if (i == fCurrentPlaylistIndex) {
308 					r.right = r.left + playbackMarkSize + textOffset;
309 					if (r.Contains (where)) {
310 						fController->TogglePlaying();
311 						handled = true;
312 					}
313 				}
314 			}
315 			break;
316 		}
317 	}
318 
319 	if (!handled)
320 		SimpleListView::MouseDown(where);
321 }
322 
323 
324 void
325 PlaylistListView::KeyDown(const char* bytes, int32 numBytes)
326 {
327 	if (numBytes < 1)
328 		return;
329 
330 	if ((bytes[0] == B_BACKSPACE) || (bytes[0] == B_DELETE))
331 		RemoveSelected();
332 
333 	DragSortableListView::KeyDown(bytes, numBytes);
334 }
335 
336 
337 void
338 PlaylistListView::MoveItems(BList& indices, int32 toIndex)
339 {
340 	fCommandStack->Perform(new (nothrow) MovePLItemsCommand(fPlaylist,
341 		(int32*)indices.Items(), indices.CountItems(), toIndex));
342 }
343 
344 
345 void
346 PlaylistListView::CopyItems(BList& indices, int32 toIndex)
347 {
348 	fCommandStack->Perform(new (nothrow) CopyPLItemsCommand(fPlaylist,
349 		(int32*)indices.Items(), indices.CountItems(), toIndex));
350 }
351 
352 
353 void
354 PlaylistListView::RemoveItemList(BList& indices)
355 {
356 	fCommandStack->Perform(new (nothrow) RemovePLItemsCommand(fPlaylist,
357 		(int32*)indices.Items(), indices.CountItems()));
358 }
359 
360 
361 void
362 PlaylistListView::DrawListItem(BView* owner, int32 index, BRect frame) const
363 {
364 	if (PlaylistItem* item = dynamic_cast<PlaylistItem*>(ItemAt(index))) {
365 		item->Draw(owner, frame, fFontHeight, index % 2,
366 			DISPLAY_NAME, index == fCurrentPlaylistIndex, fPlaybackState);
367 	}
368 }
369 
370 
371 void
372 PlaylistListView::RefsReceived(BMessage* message, int32 appendIndex)
373 {
374 	fCommandStack->Perform(new (nothrow) ImportPLItemsCommand(fPlaylist,
375 		message, appendIndex));
376 }
377 
378 
379 void
380 PlaylistListView::Randomize()
381 {
382 	int32 count = CountItems();
383 	if (count == 0)
384 		return;
385 
386 	BList indices;
387 
388 	// add current selection
389 	count = 0;
390 	while (true) {
391 		int32 index = CurrentSelection(count);
392 		if (index < 0)
393 			break;
394 		if (!indices.AddItem((void*)index))
395 			return;
396 		count++;
397 	}
398 
399 	// was anything selected?
400 	if (count == 0) {
401 		// no selection, simply add all items
402 		count = CountItems();
403 		for (int32 i = 0; i < count; i++) {
404 			if (!indices.AddItem((void*)i))
405 				return;
406 		}
407 	}
408 
409 	fCommandStack->Perform(new (nothrow) RandomizePLItemsCommand(fPlaylist,
410 		(int32*)indices.Items(), indices.CountItems()));
411 }
412 
413 
414 // #pragma mark -
415 
416 
417 void
418 PlaylistListView::_FullSync()
419 {
420 	if (!fPlaylist->Lock())
421 		return;
422 
423 	// detaching the scrollbar temporarily will
424 	// make this much quicker
425 	BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
426 	if (scrollBar) {
427 		if (Window())
428 			Window()->UpdateIfNeeded();
429 		scrollBar->SetTarget((BView*)NULL);
430 	}
431 
432 	MakeEmpty();
433 
434 	int32 count = fPlaylist->CountItems();
435 	for (int32 i = 0; i < count; i++) {
436 		entry_ref ref;
437 		if (fPlaylist->GetRefAt(i, &ref) == B_OK)
438 			_AddItem(ref, i);
439 	}
440 
441 	_SetCurrentPlaylistIndex(fPlaylist->CurrentRefIndex());
442 	_SetPlaybackState(fController->PlaybackState());
443 
444 	// reattach scrollbar and sync it by calling FrameResized()
445 	if (scrollBar) {
446 		scrollBar->SetTarget(this);
447 		FrameResized(Bounds().Width(), Bounds().Height());
448 	}
449 
450 	fPlaylist->Unlock();
451 }
452 
453 
454 void
455 PlaylistListView::_AddItem(const entry_ref& ref, int32 index)
456 {
457 	PlaylistItem* item = new (nothrow) PlaylistItem(ref);
458 	if (item)
459 		AddItem(item, index);
460 }
461 
462 
463 void
464 PlaylistListView::_RemoveItem(int32 index)
465 {
466 	delete RemoveItem(index);
467 }
468 
469 
470 void
471 PlaylistListView::_SetCurrentPlaylistIndex(int32 index)
472 {
473 	if (fCurrentPlaylistIndex == index)
474 		return;
475 
476 	InvalidateItem(fCurrentPlaylistIndex);
477 	fCurrentPlaylistIndex = index;
478 	InvalidateItem(fCurrentPlaylistIndex);
479 }
480 
481 
482 void
483 PlaylistListView::_SetPlaybackState(uint32 state)
484 {
485 	if (fPlaybackState == state)
486 		return;
487 
488 	fPlaybackState = state;
489 	InvalidateItem(fCurrentPlaylistIndex);
490 }
491 
492 
493