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