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