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