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