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