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