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