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