xref: /haiku/src/apps/mediaplayer/playlist/PlaylistWindow.cpp (revision e8cd7007416a323259791ac09c013dcce2956976)
1 /*
2  * Copyright 2007-2010, Haiku. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus 	<superstippi@gmx.de>
7  *		Fredrik Modéen	<fredrik@modeen.se>
8  */
9 
10 
11 #include "PlaylistWindow.h"
12 
13 #include <stdio.h>
14 
15 #include <Alert.h>
16 #include <Application.h>
17 #include <Autolock.h>
18 #include <Box.h>
19 #include <Button.h>
20 #include <Catalog.h>
21 #include <Entry.h>
22 #include <File.h>
23 #include <FilePanel.h>
24 #include <Locale.h>
25 #include <Menu.h>
26 #include <MenuBar.h>
27 #include <MenuItem.h>
28 #include <NodeInfo.h>
29 #include <Path.h>
30 #include <Roster.h>
31 #include <ScrollBar.h>
32 #include <ScrollView.h>
33 #include <String.h>
34 
35 #include "CommandStack.h"
36 #include "MainApp.h"
37 #include "PlaylistListView.h"
38 #include "RWLocker.h"
39 
40 
41 #undef B_TRANSLATE_CONTEXT
42 #define B_TRANSLATE_CONTEXT "MediaPlayer-PlaylistWindow"
43 
44 
45 // TODO:
46 // Maintaining a playlist file on disk is a bit tricky. The playlist ref should
47 // be discarded when the user
48 // * loads a new playlist via Open,
49 // * loads a new playlist via dropping it on the MainWindow,
50 // * loads a new playlist via dropping it into the ListView while replacing
51 //   the contents,
52 // * replacing the contents by other stuff.
53 
54 
55 enum {
56 	// file
57 	M_PLAYLIST_OPEN							= 'open',
58 	M_PLAYLIST_SAVE							= 'save',
59 	M_PLAYLIST_SAVE_AS						= 'svas',
60 	M_PLAYLIST_SAVE_RESULT					= 'psrs',
61 
62 	// edit
63 	M_PLAYLIST_EMPTY						= 'emty',
64 	M_PLAYLIST_RANDOMIZE					= 'rand',
65 
66 	M_PLAYLIST_REMOVE						= 'rmov'
67 };
68 
69 
70 static void
71 display_save_alert(const char* message)
72 {
73 	BAlert* alert = new BAlert(B_TRANSLATE("Save error"), message,
74 		B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
75 	alert->Go(NULL);
76 }
77 
78 
79 static void
80 display_save_alert(status_t error)
81 {
82 	BString errorMessage(B_TRANSLATE("Saving the playlist failed.\n\nError: "));
83 	errorMessage << strerror(error);
84 	display_save_alert(errorMessage.String());
85 }
86 
87 
88 // #pragma mark -
89 
90 
91 PlaylistWindow::PlaylistWindow(BRect frame, Playlist* playlist,
92 		Controller* controller)
93 	:
94 	BWindow(frame, B_TRANSLATE("Playlist"), B_DOCUMENT_WINDOW_LOOK,
95 		B_NORMAL_WINDOW_FEEL, B_ASYNCHRONOUS_CONTROLS),
96 	fPlaylist(playlist),
97 	fLocker(new RWLocker("command stack lock")),
98 	fCommandStack(new CommandStack(fLocker)),
99 	fCommandStackListener(this)
100 {
101 	frame = Bounds();
102 
103 	_CreateMenu(frame);
104 		// will adjust frame to account for menubar
105 
106 	frame.right -= B_V_SCROLL_BAR_WIDTH;
107 	fListView = new PlaylistListView(frame, playlist, controller,
108 		fCommandStack);
109 
110 	BScrollView* scrollView = new BScrollView("playlist scrollview", fListView,
111 		B_FOLLOW_ALL_SIDES, 0, false, true, B_NO_BORDER);
112 
113 	fTopView = 	scrollView;
114 	AddChild(fTopView);
115 
116 	// small visual tweak
117 	if (BScrollBar* scrollBar = scrollView->ScrollBar(B_VERTICAL)) {
118 		// make it so the frame of the menubar is also the frame of
119 		// the scroll bar (appears to be)
120 		scrollBar->MoveBy(0, -1);
121 		scrollBar->ResizeBy(0, -(B_H_SCROLL_BAR_HEIGHT - 2));
122 	}
123 
124 	fCommandStack->AddListener(&fCommandStackListener);
125 	_ObjectChanged(fCommandStack);
126 }
127 
128 
129 PlaylistWindow::~PlaylistWindow()
130 {
131 	// give listeners a chance to detach themselves
132 	fTopView->RemoveSelf();
133 	delete fTopView;
134 
135 	fCommandStack->RemoveListener(&fCommandStackListener);
136 	delete fCommandStack;
137 	delete fLocker;
138 }
139 
140 
141 bool
142 PlaylistWindow::QuitRequested()
143 {
144 	Hide();
145 	return false;
146 }
147 
148 
149 void
150 PlaylistWindow::MessageReceived(BMessage* message)
151 {
152 	switch (message->what) {
153 		case B_MODIFIERS_CHANGED:
154 			if (LastMouseMovedView())
155 				PostMessage(message, LastMouseMovedView());
156 			break;
157 
158 		case B_UNDO:
159 			fCommandStack->Undo();
160 			break;
161 		case B_REDO:
162 			fCommandStack->Redo();
163 			break;
164 
165 		case MSG_OBJECT_CHANGED: {
166 			Notifier* notifier;
167 			if (message->FindPointer("object", (void**)&notifier) == B_OK)
168 				_ObjectChanged(notifier);
169 			break;
170 		}
171 
172 		case B_REFS_RECEIVED:
173 			// Used for when we open a playlist from playlist window
174 			if (!message->HasInt32("append_index")) {
175 				message->AddInt32("append_index",
176 					APPEND_INDEX_REPLACE_PLAYLIST);
177 			}
178 			// supposed to fall through
179 		case B_SIMPLE_DATA:
180 		{
181 			// only accept this message when it comes from the
182 			// player window, _not_ when it is dropped in this window
183 			// outside of the playlist!
184 			int32 appendIndex;
185 			if (message->FindInt32("append_index", &appendIndex) == B_OK)
186 				fListView->RefsReceived(message, appendIndex);
187 			break;
188 		}
189 
190 		case M_PLAYLIST_OPEN:
191 		{
192 			BMessenger target(this);
193 			BMessage result(B_REFS_RECEIVED);
194 			BMessage appMessage(M_SHOW_OPEN_PANEL);
195 			appMessage.AddMessenger("target", target);
196 			appMessage.AddMessage("message", &result);
197 			appMessage.AddString("title", B_TRANSLATE("Open Playlist"));
198 			appMessage.AddString("label", B_TRANSLATE("Open"));
199 			be_app->PostMessage(&appMessage);
200 			break;
201 		}
202 
203 		case M_PLAYLIST_SAVE:
204 			if (fSavedPlaylistRef != entry_ref()) {
205 				_SavePlaylist(fSavedPlaylistRef);
206 				break;
207 			}
208 			// supposed to fall through
209 		case M_PLAYLIST_SAVE_AS:
210 		{
211 			BMessenger target(this);
212 			BMessage result(M_PLAYLIST_SAVE_RESULT);
213 			BMessage appMessage(M_SHOW_SAVE_PANEL);
214 			appMessage.AddMessenger("target", target);
215 			appMessage.AddMessage("message", &result);
216 			appMessage.AddString("title", B_TRANSLATE("Save Playlist"));
217 			appMessage.AddString("label", B_TRANSLATE("Save"));
218 			be_app->PostMessage(&appMessage);
219 			break;
220 		}
221 
222 		case M_PLAYLIST_SAVE_RESULT:
223 			_SavePlaylist(message);
224 			break;
225 
226 		case M_PLAYLIST_EMPTY:
227 			fListView->RemoveAll();
228 			break;
229 		case M_PLAYLIST_RANDOMIZE:
230 			fListView->Randomize();
231 			break;
232 		case M_PLAYLIST_REMOVE:
233 			fListView->RemoveSelected();
234 			break;
235 		case M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH:
236 		{
237 			int32 index;
238 			if (message->FindInt32("playlist index", &index) == B_OK)
239 				fListView->RemoveToTrash(index);
240 			else
241 				fListView->RemoveSelectionToTrash();
242 			break;
243 		}
244 		default:
245 			BWindow::MessageReceived(message);
246 			break;
247 	}
248 }
249 
250 
251 // #pragma mark -
252 
253 
254 void
255 PlaylistWindow::_CreateMenu(BRect& frame)
256 {
257 	frame.bottom = 15;
258 	BMenuBar* menuBar = new BMenuBar(frame, "main menu");
259 	BMenu* fileMenu = new BMenu(B_TRANSLATE("Playlist"));
260 	menuBar->AddItem(fileMenu);
261 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Open"B_UTF8_ELLIPSIS),
262 		new BMessage(M_PLAYLIST_OPEN), 'O'));
263 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Save as"B_UTF8_ELLIPSIS),
264 		new BMessage(M_PLAYLIST_SAVE_AS), 'S', B_SHIFT_KEY));
265 //	fileMenu->AddItem(new BMenuItem("Save",
266 //		new BMessage(M_PLAYLIST_SAVE), 'S'));
267 
268 	fileMenu->AddSeparatorItem();
269 
270 	fileMenu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
271 		new BMessage(B_QUIT_REQUESTED), 'W'));
272 
273 	BMenu* editMenu = new BMenu(B_TRANSLATE("Edit"));
274 	fUndoMI = new BMenuItem(B_TRANSLATE("Undo"), new BMessage(B_UNDO), 'Z');
275 	editMenu->AddItem(fUndoMI);
276 	fRedoMI = new BMenuItem(B_TRANSLATE("Redo"), new BMessage(B_REDO), 'Z',
277 		B_SHIFT_KEY);
278 	editMenu->AddItem(fRedoMI);
279 	editMenu->AddSeparatorItem();
280 	editMenu->AddItem(new BMenuItem(B_TRANSLATE("Randomize"),
281 		new BMessage(M_PLAYLIST_RANDOMIZE), 'R'));
282 	editMenu->AddSeparatorItem();
283 	editMenu->AddItem(new BMenuItem(B_TRANSLATE("Remove (Del)"),
284 		new BMessage(M_PLAYLIST_REMOVE)/*, B_DELETE, 0*/));
285 	editMenu->AddItem(new BMenuItem(B_TRANSLATE("Remove and put into Trash"),
286 		new BMessage(M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH), 'T'));
287 	editMenu->AddItem(new BMenuItem(B_TRANSLATE("Remove all"),
288 		new BMessage(M_PLAYLIST_EMPTY), 'N'));
289 
290 	menuBar->AddItem(editMenu);
291 
292 	AddChild(menuBar);
293 	fileMenu->SetTargetForItems(this);
294 	editMenu->SetTargetForItems(this);
295 
296 	menuBar->ResizeToPreferred();
297 	frame = Bounds();
298 	frame.top = menuBar->Frame().bottom + 1;
299 }
300 
301 
302 void
303 PlaylistWindow::_ObjectChanged(const Notifier* object)
304 {
305 	if (object == fCommandStack) {
306 		// relable Undo item and update enabled status
307 		BString label(B_TRANSLATE("Undo"));
308 		fUndoMI->SetEnabled(fCommandStack->GetUndoName(label));
309 		if (fUndoMI->IsEnabled())
310 			fUndoMI->SetLabel(label.String());
311 		else
312 			fUndoMI->SetLabel(B_TRANSLATE("<nothing to undo>"));
313 
314 		// relable Redo item and update enabled status
315 		label.SetTo(B_TRANSLATE("Redo"));
316 		fRedoMI->SetEnabled(fCommandStack->GetRedoName(label));
317 		if (fRedoMI->IsEnabled())
318 			fRedoMI->SetLabel(label.String());
319 		else
320 			fRedoMI->SetLabel(B_TRANSLATE("<nothing to redo>"));
321 	}
322 }
323 
324 
325 void
326 PlaylistWindow::_SavePlaylist(const BMessage* message)
327 {
328 	entry_ref ref;
329 	const char* name;
330 	if (message->FindRef("directory", &ref) != B_OK
331 		|| message->FindString("name", &name) != B_OK) {
332 		display_save_alert(B_TRANSLATE("Internal error (malformed message). "
333 			"Saving the playlist failed."));
334 		return;
335 	}
336 
337 	BString tempName(name);
338 	tempName << system_time();
339 
340 	BPath origPath(&ref);
341 	BPath tempPath(&ref);
342 	if (origPath.InitCheck() != B_OK || tempPath.InitCheck() != B_OK
343 		|| origPath.Append(name) != B_OK
344 		|| tempPath.Append(tempName.String()) != B_OK) {
345 		display_save_alert(B_TRANSLATE("Internal error (out of memory). "
346 			"Saving the playlist failed."));
347 		return;
348 	}
349 
350 	BEntry origEntry(origPath.Path());
351 	BEntry tempEntry(tempPath.Path());
352 	if (origEntry.InitCheck() != B_OK || tempEntry.InitCheck() != B_OK) {
353 		display_save_alert(B_TRANSLATE("Internal error (out of memory). "
354 			"Saving the playlist failed."));
355 		return;
356 	}
357 
358 	_SavePlaylist(origEntry, tempEntry, name);
359 }
360 
361 
362 void
363 PlaylistWindow::_SavePlaylist(const entry_ref& ref)
364 {
365 	BString tempName(ref.name);
366 	tempName << system_time();
367 	entry_ref tempRef(ref);
368 	tempRef.set_name(tempName.String());
369 
370 	BEntry origEntry(&ref);
371 	BEntry tempEntry(&tempRef);
372 
373 	_SavePlaylist(origEntry, tempEntry, ref.name);
374 }
375 
376 
377 void
378 PlaylistWindow::_SavePlaylist(BEntry& origEntry, BEntry& tempEntry,
379 	const char* finalName)
380 {
381 	class TempEntryRemover {
382 	public:
383 		TempEntryRemover(BEntry* entry)
384 			: fEntry(entry)
385 		{
386 		}
387 		~TempEntryRemover()
388 		{
389 			if (fEntry)
390 				fEntry->Remove();
391 		}
392 		void Detach()
393 		{
394 			fEntry = NULL;
395 		}
396 	private:
397 		BEntry* fEntry;
398 	} remover(&tempEntry);
399 
400 	BFile file(&tempEntry, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
401 	if (file.InitCheck() != B_OK) {
402 		BString errorMessage(B_TRANSLATE(
403 			"Saving the playlist failed:\n\nError: "));
404 		errorMessage << strerror(file.InitCheck());
405 		display_save_alert(errorMessage.String());
406 		return;
407 	}
408 
409 	AutoLocker<Playlist> lock(fPlaylist);
410 	if (!lock.IsLocked()) {
411 		display_save_alert(B_TRANSLATE("Internal error (locking failed). "
412 			"Saving the playlist failed."));
413 		return;
414 	}
415 
416 	status_t ret = fPlaylist->Flatten(&file);
417 	if (ret != B_OK) {
418 		display_save_alert(ret);
419 		return;
420 	}
421 	lock.Unlock();
422 
423 	if (origEntry.Exists()) {
424 		// TODO: copy attributes
425 	}
426 
427 	// clobber original entry, if it exists
428 	tempEntry.Rename(finalName, true);
429 	remover.Detach();
430 
431 	BNodeInfo info(&file);
432 	info.SetType("application/x-vnd.haiku-playlist");
433 }
434 
435