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 case B_SIMPLE_DATA: { 145 // only accept this message when it comes from the 146 // player window, _not_ when it is dropped in this window 147 // outside of the playlist! 148 int32 appendIndex; 149 if (message->FindInt32("append_index", &appendIndex) == B_OK) { 150 fListView->RefsReceived(message, appendIndex); 151 } 152 break; 153 } 154 155 case M_PLAYLIST_OPEN: { 156 BMessenger target(this); 157 BMessage result(B_REFS_RECEIVED); 158 BMessage appMessage(M_SHOW_OPEN_PANEL); 159 appMessage.AddMessenger("target", target); 160 appMessage.AddMessage("message", &result); 161 appMessage.AddString("title", "Open Playlist"); 162 appMessage.AddString("label", "Open"); 163 be_app->PostMessage(&appMessage); 164 break; 165 } 166 case M_PLAYLIST_SAVE: { 167 if (fSavedPlaylistRef != entry_ref()) { 168 _SavePlaylist(fSavedPlaylistRef); 169 break; 170 } else { 171 // FALL THROUGH 172 } 173 } 174 case M_PLAYLIST_SAVE_AS: { 175 BMessenger target(this); 176 BMessage result(M_PLAYLIST_SAVE_RESULT); 177 BMessage appMessage(M_SHOW_SAVE_PANEL); 178 appMessage.AddMessenger("target", target); 179 appMessage.AddMessage("message", &result); 180 appMessage.AddString("title", "Save Playlist"); 181 appMessage.AddString("label", "Save"); 182 be_app->PostMessage(&appMessage); 183 break; 184 } 185 case M_PLAYLIST_SAVE_RESULT: { 186 _SavePlaylist(message); 187 break; 188 } 189 190 case M_PLAYLIST_EMPTY: 191 fListView->RemoveAll(); 192 break; 193 case M_PLAYLIST_RANDOMIZE: 194 fListView->Randomize(); 195 break; 196 case M_PLAYLIST_REMOVE: 197 fListView->RemoveSelected(); 198 break; 199 case M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH: 200 { 201 printf("M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH\n"); 202 message->PrintToStream(); 203 int32 index; 204 if (message->FindInt32("playlist index", &index) == B_OK) 205 fListView->RemoveToTrash(index); 206 else 207 fListView->RemoveSelectionToTrash(); 208 break; 209 } 210 default: 211 BWindow::MessageReceived(message); 212 break; 213 } 214 } 215 216 217 // #pragma mark - 218 219 220 void 221 PlaylistWindow::_CreateMenu(BRect& frame) 222 { 223 frame.bottom = 15; 224 BMenuBar* menuBar = new BMenuBar(frame, "main menu"); 225 BMenu* fileMenu = new BMenu("Playlist"); 226 menuBar->AddItem(fileMenu); 227 fileMenu->AddItem(new BMenuItem("Open"B_UTF8_ELLIPSIS, 228 new BMessage(M_PLAYLIST_OPEN), 'O')); 229 fileMenu->AddItem(new BMenuItem("Save As"B_UTF8_ELLIPSIS, 230 new BMessage(M_PLAYLIST_SAVE_AS), 'S', B_SHIFT_KEY)); 231 // fileMenu->AddItem(new BMenuItem("Save", 232 // new BMessage(M_PLAYLIST_SAVE), 'S')); 233 234 fileMenu->AddSeparatorItem(); 235 236 fileMenu->AddItem(new BMenuItem("Close", 237 new BMessage(B_QUIT_REQUESTED), 'W')); 238 239 BMenu* editMenu = new BMenu("Edit"); 240 fUndoMI = new BMenuItem("Undo", new BMessage(B_UNDO), 'Z'); 241 editMenu->AddItem(fUndoMI); 242 fRedoMI = new BMenuItem("Redo", new BMessage(B_REDO), 'Z', B_SHIFT_KEY); 243 editMenu->AddItem(fRedoMI); 244 editMenu->AddSeparatorItem(); 245 editMenu->AddItem(new BMenuItem("Randomize", 246 new BMessage(M_PLAYLIST_RANDOMIZE), 'R')); 247 editMenu->AddSeparatorItem(); 248 editMenu->AddItem(new BMenuItem("Remove (Del)", 249 new BMessage(M_PLAYLIST_REMOVE)/*, B_DELETE, 0*/)); 250 editMenu->AddItem(new BMenuItem("Remove and Put into Trash", 251 new BMessage(M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH), 'T')); 252 editMenu->AddItem(new BMenuItem("Remove All", 253 new BMessage(M_PLAYLIST_EMPTY), 'N')); 254 255 menuBar->AddItem(editMenu); 256 257 AddChild(menuBar); 258 fileMenu->SetTargetForItems(this); 259 editMenu->SetTargetForItems(this); 260 261 menuBar->ResizeToPreferred(); 262 frame = Bounds(); 263 frame.top = menuBar->Frame().bottom + 1; 264 } 265 266 267 void 268 PlaylistWindow::_ObjectChanged(const Notifier* object) 269 { 270 if (object == fCommandStack) { 271 // relable Undo item and update enabled status 272 BString label("Undo"); 273 fUndoMI->SetEnabled(fCommandStack->GetUndoName(label)); 274 if (fUndoMI->IsEnabled()) 275 fUndoMI->SetLabel(label.String()); 276 else 277 fUndoMI->SetLabel("<nothing to undo>"); 278 279 // relable Redo item and update enabled status 280 label.SetTo("Redo"); 281 fRedoMI->SetEnabled(fCommandStack->GetRedoName(label)); 282 if (fRedoMI->IsEnabled()) 283 fRedoMI->SetLabel(label.String()); 284 else 285 fRedoMI->SetLabel("<nothing to redo>"); 286 } 287 } 288 289 290 static void 291 display_save_alert(const char* message) 292 { 293 BAlert* alert = new BAlert("Save Error", message, "Ok", NULL, NULL, 294 B_WIDTH_AS_USUAL, B_STOP_ALERT); 295 alert->Go(NULL); 296 } 297 298 299 static void 300 display_save_alert(status_t error) 301 { 302 BString errorMessage("Saving the playlist failed.\n\nError: "); 303 errorMessage << strerror(error); 304 display_save_alert(errorMessage.String()); 305 } 306 307 308 void 309 PlaylistWindow::_SavePlaylist(const BMessage* message) 310 { 311 entry_ref ref; 312 const char* name; 313 if (message->FindRef("directory", &ref) != B_OK 314 || message->FindString("name", &name) != B_OK) { 315 display_save_alert("Internal error (malformed message). " 316 "Saving the Playlist failed."); 317 return; 318 } 319 320 BString tempName(name); 321 tempName << system_time(); 322 323 BPath origPath(&ref); 324 BPath tempPath(&ref); 325 if (origPath.InitCheck() != B_OK || tempPath.InitCheck() != B_OK 326 || origPath.Append(name) != B_OK 327 || tempPath.Append(tempName.String()) != B_OK) { 328 display_save_alert("Internal error (out of memory). " 329 "Saving the Playlist failed."); 330 return; 331 } 332 333 BEntry origEntry(origPath.Path()); 334 BEntry tempEntry(tempPath.Path()); 335 if (origEntry.InitCheck() != B_OK || tempEntry.InitCheck() != B_OK) { 336 display_save_alert("Internal error (out of memory). " 337 "Saving the Playlist failed."); 338 return; 339 } 340 341 _SavePlaylist(origEntry, tempEntry, name); 342 } 343 344 345 void 346 PlaylistWindow::_SavePlaylist(const entry_ref& ref) 347 { 348 BString tempName(ref.name); 349 tempName << system_time(); 350 entry_ref tempRef(ref); 351 tempRef.set_name(tempName.String()); 352 353 BEntry origEntry(&ref); 354 BEntry tempEntry(&tempRef); 355 356 _SavePlaylist(origEntry, tempEntry, ref.name); 357 } 358 359 360 void 361 PlaylistWindow::_SavePlaylist(BEntry& origEntry, BEntry& tempEntry, 362 const char* finalName) 363 { 364 class TempEntryRemover { 365 public: 366 TempEntryRemover(BEntry* entry) 367 : fEntry(entry) 368 { 369 } 370 ~TempEntryRemover() 371 { 372 if (fEntry) 373 fEntry->Remove(); 374 } 375 void Detach() 376 { 377 fEntry = NULL; 378 } 379 private: 380 BEntry* fEntry; 381 } remover(&tempEntry); 382 383 BFile file(&tempEntry, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY); 384 if (file.InitCheck() != B_OK) { 385 BString errorMessage("Saving the playlist failed:\n\nError: "); 386 errorMessage << strerror(file.InitCheck()); 387 display_save_alert(errorMessage.String()); 388 return; 389 } 390 391 AutoLocker<Playlist> lock(fPlaylist); 392 if (!lock.IsLocked()) { 393 display_save_alert("Internal error (locking failed). " 394 "Saving the Playlist failed."); 395 return; 396 } 397 398 status_t ret = fPlaylist->Flatten(&file); 399 if (ret != B_OK) { 400 display_save_alert(ret); 401 return; 402 } 403 lock.Unlock(); 404 405 if (origEntry.Exists()) { 406 // TODO: copy attributes 407 } 408 409 // clobber original entry, if it exists 410 tempEntry.Rename(finalName, true); 411 remover.Detach(); 412 413 BNodeInfo info(&file); 414 info.SetType("application/x-vnd.haiku-playlist"); 415 } 416 417