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