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 47 enum { 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 }; 60 61 #define SPACE 5 62 63 PlaylistWindow::PlaylistWindow(BRect frame, Playlist* playlist, 64 Controller* controller) 65 : BWindow(frame, "Playlist", B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 66 B_ASYNCHRONOUS_CONTROLS), 67 fPlaylist(playlist), 68 fLocker(new RWLocker("command stack lock")), 69 fCommandStack(new CommandStack(fLocker)), 70 fCommandStackListener(this) 71 { 72 frame = Bounds(); 73 74 _CreateMenu(frame); 75 // will adjust frame to account for menubar 76 77 frame.right -= B_V_SCROLL_BAR_WIDTH; 78 fListView = new PlaylistListView(frame, playlist, controller, 79 fCommandStack); 80 81 BScrollView* scrollView = new BScrollView("playlist scrollview", fListView, 82 B_FOLLOW_ALL_SIDES, 0, false, true, B_NO_BORDER); 83 84 fTopView = scrollView; 85 AddChild(fTopView); 86 87 // small visual tweak 88 if (BScrollBar* scrollBar = scrollView->ScrollBar(B_VERTICAL)) { 89 // make it so the frame of the menubar is also the frame of 90 // the scroll bar (appears to be) 91 scrollBar->MoveBy(0, -1); 92 scrollBar->ResizeBy(0, -(B_H_SCROLL_BAR_HEIGHT - 2)); 93 } 94 95 fCommandStack->AddListener(&fCommandStackListener); 96 _ObjectChanged(fCommandStack); 97 } 98 99 100 PlaylistWindow::~PlaylistWindow() 101 { 102 // give listeners a chance to detach themselves 103 fTopView->RemoveSelf(); 104 delete fTopView; 105 106 fCommandStack->RemoveListener(&fCommandStackListener); 107 delete fCommandStack; 108 delete fLocker; 109 } 110 111 112 bool 113 PlaylistWindow::QuitRequested() 114 { 115 Hide(); 116 return false; 117 } 118 119 120 void 121 PlaylistWindow::MessageReceived(BMessage* message) 122 { 123 switch (message->what) { 124 case B_MODIFIERS_CHANGED: 125 if (LastMouseMovedView()) 126 PostMessage(message, LastMouseMovedView()); 127 break; 128 129 case B_UNDO: 130 fCommandStack->Undo(); 131 break; 132 case B_REDO: 133 fCommandStack->Redo(); 134 break; 135 136 case MSG_OBJECT_CHANGED: { 137 Notifier* notifier; 138 if (message->FindPointer("object", (void**)¬ifier) == B_OK) 139 _ObjectChanged(notifier); 140 break; 141 } 142 143 case B_REFS_RECEIVED: 144 // Used for when we open a playlist from playlist window 145 message->AddInt32("append_index", -1); 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_REMOVE: 199 fListView->RemoveSelected(); 200 break; 201 case M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH: 202 { 203 printf("M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH\n"); 204 message->PrintToStream(); 205 int32 index; 206 if (message->FindInt32("playlist index", &index) == B_OK) 207 fListView->RemoveToTrash(index); 208 else 209 fListView->RemoveSelectionToTrash(); 210 break; 211 } 212 default: 213 BWindow::MessageReceived(message); 214 break; 215 } 216 } 217 218 219 // #pragma mark - 220 221 222 void 223 PlaylistWindow::_CreateMenu(BRect& frame) 224 { 225 frame.bottom = 15; 226 BMenuBar* menuBar = new BMenuBar(frame, "main menu"); 227 BMenu* fileMenu = new BMenu("Playlist"); 228 menuBar->AddItem(fileMenu); 229 fileMenu->AddItem(new BMenuItem("Open"B_UTF8_ELLIPSIS, 230 new BMessage(M_PLAYLIST_OPEN), 'O')); 231 fileMenu->AddItem(new BMenuItem("Save as"B_UTF8_ELLIPSIS, 232 new BMessage(M_PLAYLIST_SAVE_AS), 'S', B_SHIFT_KEY)); 233 // fileMenu->AddItem(new BMenuItem("Save", 234 // new BMessage(M_PLAYLIST_SAVE), 'S')); 235 236 fileMenu->AddSeparatorItem(); 237 238 fileMenu->AddItem(new BMenuItem("Close", 239 new BMessage(B_QUIT_REQUESTED), 'W')); 240 241 BMenu* editMenu = new BMenu("Edit"); 242 fUndoMI = new BMenuItem("Undo", new BMessage(B_UNDO), 'Z'); 243 editMenu->AddItem(fUndoMI); 244 fRedoMI = new BMenuItem("Redo", new BMessage(B_REDO), 'Z', B_SHIFT_KEY); 245 editMenu->AddItem(fRedoMI); 246 editMenu->AddSeparatorItem(); 247 editMenu->AddItem(new BMenuItem("Randomize", 248 new BMessage(M_PLAYLIST_RANDOMIZE), 'R')); 249 editMenu->AddSeparatorItem(); 250 editMenu->AddItem(new BMenuItem("Remove (Del)", 251 new BMessage(M_PLAYLIST_REMOVE)/*, B_DELETE, 0*/)); 252 editMenu->AddItem(new BMenuItem("Remove and put into Trash", 253 new BMessage(M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH), 'T')); 254 editMenu->AddItem(new BMenuItem("Remove all", 255 new BMessage(M_PLAYLIST_EMPTY), 'N')); 256 257 menuBar->AddItem(editMenu); 258 259 AddChild(menuBar); 260 fileMenu->SetTargetForItems(this); 261 editMenu->SetTargetForItems(this); 262 263 menuBar->ResizeToPreferred(); 264 frame = Bounds(); 265 frame.top = menuBar->Frame().bottom + 1; 266 } 267 268 269 void 270 PlaylistWindow::_ObjectChanged(const Notifier* object) 271 { 272 if (object == fCommandStack) { 273 // relable Undo item and update enabled status 274 BString label("Undo"); 275 fUndoMI->SetEnabled(fCommandStack->GetUndoName(label)); 276 if (fUndoMI->IsEnabled()) 277 fUndoMI->SetLabel(label.String()); 278 else 279 fUndoMI->SetLabel("<nothing to undo>"); 280 281 // relable Redo item and update enabled status 282 label.SetTo("Redo"); 283 fRedoMI->SetEnabled(fCommandStack->GetRedoName(label)); 284 if (fRedoMI->IsEnabled()) 285 fRedoMI->SetLabel(label.String()); 286 else 287 fRedoMI->SetLabel("<nothing to redo>"); 288 } 289 } 290 291 292 static void 293 display_save_alert(const char* message) 294 { 295 BAlert* alert = new BAlert("Save error", message, "OK", NULL, NULL, 296 B_WIDTH_AS_USUAL, B_STOP_ALERT); 297 alert->Go(NULL); 298 } 299 300 301 static void 302 display_save_alert(status_t error) 303 { 304 BString errorMessage("Saving the playlist failed.\n\nError: "); 305 errorMessage << strerror(error); 306 display_save_alert(errorMessage.String()); 307 } 308 309 310 void 311 PlaylistWindow::_SavePlaylist(const BMessage* message) 312 { 313 entry_ref ref; 314 const char* name; 315 if (message->FindRef("directory", &ref) != B_OK 316 || message->FindString("name", &name) != B_OK) { 317 display_save_alert("Internal error (malformed message). " 318 "Saving the playlist failed."); 319 return; 320 } 321 322 BString tempName(name); 323 tempName << system_time(); 324 325 BPath origPath(&ref); 326 BPath tempPath(&ref); 327 if (origPath.InitCheck() != B_OK || tempPath.InitCheck() != B_OK 328 || origPath.Append(name) != B_OK 329 || tempPath.Append(tempName.String()) != B_OK) { 330 display_save_alert("Internal error (out of memory). " 331 "Saving the playlist failed."); 332 return; 333 } 334 335 BEntry origEntry(origPath.Path()); 336 BEntry tempEntry(tempPath.Path()); 337 if (origEntry.InitCheck() != B_OK || tempEntry.InitCheck() != B_OK) { 338 display_save_alert("Internal error (out of memory). " 339 "Saving the playlist failed."); 340 return; 341 } 342 343 _SavePlaylist(origEntry, tempEntry, name); 344 } 345 346 347 void 348 PlaylistWindow::_SavePlaylist(const entry_ref& ref) 349 { 350 BString tempName(ref.name); 351 tempName << system_time(); 352 entry_ref tempRef(ref); 353 tempRef.set_name(tempName.String()); 354 355 BEntry origEntry(&ref); 356 BEntry tempEntry(&tempRef); 357 358 _SavePlaylist(origEntry, tempEntry, ref.name); 359 } 360 361 362 void 363 PlaylistWindow::_SavePlaylist(BEntry& origEntry, BEntry& tempEntry, 364 const char* finalName) 365 { 366 class TempEntryRemover { 367 public: 368 TempEntryRemover(BEntry* entry) 369 : fEntry(entry) 370 { 371 } 372 ~TempEntryRemover() 373 { 374 if (fEntry) 375 fEntry->Remove(); 376 } 377 void Detach() 378 { 379 fEntry = NULL; 380 } 381 private: 382 BEntry* fEntry; 383 } remover(&tempEntry); 384 385 BFile file(&tempEntry, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY); 386 if (file.InitCheck() != B_OK) { 387 BString errorMessage("Saving the playlist failed:\n\nError: "); 388 errorMessage << strerror(file.InitCheck()); 389 display_save_alert(errorMessage.String()); 390 return; 391 } 392 393 AutoLocker<Playlist> lock(fPlaylist); 394 if (!lock.IsLocked()) { 395 display_save_alert("Internal error (locking failed). " 396 "Saving the playlist failed."); 397 return; 398 } 399 400 status_t ret = fPlaylist->Flatten(&file); 401 if (ret != B_OK) { 402 display_save_alert(ret); 403 return; 404 } 405 lock.Unlock(); 406 407 if (origEntry.Exists()) { 408 // TODO: copy attributes 409 } 410 411 // clobber original entry, if it exists 412 tempEntry.Rename(finalName, true); 413 remover.Detach(); 414 415 BNodeInfo info(&file); 416 info.SetType("application/x-vnd.haiku-playlist"); 417 } 418 419