1 /* 2 * MainWin.cpp - Media Player for the Haiku Operating System 3 * 4 * Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de> 5 * Copyright (C) 2007-2010 Stephan Aßmus <superstippi@gmx.de> (GPL->MIT ok) 6 * Copyright (C) 2007-2009 Fredrik Modéen <[FirstName]@[LastName].se> (MIT ok) 7 * 8 * Released under the terms of the MIT license. 9 */ 10 11 12 #include "MainWin.h" 13 14 #include <math.h> 15 #include <stdio.h> 16 #include <string.h> 17 18 #include <Alert.h> 19 #include <Application.h> 20 #include <Autolock.h> 21 #include <Catalog.h> 22 #include <Debug.h> 23 #include <Directory.h> 24 #include <Drivers.h> 25 #include <fs_attr.h> 26 #include <LayoutBuilder.h> 27 #include <Language.h> 28 #include <Locale.h> 29 #include <MediaRoster.h> 30 #include <Menu.h> 31 #include <MenuBar.h> 32 #include <MenuItem.h> 33 #include <MessageRunner.h> 34 #include <Messenger.h> 35 #include <PopUpMenu.h> 36 #include <PropertyInfo.h> 37 #include <RecentItems.h> 38 #include <Roster.h> 39 #include <Screen.h> 40 #include <String.h> 41 #include <TypeConstants.h> 42 #include <View.h> 43 44 #include "AudioProducer.h" 45 #include "ControllerObserver.h" 46 #include "DurationToString.h" 47 #include "FilePlaylistItem.h" 48 #include "MainApp.h" 49 #include "NetworkStreamWin.h" 50 #include "PeakView.h" 51 #include "PlaylistItem.h" 52 #include "PlaylistObserver.h" 53 #include "PlaylistWindow.h" 54 #include "Settings.h" 55 56 57 #undef B_TRANSLATION_CONTEXT 58 #define B_TRANSLATION_CONTEXT "MediaPlayer-Main" 59 #define MIN_WIDTH 250 60 61 62 int MainWin::sNoVideoWidth = MIN_WIDTH; 63 64 65 // XXX TODO: why is lround not defined? 66 #define lround(a) ((int)(0.99999 + (a))) 67 68 enum { 69 M_DUMMY = 0x100, 70 M_FILE_OPEN = 0x1000, 71 M_NETWORK_STREAM_OPEN, 72 M_EJECT_DEVICE, 73 M_FILE_INFO, 74 M_FILE_PLAYLIST, 75 M_FILE_CLOSE, 76 M_FILE_QUIT, 77 M_VIEW_SIZE, 78 M_TOGGLE_FULLSCREEN, 79 M_TOGGLE_ALWAYS_ON_TOP, 80 M_TOGGLE_NO_INTERFACE, 81 M_VOLUME_UP, 82 M_VOLUME_DOWN, 83 M_SKIP_NEXT, 84 M_SKIP_PREV, 85 M_WIND, 86 87 // The common display aspect ratios 88 M_ASPECT_SAME_AS_SOURCE, 89 M_ASPECT_NO_DISTORTION, 90 M_ASPECT_4_3, 91 M_ASPECT_16_9, 92 M_ASPECT_83_50, 93 M_ASPECT_7_4, 94 M_ASPECT_37_20, 95 M_ASPECT_47_20, 96 97 M_SELECT_AUDIO_TRACK = 0x00000800, 98 M_SELECT_AUDIO_TRACK_END = 0x00000fff, 99 M_SELECT_VIDEO_TRACK = 0x00010000, 100 M_SELECT_VIDEO_TRACK_END = 0x00010fff, 101 M_SELECT_SUB_TITLE_TRACK = 0x00020000, 102 M_SELECT_SUB_TITLE_TRACK_END = 0x00020fff, 103 104 M_SET_RATING, 105 106 M_SET_PLAYLIST_POSITION, 107 108 M_FILE_DELETE, 109 110 M_SLIDE_CONTROLS, 111 M_FINISH_SLIDING_CONTROLS 112 }; 113 114 115 static property_info sPropertyInfo[] = { 116 { "Next", { B_EXECUTE_PROPERTY }, 117 { B_DIRECT_SPECIFIER, 0 }, 118 "Skips to the next track.", 0 119 }, 120 { "Prev", { B_EXECUTE_PROPERTY }, 121 { B_DIRECT_SPECIFIER, 0 }, 122 "Skips to the previous track.", 0 123 }, 124 { "Play", { B_EXECUTE_PROPERTY }, 125 { B_DIRECT_SPECIFIER, 0 }, 126 "Starts playing.", 0 127 }, 128 { "Stop", { B_EXECUTE_PROPERTY }, 129 { B_DIRECT_SPECIFIER, 0 }, 130 "Stops playing.", 0 131 }, 132 { "Pause", { B_EXECUTE_PROPERTY }, 133 { B_DIRECT_SPECIFIER, 0 }, 134 "Pauses playback.", 0 135 }, 136 { "IsPlaying", { B_GET_PROPERTY }, 137 { B_DIRECT_SPECIFIER, 0 }, 138 "Gets whether or not the player is unpaused.", 0, 139 { B_BOOL_TYPE } 140 }, 141 { "TogglePlaying", { B_EXECUTE_PROPERTY }, 142 { B_DIRECT_SPECIFIER, 0 }, 143 "Toggles pause/play.", 0 144 }, 145 { "Mute", { B_EXECUTE_PROPERTY }, 146 { B_DIRECT_SPECIFIER, 0 }, 147 "Toggles mute.", 0 148 }, 149 { "Volume", { B_GET_PROPERTY, B_SET_PROPERTY, 0 }, 150 { B_DIRECT_SPECIFIER, 0 }, 151 "Gets/sets the volume (0.0-2.0).", 0, 152 { B_FLOAT_TYPE } 153 }, 154 { "ToggleFullscreen", { B_EXECUTE_PROPERTY }, 155 { B_DIRECT_SPECIFIER, 0 }, 156 "Toggles fullscreen.", 0 157 }, 158 { "Position", { B_GET_PROPERTY, B_SET_PROPERTY, 0 }, 159 { B_DIRECT_SPECIFIER, 0 }, 160 "Gets/sets the current playing position in microseconds.", 161 0, { B_INT64_TYPE } 162 }, 163 { "Seek", { B_SET_PROPERTY }, 164 { B_DIRECT_SPECIFIER, 0 }, 165 "Seeks by the specified amount in microseconds.", 0, 166 { B_INT64_TYPE } 167 }, 168 { "PlaylistTrack", { B_COUNT_PROPERTIES, B_CREATE_PROPERTY, 0 }, 169 { B_DIRECT_SPECIFIER, 0 }, 170 "Counts items in the Playlist or appends an item by URI.", 0, 171 { B_INT32_TYPE } 172 }, 173 { "PlaylistTrack", { B_DELETE_PROPERTY, 0 }, 174 { B_INDEX_SPECIFIER, 0 }, 175 "Deletes the nth item in Playlist.", 0, 176 { B_STRING_TYPE } 177 }, 178 { "PlaylistTrack", {}, 179 { B_INDEX_SPECIFIER, 0 }, 180 "... of PlaylistTrack { index } of ...", 0 181 }, 182 { "CurrentTrack", {}, 183 {}, 184 "... of CurrentTrack of ...", 0, 185 }, 186 187 { 0 } 188 }; 189 190 191 static property_info sItemPropertyInfo[] = { 192 { "Title", { B_GET_PROPERTY, 0 }, 193 { B_DIRECT_SPECIFIER, 0 }, 194 "Gets the title of the item.", 0, 195 { B_STRING_TYPE } 196 }, 197 { "URI", { B_GET_PROPERTY, 0 }, 198 { B_DIRECT_SPECIFIER, 0 }, 199 "Gets the URI of the item.", 0, 200 { B_STRING_TYPE } 201 }, 202 { "Duration", { B_GET_PROPERTY, 0 }, 203 { B_DIRECT_SPECIFIER, 0 }, 204 "Gets the duration of the item in microseconds.", 0, 205 { B_INT64_TYPE } 206 }, 207 { "Author", { B_GET_PROPERTY, 0 }, 208 { B_DIRECT_SPECIFIER, 0 }, 209 "Gets the author of the item.", 0, 210 { B_STRING_TYPE } 211 }, 212 { "Album", { B_GET_PROPERTY, 0 }, 213 { B_DIRECT_SPECIFIER, 0 }, 214 "Gets the album of the item.", 0, 215 { B_STRING_TYPE } 216 }, 217 { "TrackNumber", { B_GET_PROPERTY, 0 }, 218 { B_DIRECT_SPECIFIER, 0 }, 219 "Gets the track number of the item.", 0, 220 { B_INT32_TYPE } 221 }, 222 { "PlaylistIndex", { B_GET_PROPERTY, 0 }, 223 { B_DIRECT_SPECIFIER, 0 }, 224 "Gets the item's position in Playlist.", 0, 225 { B_INT32_TYPE } 226 }, 227 { "Suites", { B_GET_PROPERTY, 0 }, 228 { B_DIRECT_SPECIFIER, 0 }, 229 NULL, 0, 230 { B_PROPERTY_INFO_TYPE } 231 }, 232 233 { 0 } 234 }; 235 236 237 static const char* kRatingAttrName = "Media:Rating"; 238 239 static const char* kDisabledSeekMessage = B_TRANSLATE("Drop files to play"); 240 241 static const char* kApplicationName = B_TRANSLATE_SYSTEM_NAME(NAME); 242 243 244 MainWin::MainWin(bool isFirstWindow, BMessage* message) 245 : 246 BWindow(BRect(100, 100, 400, 300), kApplicationName, B_TITLED_WINDOW, 247 B_ASYNCHRONOUS_CONTROLS), 248 fCreationTime(system_time()), 249 fInfoWin(NULL), 250 fPlaylistWindow(NULL), 251 fHasFile(false), 252 fHasVideo(false), 253 fHasAudio(false), 254 fPlaylist(new Playlist), 255 fPlaylistObserver(new PlaylistObserver(this)), 256 fController(new Controller), 257 fControllerObserver(new ControllerObserver(this, 258 OBSERVE_FILE_CHANGES | OBSERVE_TRACK_CHANGES 259 | OBSERVE_PLAYBACK_STATE_CHANGES | OBSERVE_POSITION_CHANGES 260 | OBSERVE_VOLUME_CHANGES)), 261 fIsFullscreen(false), 262 fAlwaysOnTop(false), 263 fNoInterface(false), 264 fShowsFullscreenControls(false), 265 fSourceWidth(-1), 266 fSourceHeight(-1), 267 fWidthAspect(0), 268 fHeightAspect(0), 269 fSavedFrame(), 270 fNoVideoFrame(), 271 272 fMouseDownTracking(false), 273 fLastMousePos(0, 0), 274 fLastMouseMovedTime(system_time()), 275 fMouseMoveDist(0), 276 277 fGlobalSettingsListener(this), 278 fInitialSeekPosition(0), 279 fAllowWinding(true) 280 { 281 // Handle window position and size depending on whether this is the 282 // first window or not. Use the window size from the window that was 283 // last resized by the user. 284 static int pos = 0; 285 MoveBy(pos * 25, pos * 25); 286 pos = (pos + 1) % 15; 287 288 BRect frame = Settings::Default()->AudioPlayerWindowFrame(); 289 if (frame.IsValid()) { 290 if (isFirstWindow) { 291 if (message == NULL) { 292 MoveTo(frame.LeftTop()); 293 ResizeTo(frame.Width(), frame.Height()); 294 } else { 295 // Delay moving to the initial position, since we don't 296 // know if we will be playing audio at all. 297 message->AddRect("window frame", frame); 298 } 299 } 300 if (sNoVideoWidth == MIN_WIDTH) 301 sNoVideoWidth = frame.IntegerWidth(); 302 } else if (sNoVideoWidth > MIN_WIDTH) { 303 ResizeTo(sNoVideoWidth, Bounds().Height()); 304 } 305 fNoVideoWidth = sNoVideoWidth; 306 307 BRect rect = Bounds(); 308 309 // background 310 fBackground = new BView(rect, "background", B_FOLLOW_ALL, 311 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE); 312 fBackground->SetViewColor(0, 0, 0); 313 AddChild(fBackground); 314 315 // menu 316 fMenuBar = new BMenuBar(fBackground->Bounds(), "menu"); 317 _CreateMenu(); 318 fBackground->AddChild(fMenuBar); 319 fMenuBar->SetResizingMode(B_FOLLOW_NONE); 320 fMenuBar->ResizeToPreferred(); 321 fMenuBarWidth = (int)fMenuBar->Frame().Width() + 1; 322 fMenuBarHeight = (int)fMenuBar->Frame().Height() + 1; 323 324 // video view 325 rect = BRect(0, fMenuBarHeight, fBackground->Bounds().right, 326 fMenuBarHeight + 10); 327 fVideoView = new VideoView(rect, "video display", B_FOLLOW_NONE); 328 fBackground->AddChild(fVideoView); 329 330 // controls 331 rect = BRect(0, fMenuBarHeight + 11, fBackground->Bounds().right, 332 fBackground->Bounds().bottom); 333 fControls = new ControllerView(rect, fController, fPlaylist); 334 fBackground->AddChild(fControls); 335 fControls->ResizeToPreferred(); 336 fControlsHeight = (int)fControls->Frame().Height() + 1; 337 fControlsWidth = (int)fControls->Frame().Width() + 1; 338 fControls->SetResizingMode(B_FOLLOW_BOTTOM | B_FOLLOW_LEFT_RIGHT); 339 fControls->SetDisabledString(kDisabledSeekMessage); 340 341 fPlaylist->AddListener(fPlaylistObserver); 342 fController->SetVideoView(fVideoView); 343 fController->AddListener(fControllerObserver); 344 PeakView* peakView = fControls->GetPeakView(); 345 peakView->SetPeakNotificationWhat(MSG_PEAK_NOTIFICATION); 346 fController->SetPeakListener(peakView); 347 348 _SetupWindow(); 349 350 // setup the playlist window now, we need to have it 351 // running for the undo/redo playlist editing 352 fPlaylistWindow = new PlaylistWindow(BRect(150, 150, 500, 600), fPlaylist, 353 fController); 354 fPlaylistWindow->Hide(); 355 fPlaylistWindow->Show(); 356 // this makes sure the window thread is running without 357 // showing the window just yet 358 359 Settings::Default()->AddListener(&fGlobalSettingsListener); 360 _AdoptGlobalSettings(); 361 362 AddShortcut('z', B_COMMAND_KEY, new BMessage(B_UNDO)); 363 AddShortcut('y', B_COMMAND_KEY, new BMessage(B_UNDO)); 364 AddShortcut('z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO)); 365 AddShortcut('y', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO)); 366 367 Hide(); 368 Show(); 369 370 if (message != NULL) 371 PostMessage(message); 372 373 BMediaRoster* roster = BMediaRoster::Roster(); 374 roster->StartWatching(BMessenger(this, this), B_MEDIA_SERVER_STARTED); 375 roster->StartWatching(BMessenger(this, this), B_MEDIA_SERVER_QUIT); 376 } 377 378 379 MainWin::~MainWin() 380 { 381 // printf("MainWin::~MainWin\n"); 382 383 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 384 roster->StopWatching(BMessenger(this, this), B_MEDIA_SERVER_STARTED); 385 roster->StopWatching(BMessenger(this, this), B_MEDIA_SERVER_QUIT); 386 387 Settings::Default()->RemoveListener(&fGlobalSettingsListener); 388 fPlaylist->RemoveListener(fPlaylistObserver); 389 fController->Lock(); 390 fController->RemoveListener(fControllerObserver); 391 fController->SetPeakListener(NULL); 392 fController->SetVideoTarget(NULL); 393 fController->Unlock(); 394 395 // give the views a chance to detach from any notifiers 396 // before we delete them 397 fBackground->RemoveSelf(); 398 delete fBackground; 399 400 if (fInfoWin && fInfoWin->Lock()) 401 fInfoWin->Quit(); 402 403 if (fPlaylistWindow && fPlaylistWindow->Lock()) 404 fPlaylistWindow->Quit(); 405 406 delete fPlaylist; 407 fPlaylist = NULL; 408 409 // quit the Controller looper thread 410 thread_id controllerThread = fController->Thread(); 411 fController->PostMessage(B_QUIT_REQUESTED); 412 status_t exitValue; 413 wait_for_thread(controllerThread, &exitValue); 414 } 415 416 417 // #pragma mark - 418 419 420 void 421 MainWin::FrameResized(float newWidth, float newHeight) 422 { 423 if (newWidth != Bounds().Width() || newHeight != Bounds().Height()) { 424 debugger("size wrong\n"); 425 } 426 427 bool noMenu = fNoInterface || fIsFullscreen; 428 bool noControls = fNoInterface || fIsFullscreen; 429 430 // printf("FrameResized enter: newWidth %.0f, newHeight %.0f\n", 431 // newWidth, newHeight); 432 433 if (!fHasVideo) 434 sNoVideoWidth = fNoVideoWidth = (int)newWidth; 435 436 int maxVideoWidth = int(newWidth) + 1; 437 int maxVideoHeight = int(newHeight) + 1 438 - (noMenu ? 0 : fMenuBarHeight) 439 - (noControls ? 0 : fControlsHeight); 440 441 ASSERT(maxVideoHeight >= 0); 442 443 int y = 0; 444 445 if (noMenu) { 446 if (!fMenuBar->IsHidden(fMenuBar)) 447 fMenuBar->Hide(); 448 } else { 449 fMenuBar->MoveTo(0, y); 450 fMenuBar->ResizeTo(newWidth, fMenuBarHeight - 1); 451 if (fMenuBar->IsHidden(fMenuBar)) 452 fMenuBar->Show(); 453 y += fMenuBarHeight; 454 } 455 456 if (maxVideoHeight == 0) { 457 if (!fVideoView->IsHidden(fVideoView)) 458 fVideoView->Hide(); 459 } else { 460 _ResizeVideoView(0, y, maxVideoWidth, maxVideoHeight); 461 if (fVideoView->IsHidden(fVideoView)) 462 fVideoView->Show(); 463 y += maxVideoHeight; 464 } 465 466 if (noControls) { 467 if (!fControls->IsHidden(fControls)) 468 fControls->Hide(); 469 } else { 470 fControls->MoveTo(0, y); 471 fControls->ResizeTo(newWidth, fControlsHeight - 1); 472 if (fControls->IsHidden(fControls)) 473 fControls->Show(); 474 // y += fControlsHeight; 475 } 476 477 // printf("FrameResized leave\n"); 478 } 479 480 481 void 482 MainWin::Zoom(BPoint /*position*/, float /*width*/, float /*height*/) 483 { 484 PostMessage(M_TOGGLE_FULLSCREEN); 485 } 486 487 488 void 489 MainWin::DispatchMessage(BMessage* msg, BHandler* handler) 490 { 491 if ((msg->what == B_MOUSE_DOWN) 492 && (handler == fBackground || handler == fVideoView 493 || handler == fControls)) { 494 _MouseDown(msg, dynamic_cast<BView*>(handler)); 495 } 496 497 if ((msg->what == B_MOUSE_MOVED) 498 && (handler == fBackground || handler == fVideoView 499 || handler == fControls)) { 500 _MouseMoved(msg, dynamic_cast<BView*>(handler)); 501 } 502 503 if ((msg->what == B_MOUSE_UP) 504 && (handler == fBackground || handler == fVideoView)) { 505 _MouseUp(msg); 506 } 507 508 if ((msg->what == B_KEY_DOWN) 509 && (handler == fBackground || handler == fVideoView)) { 510 // special case for PrintScreen key 511 if (msg->FindInt32("key") == B_PRINT_KEY) { 512 fVideoView->OverlayScreenshotPrepare(); 513 BWindow::DispatchMessage(msg, handler); 514 fVideoView->OverlayScreenshotCleanup(); 515 return; 516 } 517 518 // every other key gets dispatched to our _KeyDown first 519 if (_KeyDown(msg)) { 520 // it got handled, don't pass it on 521 return; 522 } 523 } 524 525 BWindow::DispatchMessage(msg, handler); 526 } 527 528 529 void 530 MainWin::MessageReceived(BMessage* msg) 531 { 532 // msg->PrintToStream(); 533 switch (msg->what) { 534 case B_EXECUTE_PROPERTY: 535 case B_GET_PROPERTY: 536 case B_SET_PROPERTY: 537 case B_COUNT_PROPERTIES: 538 case B_CREATE_PROPERTY: 539 case B_DELETE_PROPERTY: 540 case B_GET_SUPPORTED_SUITES: 541 { 542 BMessage reply(B_REPLY); 543 status_t result = B_BAD_SCRIPT_SYNTAX; 544 int32 index; 545 BMessage specifier; 546 int32 what; 547 const char* property; 548 549 if (msg->GetCurrentSpecifier(&index, &specifier, &what, &property) != B_OK) 550 return BWindow::MessageReceived(msg); 551 552 BPropertyInfo propertyInfo(sPropertyInfo); 553 int32 match = propertyInfo.FindMatch(msg, index, &specifier, what, property); 554 switch (match) { 555 case 0: 556 fControls->SkipForward(); 557 result = B_OK; 558 break; 559 560 case 1: 561 fControls->SkipBackward(); 562 result = B_OK; 563 break; 564 565 case 2: 566 fController->Play(); 567 result = B_OK; 568 break; 569 570 case 3: 571 fController->Stop(); 572 result = B_OK; 573 break; 574 575 case 4: 576 fController->Pause(); 577 result = B_OK; 578 break; 579 580 case 5: 581 result = reply.AddBool("result", fController->IsPlaying()); 582 break; 583 584 case 6: 585 fController->TogglePlaying(); 586 result = B_OK; 587 break; 588 589 case 7: 590 fController->ToggleMute(); 591 result = B_OK; 592 break; 593 594 case 8: 595 { 596 if (msg->what == B_GET_PROPERTY) { 597 result = reply.AddFloat("result", 598 fController->Volume()); 599 } else if (msg->what == B_SET_PROPERTY) { 600 float newVolume; 601 result = msg->FindFloat("data", &newVolume); 602 if (result == B_OK) 603 fController->SetVolume(newVolume); 604 } 605 break; 606 } 607 608 case 9: 609 PostMessage(M_TOGGLE_FULLSCREEN); 610 break; 611 612 case 10: 613 { 614 if (msg->what == B_GET_PROPERTY) { 615 result = reply.AddInt64("result", 616 fController->TimePosition()); 617 } else if (msg->what == B_SET_PROPERTY) { 618 int64 newTime; 619 result = msg->FindInt64("data", &newTime); 620 if (result == B_OK) 621 fController->SetTimePosition(newTime); 622 } 623 624 break; 625 } 626 627 case 11: 628 { 629 if (msg->what != B_SET_PROPERTY) 630 break; 631 632 bigtime_t seekBy; 633 result = msg->FindInt64("data", &seekBy); 634 if (result != B_OK) 635 break; 636 637 _Wind(seekBy, 0); 638 break; 639 } 640 641 case 12: 642 { 643 BAutolock _(fPlaylist); 644 if (msg->what == B_COUNT_PROPERTIES) 645 result = reply.AddInt32("result", fPlaylist->CountItems()); 646 else if (msg->what == B_CREATE_PROPERTY) { 647 result = B_OK; 648 int32 i = msg->GetInt32("index", fPlaylist->CountItems()); 649 if (i > fPlaylist->CountItems()) { 650 result = B_BAD_INDEX; 651 break; 652 } 653 654 BString urlString; 655 entry_ref fileRef; 656 for (int32 j = 0; msg->FindString("data", j, &urlString) == B_OK; j++) { 657 BUrl url(urlString); 658 if (url.IsValid() && url.Protocol() != "file") { 659 UrlPlaylistItem* item = new UrlPlaylistItem(url); 660 if (!fPlaylist->AddItem(item, i + j)) { 661 result = B_NO_INIT; 662 delete item; 663 break; 664 } 665 } else if (!urlString.IsEmpty()) { 666 BString path = url.Path().String(); 667 if (path.IsEmpty()) 668 path = urlString; 669 670 result = BEntry(path.String()).GetRef(&fileRef); 671 if (result == B_OK && msg->AddRef("refs", &fileRef) != B_OK) 672 result = B_NO_INIT; 673 if (result != B_OK) 674 break; 675 } 676 } 677 678 if (result != B_OK) 679 break; 680 681 for (int32 j = 0; msg->FindRef("refs", j, &fileRef) == B_OK; j++) 682 if (!BEntry(&fileRef).Exists()) { 683 result = B_ENTRY_NOT_FOUND; 684 break; 685 } else { 686 FilePlaylistItem* item = new FilePlaylistItem(fileRef); 687 if (!fPlaylist->AddItem(item, i + j)) { 688 result = B_NO_INIT; 689 delete item; 690 break; 691 } 692 } 693 } 694 break; 695 } 696 697 case 13: 698 { 699 int32 i = 0; 700 int32 count = fPlaylist->CountItems(); 701 if (specifier.FindInt32("index", &i) != B_OK || i >= count) { 702 result = B_BAD_INDEX; 703 break; 704 } 705 706 BAutolock _(fPlaylist); 707 if (msg->what == B_DELETE_PROPERTY) 708 result = fPlaylist->RemoveItem(i) == NULL ? B_NO_INIT : B_OK; 709 break; 710 } 711 712 // PlaylistItem and CurrentItem 713 case 14: 714 case 15: 715 { 716 BPropertyInfo itemPropertyInfo(sItemPropertyInfo); 717 if (msg->what == B_GET_SUPPORTED_SUITES) { 718 result = reply.AddFlat("messages", &itemPropertyInfo); 719 break; 720 } 721 722 BAutolock _(fPlaylist); 723 int32 i = fPlaylist->CurrentItemIndex(); 724 if (match == 14 && (specifier.FindInt32("index", &i) != B_OK 725 || i >= fPlaylist->CountItems() || i < 0)) { 726 result = B_BAD_INDEX; 727 break; 728 } 729 730 msg->SetCurrentSpecifier(0); 731 if (msg->GetCurrentSpecifier(&index, &specifier, &what, &property) != B_OK) 732 break; 733 734 const PlaylistItem* item = NULL; 735 if (match == 14) 736 item = fPlaylist->ItemAt(i); 737 else 738 item = fController->Item(); 739 if (item == NULL) { 740 result = B_NO_INIT; 741 break; 742 } 743 744 switch (itemPropertyInfo.FindMatch(msg, index, &specifier, what, property)) { 745 case 0: 746 result = reply.AddString("result", item->Title()); 747 break; 748 749 case 1: 750 result = reply.AddString("result", item->LocationURI()); 751 break; 752 753 case 2: 754 // Duration requires non-const item 755 if (match == 14) { 756 PlaylistItem* nitem = fPlaylist->ItemAt(i); 757 if (nitem == NULL) 758 result = B_NO_INIT; 759 else 760 result = reply.AddInt64("result", nitem->Duration()); 761 } else 762 result = reply.AddInt64("result", fController->TimeDuration()); 763 break; 764 765 case 3: 766 result = reply.AddString("result", item->Author()); 767 break; 768 769 case 4: 770 result = reply.AddString("result", item->Album()); 771 break; 772 773 case 5: 774 result = reply.AddInt32("result", item->TrackNumber()); 775 break; 776 777 case 6: 778 result = reply.AddInt32("result", i); 779 break; 780 781 case 7: 782 result = reply.AddFlat("messages", &itemPropertyInfo); 783 break; 784 } 785 break; 786 } 787 788 default: 789 return BWindow::MessageReceived(msg); 790 } 791 792 if (result != B_OK) { 793 reply.what = B_MESSAGE_NOT_UNDERSTOOD; 794 reply.AddString("message", strerror(result)); 795 reply.AddInt32("error", result); 796 } 797 798 msg->SendReply(&reply); 799 break; 800 } 801 802 case B_REFS_RECEIVED: 803 case M_URL_RECEIVED: 804 _RefsReceived(msg); 805 break; 806 807 case B_SIMPLE_DATA: 808 if (msg->HasRef("refs")) 809 _RefsReceived(msg); 810 break; 811 case M_OPEN_PREVIOUS_PLAYLIST: 812 OpenPlaylist(msg); 813 break; 814 815 case B_UNDO: 816 case B_REDO: 817 fPlaylistWindow->PostMessage(msg); 818 break; 819 820 case B_MEDIA_SERVER_STARTED: 821 { 822 printf("TODO: implement B_MEDIA_SERVER_STARTED\n"); 823 // 824 // BAutolock _(fPlaylist); 825 // BMessage fakePlaylistMessage(MSG_PLAYLIST_CURRENT_ITEM_CHANGED); 826 // fakePlaylistMessage.AddInt32("index", 827 // fPlaylist->CurrentItemIndex()); 828 // PostMessage(&fakePlaylistMessage); 829 break; 830 } 831 832 case B_MEDIA_SERVER_QUIT: 833 printf("TODO: implement B_MEDIA_SERVER_QUIT\n"); 834 // if (fController->Lock()) { 835 // fController->CleanupNodes(); 836 // fController->Unlock(); 837 // } 838 break; 839 840 // PlaylistObserver messages 841 case MSG_PLAYLIST_ITEM_ADDED: 842 { 843 PlaylistItem* item; 844 int32 index; 845 if (msg->FindPointer("item", (void**)&item) == B_OK 846 && msg->FindInt32("index", &index) == B_OK) { 847 _AddPlaylistItem(item, index); 848 } 849 break; 850 } 851 case MSG_PLAYLIST_ITEM_REMOVED: 852 { 853 int32 index; 854 if (msg->FindInt32("index", &index) == B_OK) 855 _RemovePlaylistItem(index); 856 break; 857 } 858 case MSG_PLAYLIST_CURRENT_ITEM_CHANGED: 859 { 860 BAutolock _(fPlaylist); 861 862 int32 index; 863 // if false, the message was meant to only update the GUI 864 bool play; 865 if (msg->FindBool("play", &play) < B_OK || !play) 866 break; 867 if (msg->FindInt32("index", &index) < B_OK 868 || index != fPlaylist->CurrentItemIndex()) 869 break; 870 PlaylistItemRef item(fPlaylist->ItemAt(index)); 871 if (item.IsSet()) { 872 printf("open playlist item: %s\n", item->Name().String()); 873 OpenPlaylistItem(item); 874 _MarkPlaylistItem(index); 875 } 876 break; 877 } 878 case MSG_PLAYLIST_IMPORT_FAILED: 879 { 880 BAlert* alert = new BAlert(B_TRANSLATE("Nothing to Play"), 881 B_TRANSLATE("None of the files you wanted to play appear " 882 "to be media files."), B_TRANSLATE("OK")); 883 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 884 alert->Go(); 885 fControls->SetDisabledString(kDisabledSeekMessage); 886 break; 887 } 888 889 // ControllerObserver messages 890 case MSG_CONTROLLER_FILE_FINISHED: 891 { 892 BAutolock _(fPlaylist); 893 894 //The file is finished. Open at start next time. 895 fController->SaveState(true); 896 897 bool hadNext = fPlaylist->SetCurrentItemIndex( 898 fPlaylist->CurrentItemIndex() + 1); 899 if (!hadNext) { 900 // Reached end of playlist 901 // Handle "quit when done" settings 902 if ((fHasVideo && fCloseWhenDonePlayingMovie) 903 || (!fHasVideo && fCloseWhenDonePlayingSound)) 904 PostMessage(B_QUIT_REQUESTED); 905 // Handle "loop by default" settings 906 if ((fHasVideo && fLoopMovies) 907 || (!fHasVideo && fLoopSounds)) { 908 if (fPlaylist->CountItems() > 1) 909 fPlaylist->SetCurrentItemIndex(0); 910 else 911 fController->Play(); 912 } 913 } 914 break; 915 } 916 case MSG_CONTROLLER_FILE_CHANGED: 917 { 918 status_t result = B_ERROR; 919 msg->FindInt32("result", &result); 920 PlaylistItemRef itemRef; 921 PlaylistItem* item; 922 if (msg->FindPointer("item", (void**)&item) == B_OK) { 923 itemRef.SetTo(item, true); 924 // The reference was passed along with the message. 925 } else { 926 BAutolock _(fPlaylist); 927 itemRef.SetTo(fPlaylist->ItemAt( 928 fPlaylist->CurrentItemIndex())); 929 } 930 _PlaylistItemOpened(itemRef, result); 931 break; 932 } 933 case MSG_CONTROLLER_VIDEO_TRACK_CHANGED: 934 { 935 int32 index; 936 if (msg->FindInt32("index", &index) == B_OK) { 937 int32 i = 0; 938 while (BMenuItem* item = fVideoTrackMenu->ItemAt(i)) { 939 item->SetMarked(i == index); 940 i++; 941 } 942 } 943 break; 944 } 945 case MSG_CONTROLLER_AUDIO_TRACK_CHANGED: 946 { 947 int32 index; 948 if (msg->FindInt32("index", &index) == B_OK) { 949 int32 i = 0; 950 while (BMenuItem* item = fAudioTrackMenu->ItemAt(i)) { 951 item->SetMarked(i == index); 952 i++; 953 } 954 _UpdateAudioChannelCount(index); 955 } 956 break; 957 } 958 case MSG_CONTROLLER_SUB_TITLE_TRACK_CHANGED: 959 { 960 int32 index; 961 if (msg->FindInt32("index", &index) == B_OK) { 962 int32 i = 0; 963 while (BMenuItem* item = fSubTitleTrackMenu->ItemAt(i)) { 964 BMessage* message = item->Message(); 965 if (message != NULL) { 966 item->SetMarked((int32)message->what 967 - M_SELECT_SUB_TITLE_TRACK == index); 968 } 969 i++; 970 } 971 } 972 break; 973 } 974 case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED: 975 { 976 uint32 state; 977 if (msg->FindInt32("state", (int32*)&state) == B_OK) 978 fControls->SetPlaybackState(state); 979 break; 980 } 981 case MSG_CONTROLLER_POSITION_CHANGED: 982 { 983 float position; 984 if (msg->FindFloat("position", &position) == B_OK) { 985 fControls->SetPosition(position, fController->TimePosition(), 986 fController->TimeDuration()); 987 fAllowWinding = true; 988 } 989 break; 990 } 991 case MSG_CONTROLLER_SEEK_HANDLED: 992 break; 993 994 case MSG_CONTROLLER_VOLUME_CHANGED: 995 { 996 float volume; 997 if (msg->FindFloat("volume", &volume) == B_OK) 998 fControls->SetVolume(volume); 999 fController->SaveState(); 1000 break; 1001 } 1002 case MSG_CONTROLLER_MUTED_CHANGED: 1003 { 1004 bool muted; 1005 if (msg->FindBool("muted", &muted) == B_OK) 1006 fControls->SetMuted(muted); 1007 break; 1008 } 1009 1010 // menu item messages 1011 case M_FILE_OPEN: 1012 { 1013 BMessenger target(this); 1014 BMessage result(B_REFS_RECEIVED); 1015 BMessage appMessage(M_SHOW_OPEN_PANEL); 1016 appMessage.AddMessenger("target", target); 1017 appMessage.AddMessage("message", &result); 1018 appMessage.AddString("title", B_TRANSLATE("Open clips")); 1019 appMessage.AddString("label", B_TRANSLATE("Open")); 1020 be_app->PostMessage(&appMessage); 1021 break; 1022 } 1023 1024 case M_NETWORK_STREAM_OPEN: 1025 { 1026 BMessenger target(this); 1027 NetworkStreamWin* win = new NetworkStreamWin(target); 1028 win->Show(); 1029 break; 1030 } 1031 1032 case M_EJECT_DEVICE: 1033 Eject(); 1034 break; 1035 1036 case M_FILE_INFO: 1037 ShowFileInfo(); 1038 break; 1039 case M_FILE_PLAYLIST: 1040 ShowPlaylistWindow(); 1041 break; 1042 case M_FILE_CLOSE: 1043 PostMessage(B_QUIT_REQUESTED); 1044 break; 1045 case M_FILE_QUIT: 1046 be_app->PostMessage(B_QUIT_REQUESTED); 1047 break; 1048 1049 case M_TOGGLE_FULLSCREEN: 1050 _ToggleFullscreen(); 1051 break; 1052 1053 case M_TOGGLE_ALWAYS_ON_TOP: 1054 _ToggleAlwaysOnTop(); 1055 break; 1056 1057 case M_TOGGLE_NO_INTERFACE: 1058 _ToggleNoInterface(); 1059 break; 1060 1061 case M_VIEW_SIZE: 1062 { 1063 int32 size; 1064 if (msg->FindInt32("size", &size) == B_OK) { 1065 if (!fHasVideo) 1066 break; 1067 if (fIsFullscreen) 1068 _ToggleFullscreen(); 1069 _ResizeWindow(size); 1070 } 1071 break; 1072 } 1073 1074 /* 1075 case B_ACQUIRE_OVERLAY_LOCK: 1076 printf("B_ACQUIRE_OVERLAY_LOCK\n"); 1077 fVideoView->OverlayLockAcquire(); 1078 break; 1079 1080 case B_RELEASE_OVERLAY_LOCK: 1081 printf("B_RELEASE_OVERLAY_LOCK\n"); 1082 fVideoView->OverlayLockRelease(); 1083 break; 1084 */ 1085 case B_MOUSE_WHEEL_CHANGED: 1086 { 1087 float dx = msg->FindFloat("be:wheel_delta_x"); 1088 float dy = msg->FindFloat("be:wheel_delta_y"); 1089 bool inv = modifiers() & B_COMMAND_KEY; 1090 if (dx > 0.1) 1091 PostMessage(inv ? M_VOLUME_DOWN : M_SKIP_PREV); 1092 if (dx < -0.1) 1093 PostMessage(inv ? M_VOLUME_UP : M_SKIP_NEXT); 1094 if (dy > 0.1) 1095 PostMessage(inv ? M_SKIP_PREV : M_VOLUME_DOWN); 1096 if (dy < -0.1) 1097 PostMessage(inv ? M_SKIP_NEXT : M_VOLUME_UP); 1098 break; 1099 } 1100 1101 case M_SKIP_NEXT: 1102 fControls->SkipForward(); 1103 break; 1104 1105 case M_SKIP_PREV: 1106 fControls->SkipBackward(); 1107 break; 1108 1109 case M_WIND: 1110 { 1111 bigtime_t howMuch; 1112 int64 frames; 1113 if (msg->FindInt64("how much", &howMuch) != B_OK 1114 || msg->FindInt64("frames", &frames) != B_OK) { 1115 break; 1116 } 1117 1118 _Wind(howMuch, frames); 1119 break; 1120 } 1121 1122 case M_VOLUME_UP: 1123 fController->VolumeUp(); 1124 break; 1125 1126 case M_VOLUME_DOWN: 1127 fController->VolumeDown(); 1128 break; 1129 1130 case M_ASPECT_SAME_AS_SOURCE: 1131 if (fHasVideo) { 1132 int width; 1133 int height; 1134 int widthAspect; 1135 int heightAspect; 1136 fController->GetSize(&width, &height, 1137 &widthAspect, &heightAspect); 1138 VideoFormatChange(width, height, widthAspect, heightAspect); 1139 } 1140 break; 1141 1142 case M_ASPECT_NO_DISTORTION: 1143 if (fHasVideo) { 1144 int width; 1145 int height; 1146 fController->GetSize(&width, &height); 1147 VideoFormatChange(width, height, width, height); 1148 } 1149 break; 1150 1151 case M_ASPECT_4_3: 1152 VideoAspectChange(4, 3); 1153 break; 1154 1155 case M_ASPECT_16_9: // 1.77 : 1 1156 VideoAspectChange(16, 9); 1157 break; 1158 1159 case M_ASPECT_83_50: // 1.66 : 1 1160 VideoAspectChange(83, 50); 1161 break; 1162 1163 case M_ASPECT_7_4: // 1.75 : 1 1164 VideoAspectChange(7, 4); 1165 break; 1166 1167 case M_ASPECT_37_20: // 1.85 : 1 1168 VideoAspectChange(37, 20); 1169 break; 1170 1171 case M_ASPECT_47_20: // 2.35 : 1 1172 VideoAspectChange(47, 20); 1173 break; 1174 1175 case M_SET_PLAYLIST_POSITION: 1176 { 1177 BAutolock _(fPlaylist); 1178 1179 int32 index; 1180 if (msg->FindInt32("index", &index) == B_OK) 1181 fPlaylist->SetCurrentItemIndex(index); 1182 break; 1183 } 1184 1185 case MSG_OBJECT_CHANGED: 1186 // received from fGlobalSettingsListener 1187 // TODO: find out which object, if we ever watch more than 1188 // the global settings instance... 1189 _AdoptGlobalSettings(); 1190 break; 1191 1192 case M_SLIDE_CONTROLS: 1193 { 1194 float offset; 1195 if (msg->FindFloat("offset", &offset) == B_OK) { 1196 fControls->MoveBy(0, offset); 1197 fVideoView->SetSubTitleMaxBottom(fControls->Frame().top - 1); 1198 UpdateIfNeeded(); 1199 snooze(15000); 1200 } 1201 break; 1202 } 1203 case M_FINISH_SLIDING_CONTROLS: 1204 { 1205 float offset; 1206 bool show; 1207 if (msg->FindFloat("offset", &offset) == B_OK 1208 && msg->FindBool("show", &show) == B_OK) { 1209 if (show) { 1210 fControls->MoveTo(fControls->Frame().left, offset); 1211 fVideoView->SetSubTitleMaxBottom(offset - 1); 1212 } else { 1213 fVideoView->SetSubTitleMaxBottom( 1214 fVideoView->Bounds().bottom); 1215 fControls->RemoveSelf(); 1216 fControls->MoveTo(fVideoView->Frame().left, 1217 fVideoView->Frame().bottom + 1); 1218 fBackground->AddChild(fControls); 1219 fControls->SetSymbolScale(1.0f); 1220 while (!fControls->IsHidden()) 1221 fControls->Hide(); 1222 } 1223 } 1224 break; 1225 } 1226 case M_HIDE_FULL_SCREEN_CONTROLS: 1227 if (fIsFullscreen) { 1228 BPoint videoViewWhere; 1229 if (msg->FindPoint("where", &videoViewWhere) == B_OK) { 1230 if (msg->FindBool("force") 1231 || !fControls->Frame().Contains(videoViewWhere)) { 1232 _ShowFullscreenControls(false); 1233 // hide the mouse cursor until the user moves it 1234 be_app->ObscureCursor(); 1235 } 1236 } 1237 } 1238 break; 1239 1240 case M_SET_RATING: 1241 { 1242 int32 rating; 1243 if (msg->FindInt32("rating", &rating) == B_OK) 1244 _SetRating(rating); 1245 break; 1246 } 1247 1248 default: 1249 if (msg->what >= M_SELECT_AUDIO_TRACK 1250 && msg->what <= M_SELECT_AUDIO_TRACK_END) { 1251 fController->SelectAudioTrack(msg->what - M_SELECT_AUDIO_TRACK); 1252 break; 1253 } 1254 if (msg->what >= M_SELECT_VIDEO_TRACK 1255 && msg->what <= M_SELECT_VIDEO_TRACK_END) { 1256 fController->SelectVideoTrack(msg->what - M_SELECT_VIDEO_TRACK); 1257 break; 1258 } 1259 if ((int32)msg->what >= M_SELECT_SUB_TITLE_TRACK - 1 1260 && msg->what <= M_SELECT_SUB_TITLE_TRACK_END) { 1261 fController->SelectSubTitleTrack((int32)msg->what 1262 - M_SELECT_SUB_TITLE_TRACK); 1263 break; 1264 } 1265 // let BWindow handle the rest 1266 BWindow::MessageReceived(msg); 1267 } 1268 } 1269 1270 1271 void 1272 MainWin::WindowActivated(bool active) 1273 { 1274 fController->PlayerActivated(active); 1275 } 1276 1277 1278 bool 1279 MainWin::QuitRequested() 1280 { 1281 fController->SaveState(); 1282 BMessage message(M_PLAYER_QUIT); 1283 GetQuitMessage(&message); 1284 be_app->PostMessage(&message); 1285 return true; 1286 } 1287 1288 1289 void 1290 MainWin::MenusBeginning() 1291 { 1292 _SetupVideoAspectItems(fVideoAspectMenu); 1293 } 1294 1295 1296 // #pragma mark - 1297 1298 1299 void 1300 MainWin::OpenPlaylist(const BMessage* playlistArchive) 1301 { 1302 if (playlistArchive == NULL) 1303 return; 1304 1305 BAutolock _(this); 1306 BAutolock playlistLocker(fPlaylist); 1307 1308 if (fPlaylist->Unarchive(playlistArchive) != B_OK) 1309 return; 1310 1311 int32 currentIndex; 1312 if (playlistArchive->FindInt32("index", ¤tIndex) != B_OK) 1313 currentIndex = 0; 1314 fPlaylist->SetCurrentItemIndex(currentIndex); 1315 1316 playlistLocker.Unlock(); 1317 1318 if (currentIndex != -1) { 1319 // Restore the current play position only if we have something to play 1320 playlistArchive->FindInt64("position", (int64*)&fInitialSeekPosition); 1321 } 1322 1323 if (IsHidden()) 1324 Show(); 1325 } 1326 1327 1328 void 1329 MainWin::OpenPlaylistItem(const PlaylistItemRef& item) 1330 { 1331 status_t ret = fController->SetToAsync(item); 1332 if (ret != B_OK) { 1333 fprintf(stderr, "MainWin::OpenPlaylistItem() - Failed to send message " 1334 "to Controller.\n"); 1335 BString message = B_TRANSLATE("%app% encountered an internal error. " 1336 "The file could not be opened."); 1337 message.ReplaceFirst("%app%", kApplicationName); 1338 BAlert* alert = new BAlert(kApplicationName, message.String(), 1339 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1340 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1341 alert->Go(); 1342 _PlaylistItemOpened(item, ret); 1343 } else { 1344 BString string; 1345 string.SetToFormat(B_TRANSLATE("Opening '%s'."), item->Name().String()); 1346 fControls->SetDisabledString(string.String()); 1347 } 1348 } 1349 1350 1351 static int 1352 FindCdPlayerDevice(const char* directory) 1353 { 1354 BDirectory dir; 1355 dir.SetTo(directory); 1356 if (dir.InitCheck() != B_NO_ERROR) 1357 return false; 1358 dir.Rewind(); 1359 BEntry entry; 1360 while (dir.GetNextEntry(&entry) >= 0) { 1361 BPath path; 1362 if (entry.GetPath(&path) != B_NO_ERROR) 1363 continue; 1364 const char* name = path.Path(); 1365 entry_ref e; 1366 if (entry.GetRef(&e) != B_NO_ERROR) 1367 continue; 1368 if (entry.IsDirectory()) { 1369 if (strcmp(e.name, "floppy") == 0) 1370 continue; // ignore floppy 1371 int deviceFD = FindCdPlayerDevice(name); 1372 if (deviceFD >= 0) 1373 return deviceFD; 1374 } else { 1375 if (strcmp(e.name, "raw") != 0) 1376 continue; 1377 int deviceFD = open(name, O_RDONLY); 1378 if (deviceFD < 0) 1379 continue; 1380 device_geometry geometry; 1381 if (ioctl(deviceFD, B_GET_GEOMETRY, &geometry, sizeof(geometry)) >= 0 1382 && geometry.device_type == B_CD) 1383 return deviceFD; 1384 close(deviceFD); 1385 } 1386 } 1387 return B_ERROR; 1388 } 1389 1390 1391 void 1392 MainWin::Eject() 1393 { 1394 status_t mediaStatus = B_DEV_NO_MEDIA; 1395 // find the cd player device 1396 int cdPlayerFd = FindCdPlayerDevice("/dev/disk"); 1397 // get the status first 1398 ioctl(cdPlayerFd, B_GET_MEDIA_STATUS, &mediaStatus, sizeof(mediaStatus)); 1399 // if door open, load the media, else eject the cd 1400 status_t result = ioctl(cdPlayerFd, 1401 mediaStatus == B_DEV_DOOR_OPEN ? B_LOAD_MEDIA : B_EJECT_DEVICE); 1402 if (result != B_NO_ERROR) 1403 printf("Error ejecting device"); 1404 close(cdPlayerFd); 1405 } 1406 1407 1408 void 1409 MainWin::ShowFileInfo() 1410 { 1411 if (!fInfoWin) 1412 fInfoWin = new InfoWin(Frame().LeftTop(), fController); 1413 1414 if (fInfoWin->Lock()) { 1415 if (fInfoWin->IsHidden()) 1416 fInfoWin->Show(); 1417 else 1418 fInfoWin->Activate(); 1419 fInfoWin->Unlock(); 1420 } 1421 } 1422 1423 1424 void 1425 MainWin::ShowPlaylistWindow() 1426 { 1427 if (fPlaylistWindow->Lock()) { 1428 // make sure the window shows on the same workspace as ourself 1429 uint32 workspaces = Workspaces(); 1430 if (fPlaylistWindow->Workspaces() != workspaces) 1431 fPlaylistWindow->SetWorkspaces(workspaces); 1432 1433 // show or activate 1434 if (fPlaylistWindow->IsHidden()) 1435 fPlaylistWindow->Show(); 1436 else 1437 fPlaylistWindow->Activate(); 1438 1439 fPlaylistWindow->Unlock(); 1440 } 1441 } 1442 1443 1444 void 1445 MainWin::VideoAspectChange(int forcedWidth, int forcedHeight, float widthScale) 1446 { 1447 // Force specific source size and pixel width scale. 1448 if (fHasVideo) { 1449 int width; 1450 int height; 1451 fController->GetSize(&width, &height); 1452 VideoFormatChange(forcedWidth, forcedHeight, 1453 lround(width * widthScale), height); 1454 } 1455 } 1456 1457 1458 void 1459 MainWin::VideoAspectChange(float widthScale) 1460 { 1461 // Called when video aspect ratio changes and the original 1462 // width/height should be restored too, display aspect is not known, 1463 // only pixel width scale. 1464 if (fHasVideo) { 1465 int width; 1466 int height; 1467 fController->GetSize(&width, &height); 1468 VideoFormatChange(width, height, lround(width * widthScale), height); 1469 } 1470 } 1471 1472 1473 void 1474 MainWin::VideoAspectChange(int widthAspect, int heightAspect) 1475 { 1476 // Called when video aspect ratio changes and the original 1477 // width/height should be restored too. 1478 if (fHasVideo) { 1479 int width; 1480 int height; 1481 fController->GetSize(&width, &height); 1482 VideoFormatChange(width, height, widthAspect, heightAspect); 1483 } 1484 } 1485 1486 1487 void 1488 MainWin::VideoFormatChange(int width, int height, int widthAspect, 1489 int heightAspect) 1490 { 1491 // Called when video format or aspect ratio changes. 1492 1493 printf("VideoFormatChange enter: width %d, height %d, " 1494 "aspect ratio: %d:%d\n", width, height, widthAspect, heightAspect); 1495 1496 // remember current view scale 1497 int percent = _CurrentVideoSizeInPercent(); 1498 1499 fSourceWidth = width; 1500 fSourceHeight = height; 1501 fWidthAspect = widthAspect; 1502 fHeightAspect = heightAspect; 1503 1504 if (percent == 100) 1505 _ResizeWindow(100); 1506 else 1507 FrameResized(Bounds().Width(), Bounds().Height()); 1508 1509 printf("VideoFormatChange leave\n"); 1510 } 1511 1512 1513 void 1514 MainWin::GetQuitMessage(BMessage* message) 1515 { 1516 message->AddPointer("instance", this); 1517 message->AddRect("window frame", Frame()); 1518 message->AddBool("audio only", !fHasVideo); 1519 message->AddInt64("creation time", fCreationTime); 1520 1521 if (!fHasVideo && fHasAudio) { 1522 // store playlist, current index and position if this is audio 1523 BMessage playlistArchive; 1524 1525 BAutolock controllerLocker(fController); 1526 playlistArchive.AddInt64("position", fController->TimePosition()); 1527 controllerLocker.Unlock(); 1528 1529 if (!fPlaylist) 1530 return; 1531 1532 BAutolock playlistLocker(fPlaylist); 1533 if (fPlaylist->Archive(&playlistArchive) != B_OK 1534 || playlistArchive.AddInt32("index", 1535 fPlaylist->CurrentItemIndex()) != B_OK 1536 || message->AddMessage("playlist", &playlistArchive) != B_OK) { 1537 fprintf(stderr, "Failed to store current playlist.\n"); 1538 } 1539 } 1540 } 1541 1542 1543 BHandler* 1544 MainWin::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, 1545 int32 what, const char* property) 1546 { 1547 BPropertyInfo propertyInfo(sPropertyInfo); 1548 if (propertyInfo.FindMatch(message, index, specifier, what, property) 1549 != B_ERROR) 1550 return this; 1551 1552 return BWindow::ResolveSpecifier(message, index, specifier, what, property); 1553 } 1554 1555 1556 status_t 1557 MainWin::GetSupportedSuites(BMessage* data) 1558 { 1559 if (data == NULL) 1560 return B_BAD_VALUE; 1561 1562 status_t status = data->AddString("suites", "suite/vnd.Haiku-MediaPlayer"); 1563 if (status != B_OK) 1564 return status; 1565 1566 BPropertyInfo propertyInfo(sPropertyInfo); 1567 status = data->AddFlat("messages", &propertyInfo); 1568 if (status != B_OK) 1569 return status; 1570 1571 return BWindow::GetSupportedSuites(data); 1572 } 1573 1574 1575 // #pragma mark - 1576 1577 1578 void 1579 MainWin::_RefsReceived(BMessage* message) 1580 { 1581 // the playlist is replaced by dropped files 1582 // or the dropped files are appended to the end 1583 // of the existing playlist if <shift> is pressed 1584 bool append = false; 1585 if (message->FindBool("append to playlist", &append) != B_OK) 1586 append = modifiers() & B_SHIFT_KEY; 1587 1588 BAutolock _(fPlaylist); 1589 int32 appendIndex = append ? APPEND_INDEX_APPEND_LAST 1590 : APPEND_INDEX_REPLACE_PLAYLIST; 1591 message->AddInt32("append_index", appendIndex); 1592 1593 // forward the message to the playlist window, 1594 // so that undo/redo is used for modifying the playlist 1595 fPlaylistWindow->PostMessage(message); 1596 1597 if (message->FindRect("window frame", &fNoVideoFrame) != B_OK) 1598 fNoVideoFrame = BRect(); 1599 } 1600 1601 1602 void 1603 MainWin::_PlaylistItemOpened(const PlaylistItemRef& item, status_t result) 1604 { 1605 if (result != B_OK) { 1606 BAutolock _(fPlaylist); 1607 1608 item->SetPlaybackFailed(); 1609 bool allItemsFailed = true; 1610 int32 count = fPlaylist->CountItems(); 1611 for (int32 i = 0; i < count; i++) { 1612 if (!fPlaylist->ItemAtFast(i)->PlaybackFailed()) { 1613 allItemsFailed = false; 1614 break; 1615 } 1616 } 1617 1618 if (allItemsFailed) { 1619 // Display error if all files failed to play. 1620 BString message(B_TRANSLATE( 1621 "The file '%filename' could not be opened.\n\n"));; 1622 message.ReplaceAll("%filename", item->Name()); 1623 1624 if (result == B_MEDIA_NO_HANDLER) { 1625 // give a more detailed message for the most likely of all 1626 // errors 1627 message << B_TRANSLATE( 1628 "There is no decoder installed to handle the " 1629 "file format, or the decoder has trouble with the " 1630 "specific version of the format."); 1631 } else { 1632 message << B_TRANSLATE("Error: ") << strerror(result); 1633 } 1634 BAlert* alert = new BAlert("error", message.String(), 1635 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1636 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1637 alert->Go(); 1638 fControls->SetDisabledString(kDisabledSeekMessage); 1639 } else { 1640 // Just go to the next file and don't bother user (yet) 1641 fPlaylist->SetCurrentItemIndex(fPlaylist->CurrentItemIndex() + 1); 1642 } 1643 1644 fHasFile = false; 1645 fHasVideo = false; 1646 fHasAudio = false; 1647 SetTitle(kApplicationName); 1648 } else { 1649 fHasFile = true; 1650 fHasVideo = fController->VideoTrackCount() != 0; 1651 fHasAudio = fController->AudioTrackCount() != 0; 1652 SetTitle(item->Name().String()); 1653 1654 if (fInitialSeekPosition < 0) { 1655 fInitialSeekPosition 1656 = fController->TimeDuration() + fInitialSeekPosition; 1657 } 1658 fController->SetTimePosition(fInitialSeekPosition); 1659 fInitialSeekPosition = 0; 1660 1661 if (fPlaylist->CountItems() == 1) 1662 fController->RestoreState(); 1663 } 1664 _SetupWindow(); 1665 1666 if (result == B_OK) 1667 _UpdatePlaylistItemFile(); 1668 } 1669 1670 1671 void 1672 MainWin::_SetupWindow() 1673 { 1674 // printf("MainWin::_SetupWindow\n"); 1675 // Populate the track menus 1676 _SetupTrackMenus(fAudioTrackMenu, fVideoTrackMenu, fSubTitleTrackMenu); 1677 _UpdateAudioChannelCount(fController->CurrentAudioTrack()); 1678 1679 fVideoMenu->SetEnabled(fHasVideo); 1680 fAudioMenu->SetEnabled(fHasAudio); 1681 int previousSourceWidth = fSourceWidth; 1682 int previousSourceHeight = fSourceHeight; 1683 int previousWidthAspect = fWidthAspect; 1684 int previousHeightAspect = fHeightAspect; 1685 if (fHasVideo) { 1686 fController->GetSize(&fSourceWidth, &fSourceHeight, 1687 &fWidthAspect, &fHeightAspect); 1688 } else { 1689 fSourceWidth = 0; 1690 fSourceHeight = 0; 1691 fWidthAspect = 1; 1692 fHeightAspect = 1; 1693 } 1694 _UpdateControlsEnabledStatus(); 1695 1696 // Adopt the size and window layout if necessary 1697 if (previousSourceWidth != fSourceWidth 1698 || previousSourceHeight != fSourceHeight 1699 || previousWidthAspect != fWidthAspect 1700 || previousHeightAspect != fHeightAspect) { 1701 1702 _SetWindowSizeLimits(); 1703 1704 if (!fIsFullscreen) { 1705 // Resize to 100% but stay on screen 1706 _ResizeWindow(100, !fHasVideo, true); 1707 } else { 1708 // Make sure we relayout the video view when in full screen mode 1709 FrameResized(Frame().Width(), Frame().Height()); 1710 } 1711 } 1712 1713 _ShowIfNeeded(); 1714 1715 fVideoView->MakeFocus(); 1716 } 1717 1718 1719 void 1720 MainWin::_CreateMenu() 1721 { 1722 fFileMenu = new BMenu(kApplicationName); 1723 fPlaylistMenu = new BMenu(B_TRANSLATE("Playlist" B_UTF8_ELLIPSIS)); 1724 fAudioMenu = new BMenu(B_TRANSLATE("Audio")); 1725 fVideoMenu = new BMenu(B_TRANSLATE("Video")); 1726 fVideoAspectMenu = new BMenu(B_TRANSLATE("Aspect ratio")); 1727 fAudioTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track", 1728 "Audio Track Menu")); 1729 fVideoTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track", 1730 "Video Track Menu")); 1731 fSubTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles")); 1732 fAttributesMenu = new BMenu(B_TRANSLATE("Attributes")); 1733 1734 fMenuBar->AddItem(fFileMenu); 1735 fMenuBar->AddItem(fAudioMenu); 1736 fMenuBar->AddItem(fVideoMenu); 1737 fMenuBar->AddItem(fAttributesMenu); 1738 1739 BMenuItem* item = new BMenuItem(B_TRANSLATE("New player" B_UTF8_ELLIPSIS), 1740 new BMessage(M_NEW_PLAYER), 'N'); 1741 fFileMenu->AddItem(item); 1742 item->SetTarget(be_app); 1743 1744 // Add recent files to "Open File" entry as sub-menu. 1745 BRecentFilesList recentFiles(10, false, NULL, kAppSig); 1746 item = new BMenuItem(recentFiles.NewFileListMenu( 1747 B_TRANSLATE("Open file" B_UTF8_ELLIPSIS), NULL, NULL, this, 10, true, 1748 NULL, kAppSig), new BMessage(M_FILE_OPEN)); 1749 item->SetShortcut('O', 0); 1750 fFileMenu->AddItem(item); 1751 1752 item = new BMenuItem(B_TRANSLATE("Open network stream"), 1753 new BMessage(M_NETWORK_STREAM_OPEN)); 1754 fFileMenu->AddItem(item); 1755 1756 item = new BMenuItem(B_TRANSLATE("Eject Device"), 1757 new BMessage(M_EJECT_DEVICE)); 1758 fFileMenu->AddItem(item); 1759 1760 fFileMenu->AddSeparatorItem(); 1761 1762 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("File info" B_UTF8_ELLIPSIS), 1763 new BMessage(M_FILE_INFO), 'I')); 1764 fFileMenu->AddItem(fPlaylistMenu); 1765 fPlaylistMenu->Superitem()->SetShortcut('P', B_COMMAND_KEY); 1766 fPlaylistMenu->Superitem()->SetMessage(new BMessage(M_FILE_PLAYLIST)); 1767 1768 fFileMenu->AddSeparatorItem(); 1769 1770 fNoInterfaceMenuItem = new BMenuItem(B_TRANSLATE("Hide interface"), 1771 new BMessage(M_TOGGLE_NO_INTERFACE), 'H'); 1772 fFileMenu->AddItem(fNoInterfaceMenuItem); 1773 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Always on top"), 1774 new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A')); 1775 1776 item = new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), 1777 new BMessage(M_SETTINGS), ','); 1778 fFileMenu->AddItem(item); 1779 item->SetTarget(be_app); 1780 1781 fFileMenu->AddSeparatorItem(); 1782 1783 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Close"), 1784 new BMessage(M_FILE_CLOSE), 'W')); 1785 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 1786 new BMessage(M_FILE_QUIT), 'Q')); 1787 1788 fPlaylistMenu->SetRadioMode(true); 1789 1790 fAudioMenu->AddItem(fAudioTrackMenu); 1791 1792 fVideoMenu->AddItem(fVideoTrackMenu); 1793 fVideoMenu->AddItem(fSubTitleTrackMenu); 1794 fVideoMenu->AddSeparatorItem(); 1795 BMessage* resizeMessage = new BMessage(M_VIEW_SIZE); 1796 resizeMessage->AddInt32("size", 50); 1797 fVideoMenu->AddItem(new BMenuItem( 1798 B_TRANSLATE("50% scale"), resizeMessage, '0')); 1799 1800 resizeMessage = new BMessage(M_VIEW_SIZE); 1801 resizeMessage->AddInt32("size", 100); 1802 fVideoMenu->AddItem(new BMenuItem( 1803 B_TRANSLATE("100% scale"), resizeMessage, '1')); 1804 1805 resizeMessage = new BMessage(M_VIEW_SIZE); 1806 resizeMessage->AddInt32("size", 200); 1807 fVideoMenu->AddItem(new BMenuItem( 1808 B_TRANSLATE("200% scale"), resizeMessage, '2')); 1809 1810 resizeMessage = new BMessage(M_VIEW_SIZE); 1811 resizeMessage->AddInt32("size", 300); 1812 fVideoMenu->AddItem(new BMenuItem( 1813 B_TRANSLATE("300% scale"), resizeMessage, '3')); 1814 1815 resizeMessage = new BMessage(M_VIEW_SIZE); 1816 resizeMessage->AddInt32("size", 400); 1817 fVideoMenu->AddItem(new BMenuItem( 1818 B_TRANSLATE("400% scale"), resizeMessage, '4')); 1819 1820 fVideoMenu->AddSeparatorItem(); 1821 1822 fVideoMenu->AddItem(new BMenuItem(B_TRANSLATE("Full screen"), 1823 new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER)); 1824 1825 fVideoMenu->AddSeparatorItem(); 1826 1827 _SetupVideoAspectItems(fVideoAspectMenu); 1828 fVideoMenu->AddItem(fVideoAspectMenu); 1829 1830 fRatingMenu = new BMenu(B_TRANSLATE("Rating")); 1831 fAttributesMenu->AddItem(fRatingMenu); 1832 for (int32 i = 1; i <= 10; i++) { 1833 char label[16]; 1834 snprintf(label, sizeof(label), "%" B_PRId32, i); 1835 BMessage* setRatingMsg = new BMessage(M_SET_RATING); 1836 setRatingMsg->AddInt32("rating", i); 1837 fRatingMenu->AddItem(new BMenuItem(label, setRatingMsg)); 1838 } 1839 1840 BMessage* message = new BMessage(M_SET_RATING); 1841 message->AddInt32("rating", 0); 1842 fResetRatingItem = new BMenuItem(B_TRANSLATE("Reset rating"), message); 1843 fAttributesMenu->AddItem(fResetRatingItem); 1844 } 1845 1846 1847 void 1848 MainWin::_SetupVideoAspectItems(BMenu* menu) 1849 { 1850 BMenuItem* item; 1851 while ((item = menu->RemoveItem((int32)0)) != NULL) 1852 delete item; 1853 1854 int width; 1855 int height; 1856 int widthAspect; 1857 int heightAspect; 1858 fController->GetSize(&width, &height, &widthAspect, &heightAspect); 1859 // We don't care if there is a video track at all. In that 1860 // case we should end up not marking any item. 1861 1862 // NOTE: The item marking may end up marking for example both 1863 // "Stream Settings" and "16 : 9" if the stream settings happen to 1864 // be "16 : 9". 1865 1866 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Stream settings"), 1867 new BMessage(M_ASPECT_SAME_AS_SOURCE), '1', B_SHIFT_KEY)); 1868 item->SetMarked(widthAspect == fWidthAspect 1869 && heightAspect == fHeightAspect); 1870 1871 menu->AddItem(item = new BMenuItem(B_TRANSLATE("No aspect correction"), 1872 new BMessage(M_ASPECT_NO_DISTORTION), '0', B_SHIFT_KEY)); 1873 item->SetMarked(width == fWidthAspect && height == fHeightAspect); 1874 1875 menu->AddSeparatorItem(); 1876 1877 menu->AddItem(item = new BMenuItem("4 : 3", 1878 new BMessage(M_ASPECT_4_3), 2, B_SHIFT_KEY)); 1879 item->SetMarked(fWidthAspect == 4 && fHeightAspect == 3); 1880 menu->AddItem(item = new BMenuItem("16 : 9", 1881 new BMessage(M_ASPECT_16_9), 3, B_SHIFT_KEY)); 1882 item->SetMarked(fWidthAspect == 16 && fHeightAspect == 9); 1883 1884 menu->AddSeparatorItem(); 1885 1886 menu->AddItem(item = new BMenuItem("1.66 : 1", 1887 new BMessage(M_ASPECT_83_50))); 1888 item->SetMarked(fWidthAspect == 83 && fHeightAspect == 50); 1889 menu->AddItem(item = new BMenuItem("1.75 : 1", 1890 new BMessage(M_ASPECT_7_4))); 1891 item->SetMarked(fWidthAspect == 7 && fHeightAspect == 4); 1892 menu->AddItem(item = new BMenuItem(B_TRANSLATE("1.85 : 1 (American)"), 1893 new BMessage(M_ASPECT_37_20))); 1894 item->SetMarked(fWidthAspect == 37 && fHeightAspect == 20); 1895 menu->AddItem(item = new BMenuItem(B_TRANSLATE("2.35 : 1 (Cinemascope)"), 1896 new BMessage(M_ASPECT_47_20))); 1897 item->SetMarked(fWidthAspect == 47 && fHeightAspect == 20); 1898 } 1899 1900 1901 void 1902 MainWin::_SetupTrackMenus(BMenu* audioTrackMenu, BMenu* videoTrackMenu, 1903 BMenu* subTitleTrackMenu) 1904 { 1905 audioTrackMenu->RemoveItems(0, audioTrackMenu->CountItems(), true); 1906 videoTrackMenu->RemoveItems(0, videoTrackMenu->CountItems(), true); 1907 subTitleTrackMenu->RemoveItems(0, subTitleTrackMenu->CountItems(), true); 1908 1909 char s[100]; 1910 1911 int count = fController->AudioTrackCount(); 1912 int current = fController->CurrentAudioTrack(); 1913 for (int i = 0; i < count; i++) { 1914 BMessage metaData; 1915 const char* languageString = NULL; 1916 if (fController->GetAudioMetaData(i, &metaData) == B_OK) 1917 metaData.FindString("language", &languageString); 1918 if (languageString != NULL) { 1919 BLanguage language(languageString); 1920 BString languageName; 1921 if (language.GetName(languageName) == B_OK) 1922 languageString = languageName.String(); 1923 snprintf(s, sizeof(s), "%s", languageString); 1924 } else 1925 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1); 1926 BMenuItem* item = new BMenuItem(s, 1927 new BMessage(M_SELECT_AUDIO_TRACK + i)); 1928 item->SetMarked(i == current); 1929 audioTrackMenu->AddItem(item); 1930 } 1931 if (count == 0) { 1932 audioTrackMenu->AddItem(new BMenuItem(B_TRANSLATE_CONTEXT("none", 1933 "Audio track menu"), new BMessage(M_DUMMY))); 1934 audioTrackMenu->ItemAt(0)->SetMarked(true); 1935 } 1936 audioTrackMenu->SetEnabled(count > 1); 1937 1938 count = fController->VideoTrackCount(); 1939 current = fController->CurrentVideoTrack(); 1940 for (int i = 0; i < count; i++) { 1941 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1); 1942 BMenuItem* item = new BMenuItem(s, 1943 new BMessage(M_SELECT_VIDEO_TRACK + i)); 1944 item->SetMarked(i == current); 1945 videoTrackMenu->AddItem(item); 1946 } 1947 if (count == 0) { 1948 videoTrackMenu->AddItem(new BMenuItem(B_TRANSLATE("none"), 1949 new BMessage(M_DUMMY))); 1950 videoTrackMenu->ItemAt(0)->SetMarked(true); 1951 } 1952 videoTrackMenu->SetEnabled(count > 1); 1953 1954 count = fController->SubTitleTrackCount(); 1955 if (count > 0) { 1956 current = fController->CurrentSubTitleTrack(); 1957 BMenuItem* item = new BMenuItem( 1958 B_TRANSLATE_CONTEXT("Off", "Subtitles menu"), 1959 new BMessage(M_SELECT_SUB_TITLE_TRACK - 1)); 1960 subTitleTrackMenu->AddItem(item); 1961 item->SetMarked(current == -1); 1962 1963 subTitleTrackMenu->AddSeparatorItem(); 1964 1965 for (int i = 0; i < count; i++) { 1966 const char* name = fController->SubTitleTrackName(i); 1967 if (name != NULL) 1968 snprintf(s, sizeof(s), "%s", name); 1969 else 1970 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1); 1971 item = new BMenuItem(s, 1972 new BMessage(M_SELECT_SUB_TITLE_TRACK + i)); 1973 item->SetMarked(i == current); 1974 subTitleTrackMenu->AddItem(item); 1975 } 1976 } else { 1977 subTitleTrackMenu->AddItem(new BMenuItem( 1978 B_TRANSLATE_CONTEXT("none", "Subtitles menu"), 1979 new BMessage(M_DUMMY))); 1980 subTitleTrackMenu->ItemAt(0)->SetMarked(true); 1981 } 1982 subTitleTrackMenu->SetEnabled(count > 0); 1983 } 1984 1985 1986 void 1987 MainWin::_UpdateAudioChannelCount(int32 audioTrackIndex) 1988 { 1989 fControls->SetAudioChannelCount(fController->AudioTrackChannelCount()); 1990 } 1991 1992 1993 void 1994 MainWin::_GetMinimumWindowSize(int& width, int& height) const 1995 { 1996 width = MIN_WIDTH; 1997 height = 0; 1998 if (!fNoInterface) { 1999 width = max_c(width, fMenuBarWidth); 2000 width = max_c(width, fControlsWidth); 2001 height = fMenuBarHeight + fControlsHeight; 2002 } 2003 } 2004 2005 2006 void 2007 MainWin::_GetUnscaledVideoSize(int& videoWidth, int& videoHeight) const 2008 { 2009 if (fWidthAspect != 0 && fHeightAspect != 0) { 2010 videoWidth = fSourceHeight * fWidthAspect / fHeightAspect; 2011 videoHeight = fSourceWidth * fHeightAspect / fWidthAspect; 2012 // Use the scaling which produces an enlarged view. 2013 if (videoWidth > fSourceWidth) { 2014 // Enlarge width 2015 videoHeight = fSourceHeight; 2016 } else { 2017 // Enlarge height 2018 videoWidth = fSourceWidth; 2019 } 2020 } else { 2021 videoWidth = fSourceWidth; 2022 videoHeight = fSourceHeight; 2023 } 2024 } 2025 2026 2027 void 2028 MainWin::_SetWindowSizeLimits() 2029 { 2030 int minWidth; 2031 int minHeight; 2032 _GetMinimumWindowSize(minWidth, minHeight); 2033 SetSizeLimits(minWidth - 1, 32000, minHeight - 1, 2034 fHasVideo ? 32000 : minHeight - 1); 2035 } 2036 2037 2038 int 2039 MainWin::_CurrentVideoSizeInPercent() const 2040 { 2041 if (!fHasVideo) 2042 return 0; 2043 2044 int videoWidth; 2045 int videoHeight; 2046 _GetUnscaledVideoSize(videoWidth, videoHeight); 2047 2048 int viewWidth = fVideoView->Bounds().IntegerWidth() + 1; 2049 int viewHeight = fVideoView->Bounds().IntegerHeight() + 1; 2050 2051 int widthPercent = viewWidth * 100 / videoWidth; 2052 int heightPercent = viewHeight * 100 / videoHeight; 2053 2054 if (widthPercent > heightPercent) 2055 return widthPercent; 2056 return heightPercent; 2057 } 2058 2059 2060 void 2061 MainWin::_ZoomVideoView(int percentDiff) 2062 { 2063 if (!fHasVideo) 2064 return; 2065 2066 int percent = _CurrentVideoSizeInPercent(); 2067 int newSize = percent * (100 + percentDiff) / 100; 2068 2069 if (newSize < 25) 2070 newSize = 25; 2071 if (newSize > 400) 2072 newSize = 400; 2073 if (newSize != percent) { 2074 BMessage message(M_VIEW_SIZE); 2075 message.AddInt32("size", newSize); 2076 PostMessage(&message); 2077 } 2078 } 2079 2080 2081 void 2082 MainWin::_ResizeWindow(int percent, bool useNoVideoWidth, bool stayOnScreen) 2083 { 2084 // Get required window size 2085 int videoWidth; 2086 int videoHeight; 2087 _GetUnscaledVideoSize(videoWidth, videoHeight); 2088 2089 videoWidth = (videoWidth * percent) / 100; 2090 videoHeight = (videoHeight * percent) / 100; 2091 2092 // Calculate and set the minimum window size 2093 int width; 2094 int height; 2095 _GetMinimumWindowSize(width, height); 2096 2097 width = max_c(width, videoWidth) - 1; 2098 if (useNoVideoWidth) 2099 width = max_c(width, fNoVideoWidth); 2100 height = height + videoHeight - 1; 2101 2102 if (stayOnScreen) { 2103 BRect screenFrame(BScreen(this).Frame()); 2104 BRect frame(Frame()); 2105 BRect decoratorFrame(DecoratorFrame()); 2106 2107 // Shrink the screen frame by the window border size 2108 screenFrame.top += frame.top - decoratorFrame.top; 2109 screenFrame.left += frame.left - decoratorFrame.left; 2110 screenFrame.right += frame.right - decoratorFrame.right; 2111 screenFrame.bottom += frame.bottom - decoratorFrame.bottom; 2112 2113 // Update frame to what the new size would be 2114 frame.right = frame.left + width; 2115 frame.bottom = frame.top + height; 2116 2117 if (!screenFrame.Contains(frame)) { 2118 // Resize the window so it doesn't extend outside the current 2119 // screen frame. 2120 // We don't use BWindow::MoveOnScreen() in order to resize the 2121 // window while keeping the same aspect ratio. 2122 if (frame.Width() > screenFrame.Width() 2123 || frame.Height() > screenFrame.Height()) { 2124 // too large 2125 int widthDiff 2126 = frame.IntegerWidth() - screenFrame.IntegerWidth(); 2127 int heightDiff 2128 = frame.IntegerHeight() - screenFrame.IntegerHeight(); 2129 2130 float shrinkScale; 2131 if (widthDiff > heightDiff) 2132 shrinkScale = (float)(width - widthDiff) / width; 2133 else 2134 shrinkScale = (float)(height - heightDiff) / height; 2135 2136 // Resize width/height and center window 2137 width = lround(width * shrinkScale); 2138 height = lround(height * shrinkScale); 2139 MoveTo((screenFrame.left + screenFrame.right - width) / 2, 2140 (screenFrame.top + screenFrame.bottom - height) / 2); 2141 } else { 2142 // just off-screen on one or more sides 2143 int offsetX = 0; 2144 int offsetY = 0; 2145 if (frame.left < screenFrame.left) 2146 offsetX = (int)(screenFrame.left - frame.left); 2147 else if (frame.right > screenFrame.right) 2148 offsetX = (int)(screenFrame.right - frame.right); 2149 if (frame.top < screenFrame.top) 2150 offsetY = (int)(screenFrame.top - frame.top); 2151 else if (frame.bottom > screenFrame.bottom) 2152 offsetY = (int)(screenFrame.bottom - frame.bottom); 2153 MoveBy(offsetX, offsetY); 2154 } 2155 } 2156 } 2157 2158 ResizeTo(width, height); 2159 } 2160 2161 2162 void 2163 MainWin::_ResizeVideoView(int x, int y, int width, int height) 2164 { 2165 // Keep aspect ratio, place video view inside 2166 // the background area (may create black bars). 2167 int videoWidth; 2168 int videoHeight; 2169 _GetUnscaledVideoSize(videoWidth, videoHeight); 2170 float scaledWidth = videoWidth; 2171 float scaledHeight = videoHeight; 2172 float factor = min_c(width / scaledWidth, height / scaledHeight); 2173 int renderWidth = lround(scaledWidth * factor); 2174 int renderHeight = lround(scaledHeight * factor); 2175 if (renderWidth > width) 2176 renderWidth = width; 2177 if (renderHeight > height) 2178 renderHeight = height; 2179 2180 int xOffset = (width - renderWidth) / 2; 2181 int yOffset = (height - renderHeight) / 2; 2182 2183 fVideoView->MoveTo(x, y); 2184 fVideoView->ResizeTo(width - 1, height - 1); 2185 2186 BRect videoFrame(xOffset, yOffset, 2187 xOffset + renderWidth - 1, yOffset + renderHeight - 1); 2188 2189 fVideoView->SetVideoFrame(videoFrame); 2190 fVideoView->SetSubTitleMaxBottom(height - 1); 2191 } 2192 2193 2194 // #pragma mark - 2195 2196 2197 void 2198 MainWin::_MouseDown(BMessage* msg, BView* originalHandler) 2199 { 2200 uint32 buttons = msg->FindInt32("buttons"); 2201 2202 // On Zeta, only "screen_where" is reliable, "where" and "be:view_where" 2203 // seem to be broken 2204 BPoint screenWhere; 2205 if (msg->FindPoint("screen_where", &screenWhere) != B_OK) { 2206 // TODO: remove 2207 // Workaround for BeOS R5, it has no "screen_where" 2208 if (!originalHandler || msg->FindPoint("where", &screenWhere) < B_OK) 2209 return; 2210 originalHandler->ConvertToScreen(&screenWhere); 2211 } 2212 2213 // double click handling 2214 2215 if (msg->FindInt32("clicks") % 2 == 0) { 2216 BRect rect(screenWhere.x - 1, screenWhere.y - 1, screenWhere.x + 1, 2217 screenWhere.y + 1); 2218 if (rect.Contains(fMouseDownMousePos)) { 2219 if (buttons == B_PRIMARY_MOUSE_BUTTON) 2220 PostMessage(M_TOGGLE_FULLSCREEN); 2221 else if (buttons == B_SECONDARY_MOUSE_BUTTON) 2222 PostMessage(M_TOGGLE_NO_INTERFACE); 2223 2224 return; 2225 } 2226 } 2227 2228 fMouseDownMousePos = screenWhere; 2229 fMouseDownWindowPos = Frame().LeftTop(); 2230 2231 if (buttons == B_PRIMARY_MOUSE_BUTTON && !fIsFullscreen) { 2232 // start mouse tracking 2233 fVideoView->SetMouseEventMask(B_POINTER_EVENTS | B_NO_POINTER_HISTORY 2234 /* | B_LOCK_WINDOW_FOCUS */); 2235 fMouseDownTracking = true; 2236 } 2237 2238 // pop up a context menu if right mouse button is down 2239 2240 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) 2241 _ShowContextMenu(screenWhere); 2242 } 2243 2244 2245 void 2246 MainWin::_MouseMoved(BMessage* msg, BView* originalHandler) 2247 { 2248 // msg->PrintToStream(); 2249 2250 BPoint mousePos; 2251 uint32 buttons = msg->FindInt32("buttons"); 2252 // On Zeta, only "screen_where" is reliable, "where" 2253 // and "be:view_where" seem to be broken 2254 if (msg->FindPoint("screen_where", &mousePos) != B_OK) { 2255 // TODO: remove 2256 // Workaround for BeOS R5, it has no "screen_where" 2257 if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK) 2258 return; 2259 originalHandler->ConvertToScreen(&mousePos); 2260 } 2261 2262 if (buttons == B_PRIMARY_MOUSE_BUTTON && fMouseDownTracking 2263 && !fIsFullscreen) { 2264 // printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y); 2265 float delta_x = mousePos.x - fMouseDownMousePos.x; 2266 float delta_y = mousePos.y - fMouseDownMousePos.y; 2267 float x = fMouseDownWindowPos.x + delta_x; 2268 float y = fMouseDownWindowPos.y + delta_y; 2269 // printf("move window to %.0f, %.0f\n", x, y); 2270 MoveTo(x, y); 2271 } 2272 2273 bigtime_t eventTime; 2274 if (msg->FindInt64("when", &eventTime) != B_OK) 2275 eventTime = system_time(); 2276 2277 if (buttons == 0 && fIsFullscreen) { 2278 BPoint moveDelta = mousePos - fLastMousePos; 2279 float moveDeltaDist 2280 = sqrtf(moveDelta.x * moveDelta.x + moveDelta.y * moveDelta.y); 2281 if (eventTime - fLastMouseMovedTime < 200000) 2282 fMouseMoveDist += moveDeltaDist; 2283 else 2284 fMouseMoveDist = moveDeltaDist; 2285 if (fMouseMoveDist > 5) 2286 _ShowFullscreenControls(true); 2287 } 2288 2289 fLastMousePos = mousePos; 2290 fLastMouseMovedTime =eventTime; 2291 } 2292 2293 2294 void 2295 MainWin::_MouseUp(BMessage* msg) 2296 { 2297 fMouseDownTracking = false; 2298 } 2299 2300 2301 void 2302 MainWin::_ShowContextMenu(const BPoint& screenPoint) 2303 { 2304 printf("Show context menu\n"); 2305 BPopUpMenu* menu = new BPopUpMenu("context menu", false, false); 2306 BMenuItem* item; 2307 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Full screen"), 2308 new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER)); 2309 item->SetMarked(fIsFullscreen); 2310 item->SetEnabled(fHasVideo); 2311 2312 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Hide interface"), 2313 new BMessage(M_TOGGLE_NO_INTERFACE), 'H')); 2314 item->SetMarked(fNoInterface); 2315 item->SetEnabled(fHasVideo && !fIsFullscreen); 2316 2317 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"), 2318 new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A')); 2319 item->SetMarked(fAlwaysOnTop); 2320 item->SetEnabled(fHasVideo); 2321 2322 BMenu* aspectSubMenu = new BMenu(B_TRANSLATE("Aspect ratio")); 2323 _SetupVideoAspectItems(aspectSubMenu); 2324 aspectSubMenu->SetTargetForItems(this); 2325 menu->AddItem(item = new BMenuItem(aspectSubMenu)); 2326 item->SetEnabled(fHasVideo); 2327 2328 menu->AddSeparatorItem(); 2329 2330 // Add track selector menus 2331 BMenu* audioTrackMenu = new BMenu(B_TRANSLATE("Audio track")); 2332 BMenu* videoTrackMenu = new BMenu(B_TRANSLATE("Video track")); 2333 BMenu* subTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles")); 2334 _SetupTrackMenus(audioTrackMenu, videoTrackMenu, subTitleTrackMenu); 2335 2336 audioTrackMenu->SetTargetForItems(this); 2337 videoTrackMenu->SetTargetForItems(this); 2338 subTitleTrackMenu->SetTargetForItems(this); 2339 2340 menu->AddItem(item = new BMenuItem(audioTrackMenu)); 2341 item->SetEnabled(fHasAudio); 2342 2343 menu->AddItem(item = new BMenuItem(videoTrackMenu)); 2344 item->SetEnabled(fHasVideo); 2345 2346 menu->AddItem(item = new BMenuItem(subTitleTrackMenu)); 2347 item->SetEnabled(fHasVideo); 2348 2349 menu->AddSeparatorItem(); 2350 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), new BMessage(M_FILE_QUIT), 'Q')); 2351 2352 menu->SetTargetForItems(this); 2353 BRect rect(screenPoint.x - 5, screenPoint.y - 5, screenPoint.x + 5, 2354 screenPoint.y + 5); 2355 menu->Go(screenPoint, true, true, rect, true); 2356 } 2357 2358 2359 /*! Trap keys that are about to be send to background or renderer view. 2360 Return true if it shouldn't be passed to the view. 2361 */ 2362 bool 2363 MainWin::_KeyDown(BMessage* msg) 2364 { 2365 uint32 key = msg->FindInt32("key"); 2366 uint32 rawChar = msg->FindInt32("raw_char"); 2367 uint32 modifier = msg->FindInt32("modifiers"); 2368 2369 // printf("key 0x%lx, rawChar 0x%lx, modifiers 0x%lx\n", key, rawChar, 2370 // modifier); 2371 2372 // ignore the system modifier namespace 2373 if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY)) 2374 == (B_CONTROL_KEY | B_COMMAND_KEY)) 2375 return false; 2376 2377 switch (rawChar) { 2378 case B_SPACE: 2379 fController->TogglePlaying(); 2380 return true; 2381 2382 case 'm': 2383 fController->ToggleMute(); 2384 return true; 2385 2386 case B_ESCAPE: 2387 if (!fIsFullscreen) 2388 break; 2389 2390 PostMessage(M_TOGGLE_FULLSCREEN); 2391 return true; 2392 2393 case B_ENTER: // Enter / Return 2394 if ((modifier & B_COMMAND_KEY) != 0) { 2395 PostMessage(M_TOGGLE_FULLSCREEN); 2396 return true; 2397 } 2398 break; 2399 2400 case B_TAB: 2401 case 'f': 2402 if ((modifier & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY 2403 | B_MENU_KEY)) == 0) { 2404 PostMessage(M_TOGGLE_FULLSCREEN); 2405 return true; 2406 } 2407 break; 2408 2409 case B_UP_ARROW: 2410 if ((modifier & B_COMMAND_KEY) != 0) 2411 PostMessage(M_SKIP_NEXT); 2412 else 2413 PostMessage(M_VOLUME_UP); 2414 return true; 2415 2416 case B_DOWN_ARROW: 2417 if ((modifier & B_COMMAND_KEY) != 0) 2418 PostMessage(M_SKIP_PREV); 2419 else 2420 PostMessage(M_VOLUME_DOWN); 2421 return true; 2422 2423 case B_RIGHT_ARROW: 2424 if ((modifier & B_COMMAND_KEY) != 0) 2425 PostMessage(M_SKIP_NEXT); 2426 else if (fAllowWinding) { 2427 BMessage windMessage(M_WIND); 2428 if ((modifier & B_SHIFT_KEY) != 0) { 2429 windMessage.AddInt64("how much", 30000000LL); 2430 windMessage.AddInt64("frames", 5); 2431 } else { 2432 windMessage.AddInt64("how much", 5000000LL); 2433 windMessage.AddInt64("frames", 1); 2434 } 2435 PostMessage(&windMessage); 2436 } 2437 return true; 2438 2439 case B_LEFT_ARROW: 2440 if ((modifier & B_COMMAND_KEY) != 0) 2441 PostMessage(M_SKIP_PREV); 2442 else if (fAllowWinding) { 2443 BMessage windMessage(M_WIND); 2444 if ((modifier & B_SHIFT_KEY) != 0) { 2445 windMessage.AddInt64("how much", -30000000LL); 2446 windMessage.AddInt64("frames", -5); 2447 } else { 2448 windMessage.AddInt64("how much", -5000000LL); 2449 windMessage.AddInt64("frames", -1); 2450 } 2451 PostMessage(&windMessage); 2452 } 2453 return true; 2454 2455 case B_PAGE_UP: 2456 PostMessage(M_SKIP_NEXT); 2457 return true; 2458 2459 case B_PAGE_DOWN: 2460 PostMessage(M_SKIP_PREV); 2461 return true; 2462 2463 case '+': 2464 if ((modifier & B_COMMAND_KEY) == 0) { 2465 _ZoomVideoView(10); 2466 return true; 2467 } 2468 break; 2469 2470 case '-': 2471 if ((modifier & B_COMMAND_KEY) == 0) { 2472 _ZoomVideoView(-10); 2473 return true; 2474 } 2475 break; 2476 2477 case B_DELETE: 2478 case 'd': // d for delete 2479 case 't': // t for Trash 2480 if ((modifiers() & B_COMMAND_KEY) != 0) { 2481 BAutolock _(fPlaylist); 2482 BMessage removeMessage(M_PLAYLIST_MOVE_TO_TRASH); 2483 removeMessage.AddInt32("playlist index", 2484 fPlaylist->CurrentItemIndex()); 2485 fPlaylistWindow->PostMessage(&removeMessage); 2486 return true; 2487 } 2488 break; 2489 } 2490 2491 switch (key) { 2492 case 0x3a: // numeric keypad + 2493 if ((modifier & B_COMMAND_KEY) == 0) { 2494 _ZoomVideoView(10); 2495 return true; 2496 } 2497 break; 2498 2499 case 0x25: // numeric keypad - 2500 if ((modifier & B_COMMAND_KEY) == 0) { 2501 _ZoomVideoView(-10); 2502 return true; 2503 } 2504 break; 2505 2506 case 0x38: // numeric keypad up arrow 2507 PostMessage(M_VOLUME_UP); 2508 return true; 2509 2510 case 0x59: // numeric keypad down arrow 2511 PostMessage(M_VOLUME_DOWN); 2512 return true; 2513 2514 case 0x39: // numeric keypad page up 2515 case 0x4a: // numeric keypad right arrow 2516 PostMessage(M_SKIP_NEXT); 2517 return true; 2518 2519 case 0x5a: // numeric keypad page down 2520 case 0x48: // numeric keypad left arrow 2521 PostMessage(M_SKIP_PREV); 2522 return true; 2523 2524 // Playback controls along the bottom of the keyboard: 2525 // Z X C (V) B for US International 2526 case 0x4c: 2527 PostMessage(M_SKIP_PREV); 2528 return true; 2529 case 0x4d: 2530 fController->TogglePlaying(); 2531 return true; 2532 case 0x4e: 2533 fController->Pause(); 2534 return true; 2535 case 0x4f: 2536 fController->Stop(); 2537 return true; 2538 case 0x50: 2539 PostMessage(M_SKIP_NEXT); 2540 return true; 2541 } 2542 2543 return false; 2544 } 2545 2546 2547 // #pragma mark - 2548 2549 2550 void 2551 MainWin::_ToggleFullscreen() 2552 { 2553 printf("_ToggleFullscreen enter\n"); 2554 2555 if (!fHasVideo) { 2556 printf("_ToggleFullscreen - ignoring, as we don't have a video\n"); 2557 return; 2558 } 2559 2560 fIsFullscreen = !fIsFullscreen; 2561 2562 if (fIsFullscreen) { 2563 // switch to fullscreen 2564 2565 fSavedFrame = Frame(); 2566 printf("saving current frame: %d %d %d %d\n", int(fSavedFrame.left), 2567 int(fSavedFrame.top), int(fSavedFrame.right), 2568 int(fSavedFrame.bottom)); 2569 BScreen screen(this); 2570 BRect rect(screen.Frame()); 2571 2572 Hide(); 2573 MoveTo(rect.left, rect.top); 2574 ResizeTo(rect.Width(), rect.Height()); 2575 Show(); 2576 2577 } else { 2578 // switch back from full screen mode 2579 _ShowFullscreenControls(false, false); 2580 2581 Hide(); 2582 MoveTo(fSavedFrame.left, fSavedFrame.top); 2583 ResizeTo(fSavedFrame.Width(), fSavedFrame.Height()); 2584 Show(); 2585 } 2586 2587 fVideoView->SetFullscreen(fIsFullscreen); 2588 2589 _MarkItem(fFileMenu, M_TOGGLE_FULLSCREEN, fIsFullscreen); 2590 2591 printf("_ToggleFullscreen leave\n"); 2592 } 2593 2594 void 2595 MainWin::_ToggleAlwaysOnTop() 2596 { 2597 fAlwaysOnTop = !fAlwaysOnTop; 2598 SetFeel(fAlwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL); 2599 2600 _MarkItem(fFileMenu, M_TOGGLE_ALWAYS_ON_TOP, fAlwaysOnTop); 2601 } 2602 2603 2604 void 2605 MainWin::_ToggleNoInterface() 2606 { 2607 printf("_ToggleNoInterface enter\n"); 2608 2609 if (fIsFullscreen || !fHasVideo) { 2610 // Fullscreen playback is always without interface and 2611 // audio playback is always with interface. So we ignore these 2612 // two states here. 2613 printf("_ToggleNoControls leave, doing nothing, we are fullscreen\n"); 2614 return; 2615 } 2616 2617 fNoInterface = !fNoInterface; 2618 _SetWindowSizeLimits(); 2619 2620 if (fNoInterface) { 2621 MoveBy(0, fMenuBarHeight); 2622 ResizeBy(0, -(fControlsHeight + fMenuBarHeight)); 2623 SetLook(B_BORDERED_WINDOW_LOOK); 2624 } else { 2625 MoveBy(0, -fMenuBarHeight); 2626 ResizeBy(0, fControlsHeight + fMenuBarHeight); 2627 SetLook(B_TITLED_WINDOW_LOOK); 2628 } 2629 2630 _MarkItem(fFileMenu, M_TOGGLE_NO_INTERFACE, fNoInterface); 2631 2632 printf("_ToggleNoInterface leave\n"); 2633 } 2634 2635 2636 void 2637 MainWin::_ShowIfNeeded() 2638 { 2639 // Only proceed if the window is already running 2640 if (find_thread(NULL) != Thread()) 2641 return; 2642 2643 if (!fHasVideo && fNoVideoFrame.IsValid()) { 2644 MoveTo(fNoVideoFrame.LeftTop()); 2645 ResizeTo(fNoVideoFrame.Width(), fNoVideoFrame.Height()); 2646 MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN); 2647 } else if (fHasVideo && IsHidden()) 2648 CenterOnScreen(); 2649 2650 fNoVideoFrame = BRect(); 2651 2652 if (IsHidden()) { 2653 Show(); 2654 UpdateIfNeeded(); 2655 } 2656 } 2657 2658 2659 void 2660 MainWin::_ShowFullscreenControls(bool show, bool animate) 2661 { 2662 if (fShowsFullscreenControls == show) 2663 return; 2664 2665 fShowsFullscreenControls = show; 2666 fVideoView->SetFullscreenControlsVisible(show); 2667 2668 if (show) { 2669 fControls->RemoveSelf(); 2670 fControls->MoveTo(fVideoView->Bounds().left, 2671 fVideoView->Bounds().bottom + 1); 2672 fVideoView->AddChild(fControls); 2673 if (fScaleFullscreenControls) 2674 fControls->SetSymbolScale(1.5f); 2675 2676 while (fControls->IsHidden()) 2677 fControls->Show(); 2678 } 2679 2680 if (animate) { 2681 // Slide the controls into view. We need to do this with 2682 // messages, otherwise we block the video playback for the 2683 // time of the animation. 2684 const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 }; 2685 const int32 steps = sizeof(kAnimationOffsets) / sizeof(float); 2686 float height = fControls->Bounds().Height(); 2687 float moveDist = show ? -height : height; 2688 float originalY = fControls->Frame().top; 2689 for (int32 i = 0; i < steps; i++) { 2690 BMessage message(M_SLIDE_CONTROLS); 2691 message.AddFloat("offset", 2692 floorf(moveDist * kAnimationOffsets[i])); 2693 PostMessage(&message, this); 2694 } 2695 BMessage finalMessage(M_FINISH_SLIDING_CONTROLS); 2696 finalMessage.AddFloat("offset", originalY + moveDist); 2697 finalMessage.AddBool("show", show); 2698 PostMessage(&finalMessage, this); 2699 } else if (!show) { 2700 fControls->RemoveSelf(); 2701 fControls->MoveTo(fVideoView->Frame().left, 2702 fVideoView->Frame().bottom + 1); 2703 fBackground->AddChild(fControls); 2704 fControls->SetSymbolScale(1.0f); 2705 2706 while (!fControls->IsHidden()) 2707 fControls->Hide(); 2708 } 2709 } 2710 2711 2712 // #pragma mark - 2713 2714 2715 void 2716 MainWin::_Wind(bigtime_t howMuch, int64 frames) 2717 { 2718 if (!fAllowWinding || !fController->Lock()) 2719 return; 2720 2721 if (frames != 0 && fHasVideo && !fController->IsPlaying()) { 2722 int64 newFrame = fController->CurrentFrame() + frames; 2723 fController->SetFramePosition(newFrame); 2724 } else { 2725 bigtime_t seekTime = fController->TimePosition() + howMuch; 2726 if (seekTime < 0) { 2727 fInitialSeekPosition = seekTime; 2728 PostMessage(M_SKIP_PREV); 2729 } else if (seekTime > fController->TimeDuration()) { 2730 fInitialSeekPosition = 0; 2731 PostMessage(M_SKIP_NEXT); 2732 } else 2733 fController->SetTimePosition(seekTime); 2734 } 2735 2736 fController->Unlock(); 2737 fAllowWinding = false; 2738 } 2739 2740 2741 // #pragma mark - 2742 2743 2744 void 2745 MainWin::_UpdatePlaylistItemFile() 2746 { 2747 BAutolock locker(fPlaylist); 2748 const FilePlaylistItem* item 2749 = dynamic_cast<const FilePlaylistItem*>(fController->Item()); 2750 if (item == NULL) 2751 return; 2752 2753 if (!fHasVideo && !fHasAudio) 2754 return; 2755 2756 BNode node(&item->Ref()); 2757 if (node.InitCheck()) 2758 return; 2759 2760 locker.Unlock(); 2761 2762 // Set some standard attributes of the currently played file. 2763 // This should only be a temporary solution. 2764 2765 // Write duration 2766 const char* kDurationAttrName = "Media:Length"; 2767 attr_info info; 2768 status_t status = node.GetAttrInfo(kDurationAttrName, &info); 2769 if (status != B_OK || info.size == 0) { 2770 bigtime_t duration = fController->TimeDuration(); 2771 // TODO: Tracker does not seem to care about endian for scalar types 2772 node.WriteAttr(kDurationAttrName, B_INT64_TYPE, 0, &duration, 2773 sizeof(int64)); 2774 } 2775 2776 // Write audio bitrate 2777 if (fHasAudio) { 2778 status = node.GetAttrInfo("Audio:Bitrate", &info); 2779 if (status != B_OK || info.size == 0) { 2780 media_format format; 2781 if (fController->GetEncodedAudioFormat(&format) == B_OK 2782 && format.type == B_MEDIA_ENCODED_AUDIO) { 2783 int32 bitrate = (int32)(format.u.encoded_audio.bit_rate 2784 / 1000); 2785 char text[256]; 2786 snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate); 2787 node.WriteAttr("Audio:Bitrate", B_STRING_TYPE, 0, text, 2788 strlen(text) + 1); 2789 } 2790 } 2791 } 2792 2793 // Write video bitrate 2794 if (fHasVideo) { 2795 status = node.GetAttrInfo("Video:Bitrate", &info); 2796 if (status != B_OK || info.size == 0) { 2797 media_format format; 2798 if (fController->GetEncodedVideoFormat(&format) == B_OK 2799 && format.type == B_MEDIA_ENCODED_VIDEO) { 2800 int32 bitrate = (int32)(format.u.encoded_video.avg_bit_rate 2801 / 1000); 2802 char text[256]; 2803 snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate); 2804 node.WriteAttr("Video:Bitrate", B_STRING_TYPE, 0, text, 2805 strlen(text) + 1); 2806 } 2807 } 2808 } 2809 2810 _UpdateAttributesMenu(node); 2811 } 2812 2813 2814 void 2815 MainWin::_UpdateAttributesMenu(const BNode& node) 2816 { 2817 int32 rating = -1; 2818 2819 attr_info info; 2820 status_t status = node.GetAttrInfo(kRatingAttrName, &info); 2821 if (status == B_OK && info.type == B_INT32_TYPE) { 2822 // Node has the Rating attribute. 2823 node.ReadAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating, 2824 sizeof(rating)); 2825 } 2826 2827 for (int32 i = 0; BMenuItem* item = fRatingMenu->ItemAt(i); i++) 2828 item->SetMarked(i + 1 == rating); 2829 2830 fResetRatingItem->SetEnabled(rating > 0); 2831 } 2832 2833 2834 void 2835 MainWin::_SetRating(int32 rating) 2836 { 2837 BAutolock locker(fPlaylist); 2838 const FilePlaylistItem* item 2839 = dynamic_cast<const FilePlaylistItem*>(fController->Item()); 2840 if (item == NULL) 2841 return; 2842 2843 BNode node(&item->Ref()); 2844 if (node.InitCheck()) 2845 return; 2846 2847 locker.Unlock(); 2848 2849 node.WriteAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating, sizeof(rating)); 2850 2851 // TODO: The whole mechnism should work like this: 2852 // * There is already an attribute API for PlaylistItem, flesh it out! 2853 // * FilePlaylistItem node-monitors it's file somehow. 2854 // * FilePlaylistItem keeps attributes in sync and sends notications. 2855 // * MainWin updates the menu according to FilePlaylistItem notifications. 2856 // * PlaylistWin shows columns with attribute and other info. 2857 // * PlaylistWin updates also upon FilePlaylistItem notifications. 2858 // * This keeps attributes in sync when another app changes them. 2859 2860 _UpdateAttributesMenu(node); 2861 } 2862 2863 2864 void 2865 MainWin::_UpdateControlsEnabledStatus() 2866 { 2867 uint32 enabledButtons = 0; 2868 if (fHasVideo || fHasAudio) { 2869 enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED 2870 | SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED; 2871 } 2872 if (fHasAudio) 2873 enabledButtons |= VOLUME_ENABLED; 2874 2875 BAutolock _(fPlaylist); 2876 bool canSkipPrevious, canSkipNext; 2877 fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext); 2878 if (canSkipPrevious) 2879 enabledButtons |= SKIP_BACK_ENABLED; 2880 if (canSkipNext) 2881 enabledButtons |= SKIP_FORWARD_ENABLED; 2882 2883 fControls->SetEnabled(enabledButtons); 2884 2885 fNoInterfaceMenuItem->SetEnabled(fHasVideo); 2886 fAttributesMenu->SetEnabled(fHasAudio || fHasVideo); 2887 } 2888 2889 2890 void 2891 MainWin::_UpdatePlaylistMenu() 2892 { 2893 if (!fPlaylist->Lock()) 2894 return; 2895 2896 fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true); 2897 2898 int32 count = fPlaylist->CountItems(); 2899 for (int32 i = 0; i < count; i++) { 2900 PlaylistItem* item = fPlaylist->ItemAtFast(i); 2901 _AddPlaylistItem(item, i); 2902 } 2903 fPlaylistMenu->SetTargetForItems(this); 2904 2905 _MarkPlaylistItem(fPlaylist->CurrentItemIndex()); 2906 2907 fPlaylist->Unlock(); 2908 } 2909 2910 2911 void 2912 MainWin::_AddPlaylistItem(PlaylistItem* item, int32 index) 2913 { 2914 BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION); 2915 message->AddInt32("index", index); 2916 BMenuItem* menuItem = new BMenuItem(item->Name().String(), message); 2917 fPlaylistMenu->AddItem(menuItem, index); 2918 } 2919 2920 2921 void 2922 MainWin::_RemovePlaylistItem(int32 index) 2923 { 2924 delete fPlaylistMenu->RemoveItem(index); 2925 } 2926 2927 2928 void 2929 MainWin::_MarkPlaylistItem(int32 index) 2930 { 2931 if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) { 2932 item->SetMarked(true); 2933 // ... and in case the menu is currently on screen: 2934 if (fPlaylistMenu->LockLooper()) { 2935 fPlaylistMenu->Invalidate(); 2936 fPlaylistMenu->UnlockLooper(); 2937 } 2938 } 2939 } 2940 2941 2942 void 2943 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark) 2944 { 2945 if (BMenuItem* item = menu->FindItem(command)) 2946 item->SetMarked(mark); 2947 } 2948 2949 2950 void 2951 MainWin::_AdoptGlobalSettings() 2952 { 2953 mpSettings settings; 2954 Settings::Default()->Get(settings); 2955 2956 fCloseWhenDonePlayingMovie = settings.closeWhenDonePlayingMovie; 2957 fCloseWhenDonePlayingSound = settings.closeWhenDonePlayingSound; 2958 fLoopMovies = settings.loopMovie; 2959 fLoopSounds = settings.loopSound; 2960 fScaleFullscreenControls = settings.scaleFullscreenControls; 2961 } 2962