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