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