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