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