btrfs: Attempt to fix GCC2 build.
[haiku.git] / src / apps / mediaplayer / MainWin.cpp
blobb63b1934bb285f3e422b9ead97c6f35ae11b09f6
1 /*
2 * MainWin.cpp - Media Player for the Haiku Operating System
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)
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.
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.
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.
24 #include "MainWin.h"
26 #include <math.h>
27 #include <stdio.h>
28 #include <string.h>
30 #include <Alert.h>
31 #include <Application.h>
32 #include <Autolock.h>
33 #include <Catalog.h>
34 #include <Debug.h>
35 #include <fs_attr.h>
36 #include <LayoutBuilder.h>
37 #include <Language.h>
38 #include <Locale.h>
39 #include <MediaRoster.h>
40 #include <Menu.h>
41 #include <MenuBar.h>
42 #include <MenuItem.h>
43 #include <MessageRunner.h>
44 #include <Messenger.h>
45 #include <PopUpMenu.h>
46 #include <PropertyInfo.h>
47 #include <RecentItems.h>
48 #include <Roster.h>
49 #include <Screen.h>
50 #include <String.h>
51 #include <TypeConstants.h>
52 #include <View.h>
54 #include "AudioProducer.h"
55 #include "ControllerObserver.h"
56 #include "DurationToString.h"
57 #include "FilePlaylistItem.h"
58 #include "MainApp.h"
59 #include "NetworkStreamWin.h"
60 #include "PeakView.h"
61 #include "PlaylistItem.h"
62 #include "PlaylistObserver.h"
63 #include "PlaylistWindow.h"
64 #include "Settings.h"
67 #undef B_TRANSLATION_CONTEXT
68 #define B_TRANSLATION_CONTEXT "MediaPlayer-Main"
69 #define MIN_WIDTH 250
72 int MainWin::sNoVideoWidth = MIN_WIDTH;
75 // XXX TODO: why is lround not defined?
76 #define lround(a) ((int)(0.99999 + (a)))
78 enum {
79 M_DUMMY = 0x100,
80 M_FILE_OPEN = 0x1000,
81 M_NETWORK_STREAM_OPEN,
82 M_FILE_INFO,
83 M_FILE_PLAYLIST,
84 M_FILE_CLOSE,
85 M_FILE_QUIT,
86 M_VIEW_SIZE,
87 M_TOGGLE_FULLSCREEN,
88 M_TOGGLE_ALWAYS_ON_TOP,
89 M_TOGGLE_NO_INTERFACE,
90 M_VOLUME_UP,
91 M_VOLUME_DOWN,
92 M_SKIP_NEXT,
93 M_SKIP_PREV,
94 M_WIND,
96 // The common display aspect ratios
97 M_ASPECT_SAME_AS_SOURCE,
98 M_ASPECT_NO_DISTORTION,
99 M_ASPECT_4_3,
100 M_ASPECT_16_9,
101 M_ASPECT_83_50,
102 M_ASPECT_7_4,
103 M_ASPECT_37_20,
104 M_ASPECT_47_20,
106 M_SELECT_AUDIO_TRACK = 0x00000800,
107 M_SELECT_AUDIO_TRACK_END = 0x00000fff,
108 M_SELECT_VIDEO_TRACK = 0x00010000,
109 M_SELECT_VIDEO_TRACK_END = 0x00010fff,
110 M_SELECT_SUB_TITLE_TRACK = 0x00020000,
111 M_SELECT_SUB_TITLE_TRACK_END = 0x00020fff,
113 M_SET_RATING,
115 M_SET_PLAYLIST_POSITION,
117 M_FILE_DELETE,
119 M_SLIDE_CONTROLS,
120 M_FINISH_SLIDING_CONTROLS
124 static property_info sPropertyInfo[] = {
125 { B_TRANSLATE("Next"), { B_EXECUTE_PROPERTY },
126 { B_DIRECT_SPECIFIER, 0 },
127 B_TRANSLATE("Skip to the next track."), 0
129 { B_TRANSLATE("Prev"), { B_EXECUTE_PROPERTY },
130 { B_DIRECT_SPECIFIER, 0 },
131 B_TRANSLATE("Skip to the previous track."), 0
133 { B_TRANSLATE("Play"), { B_EXECUTE_PROPERTY },
134 { B_DIRECT_SPECIFIER, 0 },
135 B_TRANSLATE("Start playing."), 0
137 { B_TRANSLATE("Stop"), { B_EXECUTE_PROPERTY },
138 { B_DIRECT_SPECIFIER, 0 },
139 B_TRANSLATE("Stop playing."), 0
141 { B_TRANSLATE("Pause"), { B_EXECUTE_PROPERTY },
142 { B_DIRECT_SPECIFIER, 0 },
143 B_TRANSLATE("Pause playback."), 0
145 { B_TRANSLATE("TogglePlaying"), { B_EXECUTE_PROPERTY },
146 { B_DIRECT_SPECIFIER, 0 },
147 B_TRANSLATE("Toggle pause/play."), 0
149 { B_TRANSLATE("Mute"), { B_EXECUTE_PROPERTY },
150 { B_DIRECT_SPECIFIER, 0 },
151 B_TRANSLATE("Toggle mute."), 0
153 { B_TRANSLATE("Volume"), { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
154 { B_DIRECT_SPECIFIER, 0 },
155 B_TRANSLATE("Gets/sets the volume (0.0-2.0)."), 0,
156 { B_FLOAT_TYPE }
158 { B_TRANSLATE("URI"), { B_GET_PROPERTY, 0 },
159 { B_DIRECT_SPECIFIER, 0 },
160 B_TRANSLATE("Gets the URI of the currently playing item."), 0,
161 { B_STRING_TYPE }
163 { B_TRANSLATE("ToggleFullscreen"), { B_EXECUTE_PROPERTY },
164 { B_DIRECT_SPECIFIER, 0 },
165 B_TRANSLATE("Toggle fullscreen."), 0
167 { B_TRANSLATE("Duration"), { B_GET_PROPERTY, 0 },
168 { B_DIRECT_SPECIFIER, 0 },
169 B_TRANSLATE("Gets the duration of the currently playing item "
170 "in microseconds."), 0,
171 { B_INT64_TYPE }
173 { B_TRANSLATE("Position"), { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
174 { B_DIRECT_SPECIFIER, 0 },
175 B_TRANSLATE("Gets/sets the current playing position in microseconds."),
176 0, { B_INT64_TYPE }
178 { B_TRANSLATE("Seek"), { B_SET_PROPERTY },
179 { B_DIRECT_SPECIFIER, 0 },
180 B_TRANSLATE("Seek by the specified amounts of microseconds."), 0,
181 { B_INT64_TYPE }
184 { 0 }
188 static const char* kRatingAttrName = "Media:Rating";
190 static const char* kDisabledSeekMessage = B_TRANSLATE("Drop files to play");
192 static const char* kApplicationName = B_TRANSLATE_SYSTEM_NAME(NAME);
195 MainWin::MainWin(bool isFirstWindow, BMessage* message)
197 BWindow(BRect(100, 100, 400, 300), kApplicationName, B_TITLED_WINDOW,
198 B_ASYNCHRONOUS_CONTROLS),
199 fCreationTime(system_time()),
200 fInfoWin(NULL),
201 fPlaylistWindow(NULL),
202 fHasFile(false),
203 fHasVideo(false),
204 fHasAudio(false),
205 fPlaylist(new Playlist),
206 fPlaylistObserver(new PlaylistObserver(this)),
207 fController(new Controller),
208 fControllerObserver(new ControllerObserver(this,
209 OBSERVE_FILE_CHANGES | OBSERVE_TRACK_CHANGES
210 | OBSERVE_PLAYBACK_STATE_CHANGES | OBSERVE_POSITION_CHANGES
211 | OBSERVE_VOLUME_CHANGES)),
212 fIsFullscreen(false),
213 fAlwaysOnTop(false),
214 fNoInterface(false),
215 fShowsFullscreenControls(false),
216 fSourceWidth(-1),
217 fSourceHeight(-1),
218 fWidthAspect(0),
219 fHeightAspect(0),
220 fSavedFrame(),
221 fNoVideoFrame(),
223 fMouseDownTracking(false),
224 fLastMousePos(0, 0),
225 fLastMouseMovedTime(system_time()),
226 fMouseMoveDist(0),
228 fGlobalSettingsListener(this),
229 fInitialSeekPosition(0),
230 fAllowWinding(true)
232 // Handle window position and size depending on whether this is the
233 // first window or not. Use the window size from the window that was
234 // last resized by the user.
235 static int pos = 0;
236 MoveBy(pos * 25, pos * 25);
237 pos = (pos + 1) % 15;
239 BRect frame = Settings::Default()->AudioPlayerWindowFrame();
240 if (frame.IsValid()) {
241 if (isFirstWindow) {
242 if (message == NULL) {
243 MoveTo(frame.LeftTop());
244 ResizeTo(frame.Width(), frame.Height());
245 } else {
246 // Delay moving to the initial position, since we don't
247 // know if we will be playing audio at all.
248 message->AddRect("window frame", frame);
251 if (sNoVideoWidth == MIN_WIDTH)
252 sNoVideoWidth = frame.IntegerWidth();
253 } else if (sNoVideoWidth > MIN_WIDTH) {
254 ResizeTo(sNoVideoWidth, Bounds().Height());
256 fNoVideoWidth = sNoVideoWidth;
258 BRect rect = Bounds();
260 // background
261 fBackground = new BView(rect, "background", B_FOLLOW_ALL,
262 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
263 fBackground->SetViewColor(0, 0, 0);
264 AddChild(fBackground);
266 // menu
267 fMenuBar = new BMenuBar(fBackground->Bounds(), "menu");
268 _CreateMenu();
269 fBackground->AddChild(fMenuBar);
270 fMenuBar->SetResizingMode(B_FOLLOW_NONE);
271 fMenuBar->ResizeToPreferred();
272 fMenuBarWidth = (int)fMenuBar->Frame().Width() + 1;
273 fMenuBarHeight = (int)fMenuBar->Frame().Height() + 1;
275 // video view
276 rect = BRect(0, fMenuBarHeight, fBackground->Bounds().right,
277 fMenuBarHeight + 10);
278 fVideoView = new VideoView(rect, "video display", B_FOLLOW_NONE);
279 fBackground->AddChild(fVideoView);
281 // controls
282 rect = BRect(0, fMenuBarHeight + 11, fBackground->Bounds().right,
283 fBackground->Bounds().bottom);
284 fControls = new ControllerView(rect, fController, fPlaylist);
285 fBackground->AddChild(fControls);
286 fControls->ResizeToPreferred();
287 fControlsHeight = (int)fControls->Frame().Height() + 1;
288 fControlsWidth = (int)fControls->Frame().Width() + 1;
289 fControls->SetResizingMode(B_FOLLOW_BOTTOM | B_FOLLOW_LEFT_RIGHT);
290 fControls->SetDisabledString(kDisabledSeekMessage);
292 fPlaylist->AddListener(fPlaylistObserver);
293 fController->SetVideoView(fVideoView);
294 fController->AddListener(fControllerObserver);
295 PeakView* peakView = fControls->GetPeakView();
296 peakView->SetPeakNotificationWhat(MSG_PEAK_NOTIFICATION);
297 fController->SetPeakListener(peakView);
299 _SetupWindow();
301 // setup the playlist window now, we need to have it
302 // running for the undo/redo playlist editing
303 fPlaylistWindow = new PlaylistWindow(BRect(150, 150, 500, 600), fPlaylist,
304 fController);
305 fPlaylistWindow->Hide();
306 fPlaylistWindow->Show();
307 // this makes sure the window thread is running without
308 // showing the window just yet
310 Settings::Default()->AddListener(&fGlobalSettingsListener);
311 _AdoptGlobalSettings();
313 AddShortcut('z', B_COMMAND_KEY, new BMessage(B_UNDO));
314 AddShortcut('y', B_COMMAND_KEY, new BMessage(B_UNDO));
315 AddShortcut('z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO));
316 AddShortcut('y', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO));
318 Hide();
319 Show();
321 if (message != NULL)
322 PostMessage(message);
324 BMediaRoster* roster = BMediaRoster::Roster();
325 roster->StartWatching(BMessenger(this, this), B_MEDIA_SERVER_STARTED);
326 roster->StartWatching(BMessenger(this, this), B_MEDIA_SERVER_QUIT);
330 MainWin::~MainWin()
332 // printf("MainWin::~MainWin\n");
334 BMediaRoster* roster = BMediaRoster::CurrentRoster();
335 roster->StopWatching(BMessenger(this, this), B_MEDIA_SERVER_STARTED);
336 roster->StopWatching(BMessenger(this, this), B_MEDIA_SERVER_QUIT);
338 Settings::Default()->RemoveListener(&fGlobalSettingsListener);
339 fPlaylist->RemoveListener(fPlaylistObserver);
340 fController->Lock();
341 fController->RemoveListener(fControllerObserver);
342 fController->SetPeakListener(NULL);
343 fController->SetVideoTarget(NULL);
344 fController->Unlock();
346 // give the views a chance to detach from any notifiers
347 // before we delete them
348 fBackground->RemoveSelf();
349 delete fBackground;
351 if (fInfoWin && fInfoWin->Lock())
352 fInfoWin->Quit();
354 if (fPlaylistWindow && fPlaylistWindow->Lock())
355 fPlaylistWindow->Quit();
357 delete fPlaylist;
358 fPlaylist = NULL;
360 // quit the Controller looper thread
361 thread_id controllerThread = fController->Thread();
362 fController->PostMessage(B_QUIT_REQUESTED);
363 status_t exitValue;
364 wait_for_thread(controllerThread, &exitValue);
368 // #pragma mark -
371 void
372 MainWin::FrameResized(float newWidth, float newHeight)
374 if (newWidth != Bounds().Width() || newHeight != Bounds().Height()) {
375 debugger("size wrong\n");
378 bool noMenu = fNoInterface || fIsFullscreen;
379 bool noControls = fNoInterface || fIsFullscreen;
381 // printf("FrameResized enter: newWidth %.0f, newHeight %.0f\n",
382 // newWidth, newHeight);
384 if (!fHasVideo)
385 sNoVideoWidth = fNoVideoWidth = (int)newWidth;
387 int maxVideoWidth = int(newWidth) + 1;
388 int maxVideoHeight = int(newHeight) + 1
389 - (noMenu ? 0 : fMenuBarHeight)
390 - (noControls ? 0 : fControlsHeight);
392 ASSERT(maxVideoHeight >= 0);
394 int y = 0;
396 if (noMenu) {
397 if (!fMenuBar->IsHidden(fMenuBar))
398 fMenuBar->Hide();
399 } else {
400 fMenuBar->MoveTo(0, y);
401 fMenuBar->ResizeTo(newWidth, fMenuBarHeight - 1);
402 if (fMenuBar->IsHidden(fMenuBar))
403 fMenuBar->Show();
404 y += fMenuBarHeight;
407 if (maxVideoHeight == 0) {
408 if (!fVideoView->IsHidden(fVideoView))
409 fVideoView->Hide();
410 } else {
411 _ResizeVideoView(0, y, maxVideoWidth, maxVideoHeight);
412 if (fVideoView->IsHidden(fVideoView))
413 fVideoView->Show();
414 y += maxVideoHeight;
417 if (noControls) {
418 if (!fControls->IsHidden(fControls))
419 fControls->Hide();
420 } else {
421 fControls->MoveTo(0, y);
422 fControls->ResizeTo(newWidth, fControlsHeight - 1);
423 if (fControls->IsHidden(fControls))
424 fControls->Show();
425 // y += fControlsHeight;
428 // printf("FrameResized leave\n");
432 void
433 MainWin::Zoom(BPoint /*position*/, float /*width*/, float /*height*/)
435 PostMessage(M_TOGGLE_FULLSCREEN);
439 void
440 MainWin::DispatchMessage(BMessage* msg, BHandler* handler)
442 if ((msg->what == B_MOUSE_DOWN)
443 && (handler == fBackground || handler == fVideoView
444 || handler == fControls)) {
445 _MouseDown(msg, dynamic_cast<BView*>(handler));
448 if ((msg->what == B_MOUSE_MOVED)
449 && (handler == fBackground || handler == fVideoView
450 || handler == fControls)) {
451 _MouseMoved(msg, dynamic_cast<BView*>(handler));
454 if ((msg->what == B_MOUSE_UP)
455 && (handler == fBackground || handler == fVideoView)) {
456 _MouseUp(msg);
459 if ((msg->what == B_KEY_DOWN)
460 && (handler == fBackground || handler == fVideoView)) {
461 // special case for PrintScreen key
462 if (msg->FindInt32("key") == B_PRINT_KEY) {
463 fVideoView->OverlayScreenshotPrepare();
464 BWindow::DispatchMessage(msg, handler);
465 fVideoView->OverlayScreenshotCleanup();
466 return;
469 // every other key gets dispatched to our _KeyDown first
470 if (_KeyDown(msg)) {
471 // it got handled, don't pass it on
472 return;
476 BWindow::DispatchMessage(msg, handler);
480 void
481 MainWin::MessageReceived(BMessage* msg)
483 // msg->PrintToStream();
484 switch (msg->what) {
485 case B_EXECUTE_PROPERTY:
486 case B_GET_PROPERTY:
487 case B_SET_PROPERTY:
489 BMessage reply(B_REPLY);
490 status_t result = B_BAD_SCRIPT_SYNTAX;
491 int32 index;
492 BMessage specifier;
493 int32 what;
494 const char* property;
496 if (msg->GetCurrentSpecifier(&index, &specifier, &what,
497 &property) != B_OK) {
498 return BWindow::MessageReceived(msg);
501 BPropertyInfo propertyInfo(sPropertyInfo);
502 switch (propertyInfo.FindMatch(msg, index, &specifier, what,
503 property)) {
504 case 0:
505 fControls->SkipForward();
506 result = B_OK;
507 break;
509 case 1:
510 fControls->SkipBackward();
511 result = B_OK;
512 break;
514 case 2:
515 fController->Play();
516 result = B_OK;
517 break;
519 case 3:
520 fController->Stop();
521 result = B_OK;
522 break;
524 case 4:
525 fController->Pause();
526 result = B_OK;
527 break;
529 case 5:
530 fController->TogglePlaying();
531 result = B_OK;
532 break;
534 case 6:
535 fController->ToggleMute();
536 result = B_OK;
537 break;
539 case 7:
541 if (msg->what == B_GET_PROPERTY) {
542 result = reply.AddFloat("result",
543 fController->Volume());
544 } else if (msg->what == B_SET_PROPERTY) {
545 float newVolume;
546 result = msg->FindFloat("data", &newVolume);
547 if (result == B_OK)
548 fController->SetVolume(newVolume);
550 break;
553 case 8:
555 if (msg->what == B_GET_PROPERTY) {
556 BAutolock _(fPlaylist);
557 const PlaylistItem* item = fController->Item();
558 if (item == NULL) {
559 result = B_NO_INIT;
560 break;
563 result = reply.AddString("result", item->LocationURI());
565 break;
568 case 9:
569 PostMessage(M_TOGGLE_FULLSCREEN);
570 break;
572 case 10:
573 if (msg->what != B_GET_PROPERTY)
574 break;
576 result = reply.AddInt64("result",
577 fController->TimeDuration());
578 break;
580 case 11:
582 if (msg->what == B_GET_PROPERTY) {
583 result = reply.AddInt64("result",
584 fController->TimePosition());
585 } else if (msg->what == B_SET_PROPERTY) {
586 int64 newTime;
587 result = msg->FindInt64("data", &newTime);
588 if (result == B_OK)
589 fController->SetTimePosition(newTime);
592 break;
595 case 12:
597 if (msg->what != B_SET_PROPERTY)
598 break;
600 bigtime_t seekBy;
601 result = msg->FindInt64("data", &seekBy);
602 if (result != B_OK)
603 break;
605 _Wind(seekBy, 0);
606 break;
609 default:
610 return BWindow::MessageReceived(msg);
613 if (result != B_OK) {
614 reply.what = B_MESSAGE_NOT_UNDERSTOOD;
615 reply.AddString("message", strerror(result));
616 reply.AddInt32("error", result);
619 msg->SendReply(&reply);
620 break;
623 case B_REFS_RECEIVED:
624 case M_URL_RECEIVED:
625 _RefsReceived(msg);
626 break;
628 case B_SIMPLE_DATA:
629 if (msg->HasRef("refs"))
630 _RefsReceived(msg);
631 break;
632 case M_OPEN_PREVIOUS_PLAYLIST:
633 OpenPlaylist(msg);
634 break;
636 case B_UNDO:
637 case B_REDO:
638 fPlaylistWindow->PostMessage(msg);
639 break;
641 case B_MEDIA_SERVER_STARTED:
643 printf("TODO: implement B_MEDIA_SERVER_STARTED\n");
645 // BAutolock _(fPlaylist);
646 // BMessage fakePlaylistMessage(MSG_PLAYLIST_CURRENT_ITEM_CHANGED);
647 // fakePlaylistMessage.AddInt32("index",
648 // fPlaylist->CurrentItemIndex());
649 // PostMessage(&fakePlaylistMessage);
650 break;
653 case B_MEDIA_SERVER_QUIT:
654 printf("TODO: implement B_MEDIA_SERVER_QUIT\n");
655 // if (fController->Lock()) {
656 // fController->CleanupNodes();
657 // fController->Unlock();
658 // }
659 break;
661 // PlaylistObserver messages
662 case MSG_PLAYLIST_ITEM_ADDED:
664 PlaylistItem* item;
665 int32 index;
666 if (msg->FindPointer("item", (void**)&item) == B_OK
667 && msg->FindInt32("index", &index) == B_OK) {
668 _AddPlaylistItem(item, index);
670 break;
672 case MSG_PLAYLIST_ITEM_REMOVED:
674 int32 index;
675 if (msg->FindInt32("index", &index) == B_OK)
676 _RemovePlaylistItem(index);
677 break;
679 case MSG_PLAYLIST_CURRENT_ITEM_CHANGED:
681 BAutolock _(fPlaylist);
683 int32 index;
684 // if false, the message was meant to only update the GUI
685 bool play;
686 if (msg->FindBool("play", &play) < B_OK || !play)
687 break;
688 if (msg->FindInt32("index", &index) < B_OK
689 || index != fPlaylist->CurrentItemIndex())
690 break;
691 PlaylistItemRef item(fPlaylist->ItemAt(index));
692 if (item.Get() != NULL) {
693 printf("open playlist item: %s\n", item->Name().String());
694 OpenPlaylistItem(item);
695 _MarkPlaylistItem(index);
697 break;
699 case MSG_PLAYLIST_IMPORT_FAILED:
701 BAlert* alert = new BAlert(B_TRANSLATE("Nothing to Play"),
702 B_TRANSLATE("None of the files you wanted to play appear "
703 "to be media files."), B_TRANSLATE("OK"));
704 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
705 alert->Go();
706 fControls->SetDisabledString(kDisabledSeekMessage);
707 break;
710 // ControllerObserver messages
711 case MSG_CONTROLLER_FILE_FINISHED:
713 BAutolock _(fPlaylist);
715 bool hadNext = fPlaylist->SetCurrentItemIndex(
716 fPlaylist->CurrentItemIndex() + 1);
717 if (!hadNext) {
718 // Reached end of playlist
719 // Handle "quit when done" settings
720 if ((fHasVideo && fCloseWhenDonePlayingMovie)
721 || (!fHasVideo && fCloseWhenDonePlayingSound))
722 PostMessage(B_QUIT_REQUESTED);
723 // Handle "loop by default" settings
724 if ((fHasVideo && fLoopMovies)
725 || (!fHasVideo && fLoopSounds)) {
726 if (fPlaylist->CountItems() > 1)
727 fPlaylist->SetCurrentItemIndex(0);
728 else
729 fController->Play();
732 break;
734 case MSG_CONTROLLER_FILE_CHANGED:
736 status_t result = B_ERROR;
737 msg->FindInt32("result", &result);
738 PlaylistItemRef itemRef;
739 PlaylistItem* item;
740 if (msg->FindPointer("item", (void**)&item) == B_OK) {
741 itemRef.SetTo(item, true);
742 // The reference was passed along with the message.
743 } else {
744 BAutolock _(fPlaylist);
745 itemRef.SetTo(fPlaylist->ItemAt(
746 fPlaylist->CurrentItemIndex()));
748 _PlaylistItemOpened(itemRef, result);
749 break;
751 case MSG_CONTROLLER_VIDEO_TRACK_CHANGED:
753 int32 index;
754 if (msg->FindInt32("index", &index) == B_OK) {
755 int32 i = 0;
756 while (BMenuItem* item = fVideoTrackMenu->ItemAt(i)) {
757 item->SetMarked(i == index);
758 i++;
761 break;
763 case MSG_CONTROLLER_AUDIO_TRACK_CHANGED:
765 int32 index;
766 if (msg->FindInt32("index", &index) == B_OK) {
767 int32 i = 0;
768 while (BMenuItem* item = fAudioTrackMenu->ItemAt(i)) {
769 item->SetMarked(i == index);
770 i++;
772 _UpdateAudioChannelCount(index);
774 break;
776 case MSG_CONTROLLER_SUB_TITLE_TRACK_CHANGED:
778 int32 index;
779 if (msg->FindInt32("index", &index) == B_OK) {
780 int32 i = 0;
781 while (BMenuItem* item = fSubTitleTrackMenu->ItemAt(i)) {
782 BMessage* message = item->Message();
783 if (message != NULL) {
784 item->SetMarked((int32)message->what
785 - M_SELECT_SUB_TITLE_TRACK == index);
787 i++;
790 break;
792 case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED:
794 uint32 state;
795 if (msg->FindInt32("state", (int32*)&state) == B_OK)
796 fControls->SetPlaybackState(state);
797 break;
799 case MSG_CONTROLLER_POSITION_CHANGED:
801 float position;
802 if (msg->FindFloat("position", &position) == B_OK) {
803 fControls->SetPosition(position, fController->TimePosition(),
804 fController->TimeDuration());
805 fAllowWinding = true;
807 break;
809 case MSG_CONTROLLER_SEEK_HANDLED:
810 break;
812 case MSG_CONTROLLER_VOLUME_CHANGED:
814 float volume;
815 if (msg->FindFloat("volume", &volume) == B_OK)
816 fControls->SetVolume(volume);
817 break;
819 case MSG_CONTROLLER_MUTED_CHANGED:
821 bool muted;
822 if (msg->FindBool("muted", &muted) == B_OK)
823 fControls->SetMuted(muted);
824 break;
827 // menu item messages
828 case M_FILE_OPEN:
830 BMessenger target(this);
831 BMessage result(B_REFS_RECEIVED);
832 BMessage appMessage(M_SHOW_OPEN_PANEL);
833 appMessage.AddMessenger("target", target);
834 appMessage.AddMessage("message", &result);
835 appMessage.AddString("title", B_TRANSLATE("Open clips"));
836 appMessage.AddString("label", B_TRANSLATE("Open"));
837 be_app->PostMessage(&appMessage);
838 break;
841 case M_NETWORK_STREAM_OPEN:
843 BMessenger target(this);
844 NetworkStreamWin* win = new NetworkStreamWin(target);
845 win->Show();
846 break;
849 case M_FILE_INFO:
850 ShowFileInfo();
851 break;
852 case M_FILE_PLAYLIST:
853 ShowPlaylistWindow();
854 break;
855 case M_FILE_CLOSE:
856 PostMessage(B_QUIT_REQUESTED);
857 break;
858 case M_FILE_QUIT:
859 be_app->PostMessage(B_QUIT_REQUESTED);
860 break;
862 case M_TOGGLE_FULLSCREEN:
863 _ToggleFullscreen();
864 break;
866 case M_TOGGLE_ALWAYS_ON_TOP:
867 _ToggleAlwaysOnTop();
868 break;
870 case M_TOGGLE_NO_INTERFACE:
871 _ToggleNoInterface();
872 break;
874 case M_VIEW_SIZE:
876 int32 size;
877 if (msg->FindInt32("size", &size) == B_OK) {
878 if (!fHasVideo)
879 break;
880 if (fIsFullscreen)
881 _ToggleFullscreen();
882 _ResizeWindow(size);
884 break;
888 case B_ACQUIRE_OVERLAY_LOCK:
889 printf("B_ACQUIRE_OVERLAY_LOCK\n");
890 fVideoView->OverlayLockAcquire();
891 break;
893 case B_RELEASE_OVERLAY_LOCK:
894 printf("B_RELEASE_OVERLAY_LOCK\n");
895 fVideoView->OverlayLockRelease();
896 break;
898 case B_MOUSE_WHEEL_CHANGED:
900 float dx = msg->FindFloat("be:wheel_delta_x");
901 float dy = msg->FindFloat("be:wheel_delta_y");
902 bool inv = modifiers() & B_COMMAND_KEY;
903 if (dx > 0.1)
904 PostMessage(inv ? M_VOLUME_DOWN : M_SKIP_PREV);
905 if (dx < -0.1)
906 PostMessage(inv ? M_VOLUME_UP : M_SKIP_NEXT);
907 if (dy > 0.1)
908 PostMessage(inv ? M_SKIP_PREV : M_VOLUME_DOWN);
909 if (dy < -0.1)
910 PostMessage(inv ? M_SKIP_NEXT : M_VOLUME_UP);
911 break;
914 case M_SKIP_NEXT:
915 fControls->SkipForward();
916 break;
918 case M_SKIP_PREV:
919 fControls->SkipBackward();
920 break;
922 case M_WIND:
924 bigtime_t howMuch;
925 int64 frames;
926 if (msg->FindInt64("how much", &howMuch) != B_OK
927 || msg->FindInt64("frames", &frames) != B_OK) {
928 break;
931 _Wind(howMuch, frames);
932 break;
935 case M_VOLUME_UP:
936 fController->VolumeUp();
937 break;
939 case M_VOLUME_DOWN:
940 fController->VolumeDown();
941 break;
943 case M_ASPECT_SAME_AS_SOURCE:
944 if (fHasVideo) {
945 int width;
946 int height;
947 int widthAspect;
948 int heightAspect;
949 fController->GetSize(&width, &height,
950 &widthAspect, &heightAspect);
951 VideoFormatChange(width, height, widthAspect, heightAspect);
953 break;
955 case M_ASPECT_NO_DISTORTION:
956 if (fHasVideo) {
957 int width;
958 int height;
959 fController->GetSize(&width, &height);
960 VideoFormatChange(width, height, width, height);
962 break;
964 case M_ASPECT_4_3:
965 VideoAspectChange(4, 3);
966 break;
968 case M_ASPECT_16_9: // 1.77 : 1
969 VideoAspectChange(16, 9);
970 break;
972 case M_ASPECT_83_50: // 1.66 : 1
973 VideoAspectChange(83, 50);
974 break;
976 case M_ASPECT_7_4: // 1.75 : 1
977 VideoAspectChange(7, 4);
978 break;
980 case M_ASPECT_37_20: // 1.85 : 1
981 VideoAspectChange(37, 20);
982 break;
984 case M_ASPECT_47_20: // 2.35 : 1
985 VideoAspectChange(47, 20);
986 break;
988 case M_SET_PLAYLIST_POSITION:
990 BAutolock _(fPlaylist);
992 int32 index;
993 if (msg->FindInt32("index", &index) == B_OK)
994 fPlaylist->SetCurrentItemIndex(index);
995 break;
998 case MSG_OBJECT_CHANGED:
999 // received from fGlobalSettingsListener
1000 // TODO: find out which object, if we ever watch more than
1001 // the global settings instance...
1002 _AdoptGlobalSettings();
1003 break;
1005 case M_SLIDE_CONTROLS:
1007 float offset;
1008 if (msg->FindFloat("offset", &offset) == B_OK) {
1009 fControls->MoveBy(0, offset);
1010 fVideoView->SetSubTitleMaxBottom(fControls->Frame().top - 1);
1011 UpdateIfNeeded();
1012 snooze(15000);
1014 break;
1016 case M_FINISH_SLIDING_CONTROLS:
1018 float offset;
1019 bool show;
1020 if (msg->FindFloat("offset", &offset) == B_OK
1021 && msg->FindBool("show", &show) == B_OK) {
1022 if (show) {
1023 fControls->MoveTo(fControls->Frame().left, offset);
1024 fVideoView->SetSubTitleMaxBottom(offset - 1);
1025 } else {
1026 fVideoView->SetSubTitleMaxBottom(
1027 fVideoView->Bounds().bottom);
1028 fControls->RemoveSelf();
1029 fControls->MoveTo(fVideoView->Frame().left,
1030 fVideoView->Frame().bottom + 1);
1031 fBackground->AddChild(fControls);
1032 fControls->SetSymbolScale(1.0f);
1033 while (!fControls->IsHidden())
1034 fControls->Hide();
1037 break;
1039 case M_HIDE_FULL_SCREEN_CONTROLS:
1040 if (fIsFullscreen) {
1041 BPoint videoViewWhere;
1042 if (msg->FindPoint("where", &videoViewWhere) == B_OK) {
1043 if (msg->FindBool("force")
1044 || !fControls->Frame().Contains(videoViewWhere)) {
1045 _ShowFullscreenControls(false);
1046 // hide the mouse cursor until the user moves it
1047 be_app->ObscureCursor();
1051 break;
1053 case M_SET_RATING:
1055 int32 rating;
1056 if (msg->FindInt32("rating", &rating) == B_OK)
1057 _SetRating(rating);
1058 break;
1061 default:
1062 if (msg->what >= M_SELECT_AUDIO_TRACK
1063 && msg->what <= M_SELECT_AUDIO_TRACK_END) {
1064 fController->SelectAudioTrack(msg->what - M_SELECT_AUDIO_TRACK);
1065 break;
1067 if (msg->what >= M_SELECT_VIDEO_TRACK
1068 && msg->what <= M_SELECT_VIDEO_TRACK_END) {
1069 fController->SelectVideoTrack(msg->what - M_SELECT_VIDEO_TRACK);
1070 break;
1072 if ((int32)msg->what >= M_SELECT_SUB_TITLE_TRACK - 1
1073 && msg->what <= M_SELECT_SUB_TITLE_TRACK_END) {
1074 fController->SelectSubTitleTrack((int32)msg->what
1075 - M_SELECT_SUB_TITLE_TRACK);
1076 break;
1078 // let BWindow handle the rest
1079 BWindow::MessageReceived(msg);
1084 void
1085 MainWin::WindowActivated(bool active)
1087 fController->PlayerActivated(active);
1091 bool
1092 MainWin::QuitRequested()
1094 BMessage message(M_PLAYER_QUIT);
1095 GetQuitMessage(&message);
1096 be_app->PostMessage(&message);
1097 return true;
1101 void
1102 MainWin::MenusBeginning()
1104 _SetupVideoAspectItems(fVideoAspectMenu);
1108 // #pragma mark -
1111 void
1112 MainWin::OpenPlaylist(const BMessage* playlistArchive)
1114 if (playlistArchive == NULL)
1115 return;
1117 BAutolock _(this);
1118 BAutolock playlistLocker(fPlaylist);
1120 if (fPlaylist->Unarchive(playlistArchive) != B_OK)
1121 return;
1123 int32 currentIndex;
1124 if (playlistArchive->FindInt32("index", &currentIndex) != B_OK)
1125 currentIndex = 0;
1126 fPlaylist->SetCurrentItemIndex(currentIndex);
1128 playlistLocker.Unlock();
1130 if (currentIndex != -1) {
1131 // Restore the current play position only if we have something to play
1132 playlistArchive->FindInt64("position", (int64*)&fInitialSeekPosition);
1135 if (IsHidden())
1136 Show();
1140 void
1141 MainWin::OpenPlaylistItem(const PlaylistItemRef& item)
1143 status_t ret = fController->SetToAsync(item);
1144 if (ret != B_OK) {
1145 fprintf(stderr, "MainWin::OpenPlaylistItem() - Failed to send message "
1146 "to Controller.\n");
1147 BString message = B_TRANSLATE("%app% encountered an internal error. "
1148 "The file could not be opened.");
1149 message.ReplaceFirst("%app%", kApplicationName);
1150 BAlert* alert = new BAlert(kApplicationName, message.String(),
1151 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1152 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1153 alert->Go();
1154 _PlaylistItemOpened(item, ret);
1155 } else {
1156 BString string;
1157 string << "Opening '" << item->Name() << "'.";
1158 fControls->SetDisabledString(string.String());
1163 void
1164 MainWin::ShowFileInfo()
1166 if (!fInfoWin)
1167 fInfoWin = new InfoWin(Frame().LeftTop(), fController);
1169 if (fInfoWin->Lock()) {
1170 if (fInfoWin->IsHidden())
1171 fInfoWin->Show();
1172 else
1173 fInfoWin->Activate();
1174 fInfoWin->Unlock();
1179 void
1180 MainWin::ShowPlaylistWindow()
1182 if (fPlaylistWindow->Lock()) {
1183 // make sure the window shows on the same workspace as ourself
1184 uint32 workspaces = Workspaces();
1185 if (fPlaylistWindow->Workspaces() != workspaces)
1186 fPlaylistWindow->SetWorkspaces(workspaces);
1188 // show or activate
1189 if (fPlaylistWindow->IsHidden())
1190 fPlaylistWindow->Show();
1191 else
1192 fPlaylistWindow->Activate();
1194 fPlaylistWindow->Unlock();
1199 void
1200 MainWin::VideoAspectChange(int forcedWidth, int forcedHeight, float widthScale)
1202 // Force specific source size and pixel width scale.
1203 if (fHasVideo) {
1204 int width;
1205 int height;
1206 fController->GetSize(&width, &height);
1207 VideoFormatChange(forcedWidth, forcedHeight,
1208 lround(width * widthScale), height);
1213 void
1214 MainWin::VideoAspectChange(float widthScale)
1216 // Called when video aspect ratio changes and the original
1217 // width/height should be restored too, display aspect is not known,
1218 // only pixel width scale.
1219 if (fHasVideo) {
1220 int width;
1221 int height;
1222 fController->GetSize(&width, &height);
1223 VideoFormatChange(width, height, lround(width * widthScale), height);
1228 void
1229 MainWin::VideoAspectChange(int widthAspect, int heightAspect)
1231 // Called when video aspect ratio changes and the original
1232 // width/height should be restored too.
1233 if (fHasVideo) {
1234 int width;
1235 int height;
1236 fController->GetSize(&width, &height);
1237 VideoFormatChange(width, height, widthAspect, heightAspect);
1242 void
1243 MainWin::VideoFormatChange(int width, int height, int widthAspect,
1244 int heightAspect)
1246 // Called when video format or aspect ratio changes.
1248 printf("VideoFormatChange enter: width %d, height %d, "
1249 "aspect ratio: %d:%d\n", width, height, widthAspect, heightAspect);
1251 // remember current view scale
1252 int percent = _CurrentVideoSizeInPercent();
1254 fSourceWidth = width;
1255 fSourceHeight = height;
1256 fWidthAspect = widthAspect;
1257 fHeightAspect = heightAspect;
1259 if (percent == 100)
1260 _ResizeWindow(100);
1261 else
1262 FrameResized(Bounds().Width(), Bounds().Height());
1264 printf("VideoFormatChange leave\n");
1268 void
1269 MainWin::GetQuitMessage(BMessage* message)
1271 message->AddPointer("instance", this);
1272 message->AddRect("window frame", Frame());
1273 message->AddBool("audio only", !fHasVideo);
1274 message->AddInt64("creation time", fCreationTime);
1276 if (!fHasVideo && fHasAudio) {
1277 // store playlist, current index and position if this is audio
1278 BMessage playlistArchive;
1280 BAutolock controllerLocker(fController);
1281 playlistArchive.AddInt64("position", fController->TimePosition());
1282 controllerLocker.Unlock();
1284 if (!fPlaylist)
1285 return;
1287 BAutolock playlistLocker(fPlaylist);
1288 if (fPlaylist->Archive(&playlistArchive) != B_OK
1289 || playlistArchive.AddInt32("index",
1290 fPlaylist->CurrentItemIndex()) != B_OK
1291 || message->AddMessage("playlist", &playlistArchive) != B_OK) {
1292 fprintf(stderr, "Failed to store current playlist.\n");
1298 BHandler*
1299 MainWin::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
1300 int32 what, const char* property)
1302 BPropertyInfo propertyInfo(sPropertyInfo);
1303 switch (propertyInfo.FindMatch(message, index, specifier, what, property)) {
1304 case 0:
1305 case 1:
1306 case 2:
1307 case 3:
1308 case 4:
1309 case 5:
1310 case 6:
1311 case 7:
1312 case 8:
1313 case 9:
1314 case 10:
1315 case 11:
1316 case 12:
1317 return this;
1320 return BWindow::ResolveSpecifier(message, index, specifier, what, property);
1324 status_t
1325 MainWin::GetSupportedSuites(BMessage* data)
1327 if (data == NULL)
1328 return B_BAD_VALUE;
1330 status_t status = data->AddString("suites", "suite/vnd.Haiku-MediaPlayer");
1331 if (status != B_OK)
1332 return status;
1334 BPropertyInfo propertyInfo(sPropertyInfo);
1335 status = data->AddFlat("messages", &propertyInfo);
1336 if (status != B_OK)
1337 return status;
1339 return BWindow::GetSupportedSuites(data);
1343 // #pragma mark -
1346 void
1347 MainWin::_RefsReceived(BMessage* message)
1349 // the playlist is replaced by dropped files
1350 // or the dropped files are appended to the end
1351 // of the existing playlist if <shift> is pressed
1352 bool append = false;
1353 if (message->FindBool("append to playlist", &append) != B_OK)
1354 append = modifiers() & B_SHIFT_KEY;
1356 BAutolock _(fPlaylist);
1357 int32 appendIndex = append ? APPEND_INDEX_APPEND_LAST
1358 : APPEND_INDEX_REPLACE_PLAYLIST;
1359 message->AddInt32("append_index", appendIndex);
1361 // forward the message to the playlist window,
1362 // so that undo/redo is used for modifying the playlist
1363 fPlaylistWindow->PostMessage(message);
1365 if (message->FindRect("window frame", &fNoVideoFrame) != B_OK)
1366 fNoVideoFrame = BRect();
1370 void
1371 MainWin::_PlaylistItemOpened(const PlaylistItemRef& item, status_t result)
1373 if (result != B_OK) {
1374 BAutolock _(fPlaylist);
1376 item->SetPlaybackFailed();
1377 bool allItemsFailed = true;
1378 int32 count = fPlaylist->CountItems();
1379 for (int32 i = 0; i < count; i++) {
1380 if (!fPlaylist->ItemAtFast(i)->PlaybackFailed()) {
1381 allItemsFailed = false;
1382 break;
1386 if (allItemsFailed) {
1387 // Display error if all files failed to play.
1388 BString message(B_TRANSLATE(
1389 "The file '%filename' could not be opened.\n\n"));;
1390 message.ReplaceAll("%filename", item->Name());
1392 if (result == B_MEDIA_NO_HANDLER) {
1393 // give a more detailed message for the most likely of all
1394 // errors
1395 message << B_TRANSLATE(
1396 "There is no decoder installed to handle the "
1397 "file format, or the decoder has trouble with the "
1398 "specific version of the format.");
1399 } else {
1400 message << B_TRANSLATE("Error: ") << strerror(result);
1402 BAlert* alert = new BAlert("error", message.String(),
1403 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1404 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1405 alert->Go();
1406 fControls->SetDisabledString(kDisabledSeekMessage);
1407 } else {
1408 // Just go to the next file and don't bother user (yet)
1409 fPlaylist->SetCurrentItemIndex(fPlaylist->CurrentItemIndex() + 1);
1412 fHasFile = false;
1413 fHasVideo = false;
1414 fHasAudio = false;
1415 SetTitle(kApplicationName);
1416 } else {
1417 fHasFile = true;
1418 fHasVideo = fController->VideoTrackCount() != 0;
1419 fHasAudio = fController->AudioTrackCount() != 0;
1420 SetTitle(item->Name().String());
1422 if (fInitialSeekPosition < 0) {
1423 fInitialSeekPosition
1424 = fController->TimeDuration() + fInitialSeekPosition;
1426 fController->SetTimePosition(fInitialSeekPosition);
1427 fInitialSeekPosition = 0;
1429 _SetupWindow();
1431 if (result == B_OK)
1432 _UpdatePlaylistItemFile();
1436 void
1437 MainWin::_SetupWindow()
1439 // printf("MainWin::_SetupWindow\n");
1440 // Populate the track menus
1441 _SetupTrackMenus(fAudioTrackMenu, fVideoTrackMenu, fSubTitleTrackMenu);
1442 _UpdateAudioChannelCount(fController->CurrentAudioTrack());
1444 fVideoMenu->SetEnabled(fHasVideo);
1445 fAudioMenu->SetEnabled(fHasAudio);
1446 int previousSourceWidth = fSourceWidth;
1447 int previousSourceHeight = fSourceHeight;
1448 int previousWidthAspect = fWidthAspect;
1449 int previousHeightAspect = fHeightAspect;
1450 if (fHasVideo) {
1451 fController->GetSize(&fSourceWidth, &fSourceHeight,
1452 &fWidthAspect, &fHeightAspect);
1453 } else {
1454 fSourceWidth = 0;
1455 fSourceHeight = 0;
1456 fWidthAspect = 1;
1457 fHeightAspect = 1;
1459 _UpdateControlsEnabledStatus();
1461 // Adopt the size and window layout if necessary
1462 if (previousSourceWidth != fSourceWidth
1463 || previousSourceHeight != fSourceHeight
1464 || previousWidthAspect != fWidthAspect
1465 || previousHeightAspect != fHeightAspect) {
1467 _SetWindowSizeLimits();
1469 if (!fIsFullscreen) {
1470 // Resize to 100% but stay on screen
1471 _ResizeWindow(100, !fHasVideo, true);
1472 } else {
1473 // Make sure we relayout the video view when in full screen mode
1474 FrameResized(Frame().Width(), Frame().Height());
1478 _ShowIfNeeded();
1480 fVideoView->MakeFocus();
1484 void
1485 MainWin::_CreateMenu()
1487 fFileMenu = new BMenu(kApplicationName);
1488 fPlaylistMenu = new BMenu(B_TRANSLATE("Playlist" B_UTF8_ELLIPSIS));
1489 fAudioMenu = new BMenu(B_TRANSLATE("Audio"));
1490 fVideoMenu = new BMenu(B_TRANSLATE("Video"));
1491 fVideoAspectMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
1492 fAudioTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track",
1493 "Audio Track Menu"));
1494 fVideoTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track",
1495 "Video Track Menu"));
1496 fSubTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
1497 fAttributesMenu = new BMenu(B_TRANSLATE("Attributes"));
1499 fMenuBar->AddItem(fFileMenu);
1500 fMenuBar->AddItem(fAudioMenu);
1501 fMenuBar->AddItem(fVideoMenu);
1502 fMenuBar->AddItem(fAttributesMenu);
1504 BMenuItem* item = new BMenuItem(B_TRANSLATE("New player" B_UTF8_ELLIPSIS),
1505 new BMessage(M_NEW_PLAYER), 'N');
1506 fFileMenu->AddItem(item);
1507 item->SetTarget(be_app);
1509 // Add recent files to "Open File" entry as sub-menu.
1510 BRecentFilesList recentFiles(10, false, NULL, kAppSig);
1511 item = new BMenuItem(recentFiles.NewFileListMenu(
1512 B_TRANSLATE("Open file" B_UTF8_ELLIPSIS), NULL, NULL, this, 10, true,
1513 NULL, kAppSig), new BMessage(M_FILE_OPEN));
1514 item->SetShortcut('O', 0);
1515 fFileMenu->AddItem(item);
1517 item = new BMenuItem(B_TRANSLATE("Open network stream"),
1518 new BMessage(M_NETWORK_STREAM_OPEN));
1519 fFileMenu->AddItem(item);
1521 fFileMenu->AddSeparatorItem();
1523 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("File info" B_UTF8_ELLIPSIS),
1524 new BMessage(M_FILE_INFO), 'I'));
1525 fFileMenu->AddItem(fPlaylistMenu);
1526 fPlaylistMenu->Superitem()->SetShortcut('P', B_COMMAND_KEY);
1527 fPlaylistMenu->Superitem()->SetMessage(new BMessage(M_FILE_PLAYLIST));
1529 fFileMenu->AddSeparatorItem();
1531 fNoInterfaceMenuItem = new BMenuItem(B_TRANSLATE("Hide interface"),
1532 new BMessage(M_TOGGLE_NO_INTERFACE), 'H');
1533 fFileMenu->AddItem(fNoInterfaceMenuItem);
1534 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Always on top"),
1535 new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
1537 item = new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
1538 new BMessage(M_SETTINGS), 'S');
1539 fFileMenu->AddItem(item);
1540 item->SetTarget(be_app);
1542 fFileMenu->AddSeparatorItem();
1544 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
1545 new BMessage(M_FILE_CLOSE), 'W'));
1546 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
1547 new BMessage(M_FILE_QUIT), 'Q'));
1549 fPlaylistMenu->SetRadioMode(true);
1551 fAudioMenu->AddItem(fAudioTrackMenu);
1553 fVideoMenu->AddItem(fVideoTrackMenu);
1554 fVideoMenu->AddItem(fSubTitleTrackMenu);
1555 fVideoMenu->AddSeparatorItem();
1556 BMessage* resizeMessage = new BMessage(M_VIEW_SIZE);
1557 resizeMessage->AddInt32("size", 50);
1558 fVideoMenu->AddItem(new BMenuItem(
1559 B_TRANSLATE("50% scale"), resizeMessage, '0'));
1561 resizeMessage = new BMessage(M_VIEW_SIZE);
1562 resizeMessage->AddInt32("size", 100);
1563 fVideoMenu->AddItem(new BMenuItem(
1564 B_TRANSLATE("100% scale"), resizeMessage, '1'));
1566 resizeMessage = new BMessage(M_VIEW_SIZE);
1567 resizeMessage->AddInt32("size", 200);
1568 fVideoMenu->AddItem(new BMenuItem(
1569 B_TRANSLATE("200% scale"), resizeMessage, '2'));
1571 resizeMessage = new BMessage(M_VIEW_SIZE);
1572 resizeMessage->AddInt32("size", 300);
1573 fVideoMenu->AddItem(new BMenuItem(
1574 B_TRANSLATE("300% scale"), resizeMessage, '3'));
1576 resizeMessage = new BMessage(M_VIEW_SIZE);
1577 resizeMessage->AddInt32("size", 400);
1578 fVideoMenu->AddItem(new BMenuItem(
1579 B_TRANSLATE("400% scale"), resizeMessage, '4'));
1581 fVideoMenu->AddSeparatorItem();
1583 fVideoMenu->AddItem(new BMenuItem(B_TRANSLATE("Full screen"),
1584 new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
1586 fVideoMenu->AddSeparatorItem();
1588 _SetupVideoAspectItems(fVideoAspectMenu);
1589 fVideoMenu->AddItem(fVideoAspectMenu);
1591 fRatingMenu = new BMenu(B_TRANSLATE("Rating"));
1592 fAttributesMenu->AddItem(fRatingMenu);
1593 for (int32 i = 1; i <= 10; i++) {
1594 char label[16];
1595 snprintf(label, sizeof(label), "%" B_PRId32, i);
1596 BMessage* setRatingMsg = new BMessage(M_SET_RATING);
1597 setRatingMsg->AddInt32("rating", i);
1598 fRatingMenu->AddItem(new BMenuItem(label, setRatingMsg));
1603 void
1604 MainWin::_SetupVideoAspectItems(BMenu* menu)
1606 BMenuItem* item;
1607 while ((item = menu->RemoveItem((int32)0)) != NULL)
1608 delete item;
1610 int width;
1611 int height;
1612 int widthAspect;
1613 int heightAspect;
1614 fController->GetSize(&width, &height, &widthAspect, &heightAspect);
1615 // We don't care if there is a video track at all. In that
1616 // case we should end up not marking any item.
1618 // NOTE: The item marking may end up marking for example both
1619 // "Stream Settings" and "16 : 9" if the stream settings happen to
1620 // be "16 : 9".
1622 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Stream settings"),
1623 new BMessage(M_ASPECT_SAME_AS_SOURCE), '1', B_SHIFT_KEY));
1624 item->SetMarked(widthAspect == fWidthAspect
1625 && heightAspect == fHeightAspect);
1627 menu->AddItem(item = new BMenuItem(B_TRANSLATE("No aspect correction"),
1628 new BMessage(M_ASPECT_NO_DISTORTION), '0', B_SHIFT_KEY));
1629 item->SetMarked(width == fWidthAspect && height == fHeightAspect);
1631 menu->AddSeparatorItem();
1633 menu->AddItem(item = new BMenuItem("4 : 3",
1634 new BMessage(M_ASPECT_4_3), 2, B_SHIFT_KEY));
1635 item->SetMarked(fWidthAspect == 4 && fHeightAspect == 3);
1636 menu->AddItem(item = new BMenuItem("16 : 9",
1637 new BMessage(M_ASPECT_16_9), 3, B_SHIFT_KEY));
1638 item->SetMarked(fWidthAspect == 16 && fHeightAspect == 9);
1640 menu->AddSeparatorItem();
1642 menu->AddItem(item = new BMenuItem("1.66 : 1",
1643 new BMessage(M_ASPECT_83_50)));
1644 item->SetMarked(fWidthAspect == 83 && fHeightAspect == 50);
1645 menu->AddItem(item = new BMenuItem("1.75 : 1",
1646 new BMessage(M_ASPECT_7_4)));
1647 item->SetMarked(fWidthAspect == 7 && fHeightAspect == 4);
1648 menu->AddItem(item = new BMenuItem(B_TRANSLATE("1.85 : 1 (American)"),
1649 new BMessage(M_ASPECT_37_20)));
1650 item->SetMarked(fWidthAspect == 37 && fHeightAspect == 20);
1651 menu->AddItem(item = new BMenuItem(B_TRANSLATE("2.35 : 1 (Cinemascope)"),
1652 new BMessage(M_ASPECT_47_20)));
1653 item->SetMarked(fWidthAspect == 47 && fHeightAspect == 20);
1657 void
1658 MainWin::_SetupTrackMenus(BMenu* audioTrackMenu, BMenu* videoTrackMenu,
1659 BMenu* subTitleTrackMenu)
1661 audioTrackMenu->RemoveItems(0, audioTrackMenu->CountItems(), true);
1662 videoTrackMenu->RemoveItems(0, videoTrackMenu->CountItems(), true);
1663 subTitleTrackMenu->RemoveItems(0, subTitleTrackMenu->CountItems(), true);
1665 char s[100];
1667 int count = fController->AudioTrackCount();
1668 int current = fController->CurrentAudioTrack();
1669 for (int i = 0; i < count; i++) {
1670 BMessage metaData;
1671 const char* languageString = NULL;
1672 if (fController->GetAudioMetaData(i, &metaData) == B_OK)
1673 metaData.FindString("language", &languageString);
1674 if (languageString != NULL) {
1675 BLanguage language(languageString);
1676 BString languageName;
1677 if (language.GetName(languageName) == B_OK)
1678 languageString = languageName.String();
1679 snprintf(s, sizeof(s), "%s", languageString);
1680 } else
1681 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1682 BMenuItem* item = new BMenuItem(s,
1683 new BMessage(M_SELECT_AUDIO_TRACK + i));
1684 item->SetMarked(i == current);
1685 audioTrackMenu->AddItem(item);
1687 if (count == 0) {
1688 audioTrackMenu->AddItem(new BMenuItem(B_TRANSLATE_CONTEXT("none",
1689 "Audio track menu"), new BMessage(M_DUMMY)));
1690 audioTrackMenu->ItemAt(0)->SetMarked(true);
1692 audioTrackMenu->SetEnabled(count > 1);
1694 count = fController->VideoTrackCount();
1695 current = fController->CurrentVideoTrack();
1696 for (int i = 0; i < count; i++) {
1697 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1698 BMenuItem* item = new BMenuItem(s,
1699 new BMessage(M_SELECT_VIDEO_TRACK + i));
1700 item->SetMarked(i == current);
1701 videoTrackMenu->AddItem(item);
1703 if (count == 0) {
1704 videoTrackMenu->AddItem(new BMenuItem(B_TRANSLATE("none"),
1705 new BMessage(M_DUMMY)));
1706 videoTrackMenu->ItemAt(0)->SetMarked(true);
1708 videoTrackMenu->SetEnabled(count > 1);
1710 count = fController->SubTitleTrackCount();
1711 if (count > 0) {
1712 current = fController->CurrentSubTitleTrack();
1713 BMenuItem* item = new BMenuItem(
1714 B_TRANSLATE_CONTEXT("Off", "Subtitles menu"),
1715 new BMessage(M_SELECT_SUB_TITLE_TRACK - 1));
1716 subTitleTrackMenu->AddItem(item);
1717 item->SetMarked(current == -1);
1719 subTitleTrackMenu->AddSeparatorItem();
1721 for (int i = 0; i < count; i++) {
1722 const char* name = fController->SubTitleTrackName(i);
1723 if (name != NULL)
1724 snprintf(s, sizeof(s), "%s", name);
1725 else
1726 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1727 item = new BMenuItem(s,
1728 new BMessage(M_SELECT_SUB_TITLE_TRACK + i));
1729 item->SetMarked(i == current);
1730 subTitleTrackMenu->AddItem(item);
1732 } else {
1733 subTitleTrackMenu->AddItem(new BMenuItem(
1734 B_TRANSLATE_CONTEXT("none", "Subtitles menu"),
1735 new BMessage(M_DUMMY)));
1736 subTitleTrackMenu->ItemAt(0)->SetMarked(true);
1738 subTitleTrackMenu->SetEnabled(count > 0);
1742 void
1743 MainWin::_UpdateAudioChannelCount(int32 audioTrackIndex)
1745 fControls->SetAudioChannelCount(fController->AudioTrackChannelCount());
1749 void
1750 MainWin::_GetMinimumWindowSize(int& width, int& height) const
1752 width = MIN_WIDTH;
1753 height = 0;
1754 if (!fNoInterface) {
1755 width = max_c(width, fMenuBarWidth);
1756 width = max_c(width, fControlsWidth);
1757 height = fMenuBarHeight + fControlsHeight;
1762 void
1763 MainWin::_GetUnscaledVideoSize(int& videoWidth, int& videoHeight) const
1765 if (fWidthAspect != 0 && fHeightAspect != 0) {
1766 videoWidth = fSourceHeight * fWidthAspect / fHeightAspect;
1767 videoHeight = fSourceWidth * fHeightAspect / fWidthAspect;
1768 // Use the scaling which produces an enlarged view.
1769 if (videoWidth > fSourceWidth) {
1770 // Enlarge width
1771 videoHeight = fSourceHeight;
1772 } else {
1773 // Enlarge height
1774 videoWidth = fSourceWidth;
1776 } else {
1777 videoWidth = fSourceWidth;
1778 videoHeight = fSourceHeight;
1783 void
1784 MainWin::_SetWindowSizeLimits()
1786 int minWidth;
1787 int minHeight;
1788 _GetMinimumWindowSize(minWidth, minHeight);
1789 SetSizeLimits(minWidth - 1, 32000, minHeight - 1,
1790 fHasVideo ? 32000 : minHeight - 1);
1795 MainWin::_CurrentVideoSizeInPercent() const
1797 if (!fHasVideo)
1798 return 0;
1800 int videoWidth;
1801 int videoHeight;
1802 _GetUnscaledVideoSize(videoWidth, videoHeight);
1804 int viewWidth = fVideoView->Bounds().IntegerWidth() + 1;
1805 int viewHeight = fVideoView->Bounds().IntegerHeight() + 1;
1807 int widthPercent = viewWidth * 100 / videoWidth;
1808 int heightPercent = viewHeight * 100 / videoHeight;
1810 if (widthPercent > heightPercent)
1811 return widthPercent;
1812 return heightPercent;
1816 void
1817 MainWin::_ZoomVideoView(int percentDiff)
1819 if (!fHasVideo)
1820 return;
1822 int percent = _CurrentVideoSizeInPercent();
1823 int newSize = percent * (100 + percentDiff) / 100;
1825 if (newSize < 25)
1826 newSize = 25;
1827 if (newSize > 400)
1828 newSize = 400;
1829 if (newSize != percent) {
1830 BMessage message(M_VIEW_SIZE);
1831 message.AddInt32("size", newSize);
1832 PostMessage(&message);
1837 void
1838 MainWin::_ResizeWindow(int percent, bool useNoVideoWidth, bool stayOnScreen)
1840 // Get required window size
1841 int videoWidth;
1842 int videoHeight;
1843 _GetUnscaledVideoSize(videoWidth, videoHeight);
1845 videoWidth = (videoWidth * percent) / 100;
1846 videoHeight = (videoHeight * percent) / 100;
1848 // Calculate and set the minimum window size
1849 int width;
1850 int height;
1851 _GetMinimumWindowSize(width, height);
1853 width = max_c(width, videoWidth) - 1;
1854 if (useNoVideoWidth)
1855 width = max_c(width, fNoVideoWidth);
1856 height = height + videoHeight - 1;
1858 if (stayOnScreen) {
1859 BRect screenFrame(BScreen(this).Frame());
1860 BRect frame(Frame());
1861 BRect decoratorFrame(DecoratorFrame());
1863 // Shrink the screen frame by the window border size
1864 screenFrame.top += frame.top - decoratorFrame.top;
1865 screenFrame.left += frame.left - decoratorFrame.left;
1866 screenFrame.right += frame.right - decoratorFrame.right;
1867 screenFrame.bottom += frame.bottom - decoratorFrame.bottom;
1869 // Update frame to what the new size would be
1870 frame.right = frame.left + width;
1871 frame.bottom = frame.top + height;
1873 if (!screenFrame.Contains(frame)) {
1874 // Resize the window so it doesn't extend outside the current
1875 // screen frame.
1876 // We don't use BWindow::MoveOnScreen() in order to resize the
1877 // window while keeping the same aspect ratio.
1878 if (frame.Width() > screenFrame.Width()
1879 || frame.Height() > screenFrame.Height()) {
1880 // too large
1881 int widthDiff
1882 = frame.IntegerWidth() - screenFrame.IntegerWidth();
1883 int heightDiff
1884 = frame.IntegerHeight() - screenFrame.IntegerHeight();
1886 float shrinkScale;
1887 if (widthDiff > heightDiff)
1888 shrinkScale = (float)(width - widthDiff) / width;
1889 else
1890 shrinkScale = (float)(height - heightDiff) / height;
1892 // Resize width/height and center window
1893 width = lround(width * shrinkScale);
1894 height = lround(height * shrinkScale);
1895 MoveTo((screenFrame.left + screenFrame.right - width) / 2,
1896 (screenFrame.top + screenFrame.bottom - height) / 2);
1897 } else {
1898 // just off-screen on one or more sides
1899 int offsetX = 0;
1900 int offsetY = 0;
1901 if (frame.left < screenFrame.left)
1902 offsetX = (int)(screenFrame.left - frame.left);
1903 else if (frame.right > screenFrame.right)
1904 offsetX = (int)(screenFrame.right - frame.right);
1905 if (frame.top < screenFrame.top)
1906 offsetY = (int)(screenFrame.top - frame.top);
1907 else if (frame.bottom > screenFrame.bottom)
1908 offsetY = (int)(screenFrame.bottom - frame.bottom);
1909 MoveBy(offsetX, offsetY);
1914 ResizeTo(width, height);
1918 void
1919 MainWin::_ResizeVideoView(int x, int y, int width, int height)
1921 // Keep aspect ratio, place video view inside
1922 // the background area (may create black bars).
1923 int videoWidth;
1924 int videoHeight;
1925 _GetUnscaledVideoSize(videoWidth, videoHeight);
1926 float scaledWidth = videoWidth;
1927 float scaledHeight = videoHeight;
1928 float factor = min_c(width / scaledWidth, height / scaledHeight);
1929 int renderWidth = lround(scaledWidth * factor);
1930 int renderHeight = lround(scaledHeight * factor);
1931 if (renderWidth > width)
1932 renderWidth = width;
1933 if (renderHeight > height)
1934 renderHeight = height;
1936 int xOffset = (width - renderWidth) / 2;
1937 int yOffset = (height - renderHeight) / 2;
1939 fVideoView->MoveTo(x, y);
1940 fVideoView->ResizeTo(width - 1, height - 1);
1942 BRect videoFrame(xOffset, yOffset,
1943 xOffset + renderWidth - 1, yOffset + renderHeight - 1);
1945 fVideoView->SetVideoFrame(videoFrame);
1946 fVideoView->SetSubTitleMaxBottom(height - 1);
1950 // #pragma mark -
1953 void
1954 MainWin::_MouseDown(BMessage* msg, BView* originalHandler)
1956 uint32 buttons = msg->FindInt32("buttons");
1958 // On Zeta, only "screen_where" is reliable, "where" and "be:view_where"
1959 // seem to be broken
1960 BPoint screenWhere;
1961 if (msg->FindPoint("screen_where", &screenWhere) != B_OK) {
1962 // TODO: remove
1963 // Workaround for BeOS R5, it has no "screen_where"
1964 if (!originalHandler || msg->FindPoint("where", &screenWhere) < B_OK)
1965 return;
1966 originalHandler->ConvertToScreen(&screenWhere);
1969 // double click handling
1971 if (msg->FindInt32("clicks") % 2 == 0) {
1972 BRect rect(screenWhere.x - 1, screenWhere.y - 1, screenWhere.x + 1,
1973 screenWhere.y + 1);
1974 if (rect.Contains(fMouseDownMousePos)) {
1975 if (buttons == B_PRIMARY_MOUSE_BUTTON)
1976 PostMessage(M_TOGGLE_FULLSCREEN);
1977 else if (buttons == B_SECONDARY_MOUSE_BUTTON)
1978 PostMessage(M_TOGGLE_NO_INTERFACE);
1980 return;
1984 fMouseDownMousePos = screenWhere;
1985 fMouseDownWindowPos = Frame().LeftTop();
1987 if (buttons == B_PRIMARY_MOUSE_BUTTON && !fIsFullscreen) {
1988 // start mouse tracking
1989 fVideoView->SetMouseEventMask(B_POINTER_EVENTS | B_NO_POINTER_HISTORY
1990 /* | B_LOCK_WINDOW_FOCUS */);
1991 fMouseDownTracking = true;
1994 // pop up a context menu if right mouse button is down
1996 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
1997 _ShowContextMenu(screenWhere);
2001 void
2002 MainWin::_MouseMoved(BMessage* msg, BView* originalHandler)
2004 // msg->PrintToStream();
2006 BPoint mousePos;
2007 uint32 buttons = msg->FindInt32("buttons");
2008 // On Zeta, only "screen_where" is reliable, "where"
2009 // and "be:view_where" seem to be broken
2010 if (msg->FindPoint("screen_where", &mousePos) != B_OK) {
2011 // TODO: remove
2012 // Workaround for BeOS R5, it has no "screen_where"
2013 if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK)
2014 return;
2015 originalHandler->ConvertToScreen(&mousePos);
2018 if (buttons == B_PRIMARY_MOUSE_BUTTON && fMouseDownTracking
2019 && !fIsFullscreen) {
2020 // printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y);
2021 float delta_x = mousePos.x - fMouseDownMousePos.x;
2022 float delta_y = mousePos.y - fMouseDownMousePos.y;
2023 float x = fMouseDownWindowPos.x + delta_x;
2024 float y = fMouseDownWindowPos.y + delta_y;
2025 // printf("move window to %.0f, %.0f\n", x, y);
2026 MoveTo(x, y);
2029 bigtime_t eventTime;
2030 if (msg->FindInt64("when", &eventTime) != B_OK)
2031 eventTime = system_time();
2033 if (buttons == 0 && fIsFullscreen) {
2034 BPoint moveDelta = mousePos - fLastMousePos;
2035 float moveDeltaDist
2036 = sqrtf(moveDelta.x * moveDelta.x + moveDelta.y * moveDelta.y);
2037 if (eventTime - fLastMouseMovedTime < 200000)
2038 fMouseMoveDist += moveDeltaDist;
2039 else
2040 fMouseMoveDist = moveDeltaDist;
2041 if (fMouseMoveDist > 5)
2042 _ShowFullscreenControls(true);
2045 fLastMousePos = mousePos;
2046 fLastMouseMovedTime =eventTime;
2050 void
2051 MainWin::_MouseUp(BMessage* msg)
2053 fMouseDownTracking = false;
2057 void
2058 MainWin::_ShowContextMenu(const BPoint& screenPoint)
2060 printf("Show context menu\n");
2061 BPopUpMenu* menu = new BPopUpMenu("context menu", false, false);
2062 BMenuItem* item;
2063 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Full screen"),
2064 new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
2065 item->SetMarked(fIsFullscreen);
2066 item->SetEnabled(fHasVideo);
2068 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Hide interface"),
2069 new BMessage(M_TOGGLE_NO_INTERFACE), 'H'));
2070 item->SetMarked(fNoInterface);
2071 item->SetEnabled(fHasVideo && !fIsFullscreen);
2073 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"),
2074 new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
2075 item->SetMarked(fAlwaysOnTop);
2076 item->SetEnabled(fHasVideo);
2078 BMenu* aspectSubMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
2079 _SetupVideoAspectItems(aspectSubMenu);
2080 aspectSubMenu->SetTargetForItems(this);
2081 menu->AddItem(item = new BMenuItem(aspectSubMenu));
2082 item->SetEnabled(fHasVideo);
2084 menu->AddSeparatorItem();
2086 // Add track selector menus
2087 BMenu* audioTrackMenu = new BMenu(B_TRANSLATE("Audio track"));
2088 BMenu* videoTrackMenu = new BMenu(B_TRANSLATE("Video track"));
2089 BMenu* subTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
2090 _SetupTrackMenus(audioTrackMenu, videoTrackMenu, subTitleTrackMenu);
2092 audioTrackMenu->SetTargetForItems(this);
2093 videoTrackMenu->SetTargetForItems(this);
2094 subTitleTrackMenu->SetTargetForItems(this);
2096 menu->AddItem(item = new BMenuItem(audioTrackMenu));
2097 item->SetEnabled(fHasAudio);
2099 menu->AddItem(item = new BMenuItem(videoTrackMenu));
2100 item->SetEnabled(fHasVideo);
2102 menu->AddItem(item = new BMenuItem(subTitleTrackMenu));
2103 item->SetEnabled(fHasVideo);
2105 menu->AddSeparatorItem();
2106 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), new BMessage(M_FILE_QUIT), 'Q'));
2108 menu->SetTargetForItems(this);
2109 BRect rect(screenPoint.x - 5, screenPoint.y - 5, screenPoint.x + 5,
2110 screenPoint.y + 5);
2111 menu->Go(screenPoint, true, true, rect, true);
2115 /*! Trap keys that are about to be send to background or renderer view.
2116 Return true if it shouldn't be passed to the view.
2118 bool
2119 MainWin::_KeyDown(BMessage* msg)
2121 uint32 key = msg->FindInt32("key");
2122 uint32 rawChar = msg->FindInt32("raw_char");
2123 uint32 modifier = msg->FindInt32("modifiers");
2125 // printf("key 0x%lx, rawChar 0x%lx, modifiers 0x%lx\n", key, rawChar,
2126 // modifier);
2128 // ignore the system modifier namespace
2129 if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
2130 == (B_CONTROL_KEY | B_COMMAND_KEY))
2131 return false;
2133 switch (rawChar) {
2134 case B_SPACE:
2135 fController->TogglePlaying();
2136 return true;
2138 case 'm':
2139 fController->ToggleMute();
2140 return true;
2142 case B_ESCAPE:
2143 if (!fIsFullscreen)
2144 break;
2146 PostMessage(M_TOGGLE_FULLSCREEN);
2147 return true;
2149 case B_ENTER: // Enter / Return
2150 if ((modifier & B_COMMAND_KEY) != 0) {
2151 PostMessage(M_TOGGLE_FULLSCREEN);
2152 return true;
2154 break;
2156 case B_TAB:
2157 case 'f':
2158 if ((modifier & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
2159 | B_MENU_KEY)) == 0) {
2160 PostMessage(M_TOGGLE_FULLSCREEN);
2161 return true;
2163 break;
2165 case B_UP_ARROW:
2166 if ((modifier & B_COMMAND_KEY) != 0)
2167 PostMessage(M_SKIP_NEXT);
2168 else
2169 PostMessage(M_VOLUME_UP);
2170 return true;
2172 case B_DOWN_ARROW:
2173 if ((modifier & B_COMMAND_KEY) != 0)
2174 PostMessage(M_SKIP_PREV);
2175 else
2176 PostMessage(M_VOLUME_DOWN);
2177 return true;
2179 case B_RIGHT_ARROW:
2180 if ((modifier & B_COMMAND_KEY) != 0)
2181 PostMessage(M_SKIP_NEXT);
2182 else if (fAllowWinding) {
2183 BMessage windMessage(M_WIND);
2184 if ((modifier & B_SHIFT_KEY) != 0) {
2185 windMessage.AddInt64("how much", 30000000LL);
2186 windMessage.AddInt64("frames", 5);
2187 } else {
2188 windMessage.AddInt64("how much", 5000000LL);
2189 windMessage.AddInt64("frames", 1);
2191 PostMessage(&windMessage);
2193 return true;
2195 case B_LEFT_ARROW:
2196 if ((modifier & B_COMMAND_KEY) != 0)
2197 PostMessage(M_SKIP_PREV);
2198 else if (fAllowWinding) {
2199 BMessage windMessage(M_WIND);
2200 if ((modifier & B_SHIFT_KEY) != 0) {
2201 windMessage.AddInt64("how much", -30000000LL);
2202 windMessage.AddInt64("frames", -5);
2203 } else {
2204 windMessage.AddInt64("how much", -5000000LL);
2205 windMessage.AddInt64("frames", -1);
2207 PostMessage(&windMessage);
2209 return true;
2211 case B_PAGE_UP:
2212 PostMessage(M_SKIP_NEXT);
2213 return true;
2215 case B_PAGE_DOWN:
2216 PostMessage(M_SKIP_PREV);
2217 return true;
2219 case '+':
2220 if ((modifier & B_COMMAND_KEY) == 0) {
2221 _ZoomVideoView(10);
2222 return true;
2224 break;
2226 case '-':
2227 if ((modifier & B_COMMAND_KEY) == 0) {
2228 _ZoomVideoView(-10);
2229 return true;
2231 break;
2233 case B_DELETE:
2234 case 'd': // d for delete
2235 case 't': // t for Trash
2236 if ((modifiers() & B_COMMAND_KEY) != 0) {
2237 BAutolock _(fPlaylist);
2238 BMessage removeMessage(M_PLAYLIST_MOVE_TO_TRASH);
2239 removeMessage.AddInt32("playlist index",
2240 fPlaylist->CurrentItemIndex());
2241 fPlaylistWindow->PostMessage(&removeMessage);
2242 return true;
2244 break;
2247 switch (key) {
2248 case 0x3a: // numeric keypad +
2249 if ((modifier & B_COMMAND_KEY) == 0) {
2250 _ZoomVideoView(10);
2251 return true;
2253 break;
2255 case 0x25: // numeric keypad -
2256 if ((modifier & B_COMMAND_KEY) == 0) {
2257 _ZoomVideoView(-10);
2258 return true;
2260 break;
2262 case 0x38: // numeric keypad up arrow
2263 PostMessage(M_VOLUME_UP);
2264 return true;
2266 case 0x59: // numeric keypad down arrow
2267 PostMessage(M_VOLUME_DOWN);
2268 return true;
2270 case 0x39: // numeric keypad page up
2271 case 0x4a: // numeric keypad right arrow
2272 PostMessage(M_SKIP_NEXT);
2273 return true;
2275 case 0x5a: // numeric keypad page down
2276 case 0x48: // numeric keypad left arrow
2277 PostMessage(M_SKIP_PREV);
2278 return true;
2280 // Playback controls along the bottom of the keyboard:
2281 // Z X C (V) B for US International
2282 case 0x4c:
2283 PostMessage(M_SKIP_PREV);
2284 return true;
2285 case 0x4d:
2286 fController->TogglePlaying();
2287 return true;
2288 case 0x4e:
2289 fController->Pause();
2290 return true;
2291 case 0x4f:
2292 fController->Stop();
2293 return true;
2294 case 0x50:
2295 PostMessage(M_SKIP_NEXT);
2296 return true;
2299 return false;
2303 // #pragma mark -
2306 void
2307 MainWin::_ToggleFullscreen()
2309 printf("_ToggleFullscreen enter\n");
2311 if (!fHasVideo) {
2312 printf("_ToggleFullscreen - ignoring, as we don't have a video\n");
2313 return;
2316 fIsFullscreen = !fIsFullscreen;
2318 if (fIsFullscreen) {
2319 // switch to fullscreen
2321 fSavedFrame = Frame();
2322 printf("saving current frame: %d %d %d %d\n", int(fSavedFrame.left),
2323 int(fSavedFrame.top), int(fSavedFrame.right),
2324 int(fSavedFrame.bottom));
2325 BScreen screen(this);
2326 BRect rect(screen.Frame());
2328 Hide();
2329 MoveTo(rect.left, rect.top);
2330 ResizeTo(rect.Width(), rect.Height());
2331 Show();
2333 } else {
2334 // switch back from full screen mode
2335 _ShowFullscreenControls(false, false);
2337 Hide();
2338 MoveTo(fSavedFrame.left, fSavedFrame.top);
2339 ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
2340 Show();
2343 fVideoView->SetFullscreen(fIsFullscreen);
2345 _MarkItem(fFileMenu, M_TOGGLE_FULLSCREEN, fIsFullscreen);
2347 printf("_ToggleFullscreen leave\n");
2350 void
2351 MainWin::_ToggleAlwaysOnTop()
2353 fAlwaysOnTop = !fAlwaysOnTop;
2354 SetFeel(fAlwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL);
2356 _MarkItem(fFileMenu, M_TOGGLE_ALWAYS_ON_TOP, fAlwaysOnTop);
2360 void
2361 MainWin::_ToggleNoInterface()
2363 printf("_ToggleNoInterface enter\n");
2365 if (fIsFullscreen || !fHasVideo) {
2366 // Fullscreen playback is always without interface and
2367 // audio playback is always with interface. So we ignore these
2368 // two states here.
2369 printf("_ToggleNoControls leave, doing nothing, we are fullscreen\n");
2370 return;
2373 fNoInterface = !fNoInterface;
2374 _SetWindowSizeLimits();
2376 if (fNoInterface) {
2377 MoveBy(0, fMenuBarHeight);
2378 ResizeBy(0, -(fControlsHeight + fMenuBarHeight));
2379 SetLook(B_BORDERED_WINDOW_LOOK);
2380 } else {
2381 MoveBy(0, -fMenuBarHeight);
2382 ResizeBy(0, fControlsHeight + fMenuBarHeight);
2383 SetLook(B_TITLED_WINDOW_LOOK);
2386 _MarkItem(fFileMenu, M_TOGGLE_NO_INTERFACE, fNoInterface);
2388 printf("_ToggleNoInterface leave\n");
2392 void
2393 MainWin::_ShowIfNeeded()
2395 // Only proceed if the window is already running
2396 if (find_thread(NULL) != Thread())
2397 return;
2399 if (!fHasVideo && fNoVideoFrame.IsValid()) {
2400 MoveTo(fNoVideoFrame.LeftTop());
2401 ResizeTo(fNoVideoFrame.Width(), fNoVideoFrame.Height());
2402 MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN);
2403 } else if (fHasVideo && IsHidden())
2404 CenterOnScreen();
2406 fNoVideoFrame = BRect();
2408 if (IsHidden()) {
2409 Show();
2410 UpdateIfNeeded();
2415 void
2416 MainWin::_ShowFullscreenControls(bool show, bool animate)
2418 if (fShowsFullscreenControls == show)
2419 return;
2421 fShowsFullscreenControls = show;
2422 fVideoView->SetFullscreenControlsVisible(show);
2424 if (show) {
2425 fControls->RemoveSelf();
2426 fControls->MoveTo(fVideoView->Bounds().left,
2427 fVideoView->Bounds().bottom + 1);
2428 fVideoView->AddChild(fControls);
2429 if (fScaleFullscreenControls)
2430 fControls->SetSymbolScale(1.5f);
2432 while (fControls->IsHidden())
2433 fControls->Show();
2436 if (animate) {
2437 // Slide the controls into view. We need to do this with
2438 // messages, otherwise we block the video playback for the
2439 // time of the animation.
2440 const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
2441 const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
2442 float height = fControls->Bounds().Height();
2443 float moveDist = show ? -height : height;
2444 float originalY = fControls->Frame().top;
2445 for (int32 i = 0; i < steps; i++) {
2446 BMessage message(M_SLIDE_CONTROLS);
2447 message.AddFloat("offset",
2448 floorf(moveDist * kAnimationOffsets[i]));
2449 PostMessage(&message, this);
2451 BMessage finalMessage(M_FINISH_SLIDING_CONTROLS);
2452 finalMessage.AddFloat("offset", originalY + moveDist);
2453 finalMessage.AddBool("show", show);
2454 PostMessage(&finalMessage, this);
2455 } else if (!show) {
2456 fControls->RemoveSelf();
2457 fControls->MoveTo(fVideoView->Frame().left,
2458 fVideoView->Frame().bottom + 1);
2459 fBackground->AddChild(fControls);
2460 fControls->SetSymbolScale(1.0f);
2462 while (!fControls->IsHidden())
2463 fControls->Hide();
2468 // #pragma mark -
2471 void
2472 MainWin::_Wind(bigtime_t howMuch, int64 frames)
2474 if (!fAllowWinding || !fController->Lock())
2475 return;
2477 if (frames != 0 && fHasVideo && !fController->IsPlaying()) {
2478 int64 newFrame = fController->CurrentFrame() + frames;
2479 fController->SetFramePosition(newFrame);
2480 } else {
2481 bigtime_t seekTime = fController->TimePosition() + howMuch;
2482 if (seekTime < 0) {
2483 fInitialSeekPosition = seekTime;
2484 PostMessage(M_SKIP_PREV);
2485 } else if (seekTime > fController->TimeDuration()) {
2486 fInitialSeekPosition = 0;
2487 PostMessage(M_SKIP_NEXT);
2488 } else
2489 fController->SetTimePosition(seekTime);
2492 fController->Unlock();
2493 fAllowWinding = false;
2497 // #pragma mark -
2500 void
2501 MainWin::_UpdatePlaylistItemFile()
2503 BAutolock locker(fPlaylist);
2504 const FilePlaylistItem* item
2505 = dynamic_cast<const FilePlaylistItem*>(fController->Item());
2506 if (item == NULL)
2507 return;
2509 if (!fHasVideo && !fHasAudio)
2510 return;
2512 BNode node(&item->Ref());
2513 if (node.InitCheck())
2514 return;
2516 locker.Unlock();
2518 // Set some standard attributes of the currently played file.
2519 // This should only be a temporary solution.
2521 // Write duration
2522 const char* kDurationAttrName = "Media:Length";
2523 attr_info info;
2524 status_t status = node.GetAttrInfo(kDurationAttrName, &info);
2525 if (status != B_OK || info.size == 0) {
2526 bigtime_t duration = fController->TimeDuration();
2527 // TODO: Tracker does not seem to care about endian for scalar types
2528 node.WriteAttr(kDurationAttrName, B_INT64_TYPE, 0, &duration,
2529 sizeof(int64));
2532 // Write audio bitrate
2533 if (fHasAudio) {
2534 status = node.GetAttrInfo("Audio:Bitrate", &info);
2535 if (status != B_OK || info.size == 0) {
2536 media_format format;
2537 if (fController->GetEncodedAudioFormat(&format) == B_OK
2538 && format.type == B_MEDIA_ENCODED_AUDIO) {
2539 int32 bitrate = (int32)(format.u.encoded_audio.bit_rate
2540 / 1000);
2541 char text[256];
2542 snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2543 node.WriteAttr("Audio:Bitrate", B_STRING_TYPE, 0, text,
2544 strlen(text) + 1);
2549 // Write video bitrate
2550 if (fHasVideo) {
2551 status = node.GetAttrInfo("Video:Bitrate", &info);
2552 if (status != B_OK || info.size == 0) {
2553 media_format format;
2554 if (fController->GetEncodedVideoFormat(&format) == B_OK
2555 && format.type == B_MEDIA_ENCODED_VIDEO) {
2556 int32 bitrate = (int32)(format.u.encoded_video.avg_bit_rate
2557 / 1000);
2558 char text[256];
2559 snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2560 node.WriteAttr("Video:Bitrate", B_STRING_TYPE, 0, text,
2561 strlen(text) + 1);
2566 _UpdateAttributesMenu(node);
2570 void
2571 MainWin::_UpdateAttributesMenu(const BNode& node)
2573 int32 rating = -1;
2575 attr_info info;
2576 status_t status = node.GetAttrInfo(kRatingAttrName, &info);
2577 if (status == B_OK && info.type == B_INT32_TYPE) {
2578 // Node has the Rating attribute.
2579 node.ReadAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating,
2580 sizeof(rating));
2583 for (int32 i = 0; BMenuItem* item = fRatingMenu->ItemAt(i); i++)
2584 item->SetMarked(i + 1 == rating);
2588 void
2589 MainWin::_SetRating(int32 rating)
2591 BAutolock locker(fPlaylist);
2592 const FilePlaylistItem* item
2593 = dynamic_cast<const FilePlaylistItem*>(fController->Item());
2594 if (item == NULL)
2595 return;
2597 BNode node(&item->Ref());
2598 if (node.InitCheck())
2599 return;
2601 locker.Unlock();
2603 node.WriteAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating, sizeof(rating));
2605 // TODO: The whole mechnism should work like this:
2606 // * There is already an attribute API for PlaylistItem, flesh it out!
2607 // * FilePlaylistItem node-monitors it's file somehow.
2608 // * FilePlaylistItem keeps attributes in sync and sends notications.
2609 // * MainWin updates the menu according to FilePlaylistItem notifications.
2610 // * PlaylistWin shows columns with attribute and other info.
2611 // * PlaylistWin updates also upon FilePlaylistItem notifications.
2612 // * This keeps attributes in sync when another app changes them.
2614 _UpdateAttributesMenu(node);
2618 void
2619 MainWin::_UpdateControlsEnabledStatus()
2621 uint32 enabledButtons = 0;
2622 if (fHasVideo || fHasAudio) {
2623 enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED
2624 | SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED;
2626 if (fHasAudio)
2627 enabledButtons |= VOLUME_ENABLED;
2629 BAutolock _(fPlaylist);
2630 bool canSkipPrevious, canSkipNext;
2631 fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext);
2632 if (canSkipPrevious)
2633 enabledButtons |= SKIP_BACK_ENABLED;
2634 if (canSkipNext)
2635 enabledButtons |= SKIP_FORWARD_ENABLED;
2637 fControls->SetEnabled(enabledButtons);
2639 fNoInterfaceMenuItem->SetEnabled(fHasVideo);
2640 fAttributesMenu->SetEnabled(fHasAudio || fHasVideo);
2644 void
2645 MainWin::_UpdatePlaylistMenu()
2647 if (!fPlaylist->Lock())
2648 return;
2650 fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true);
2652 int32 count = fPlaylist->CountItems();
2653 for (int32 i = 0; i < count; i++) {
2654 PlaylistItem* item = fPlaylist->ItemAtFast(i);
2655 _AddPlaylistItem(item, i);
2657 fPlaylistMenu->SetTargetForItems(this);
2659 _MarkPlaylistItem(fPlaylist->CurrentItemIndex());
2661 fPlaylist->Unlock();
2665 void
2666 MainWin::_AddPlaylistItem(PlaylistItem* item, int32 index)
2668 BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION);
2669 message->AddInt32("index", index);
2670 BMenuItem* menuItem = new BMenuItem(item->Name().String(), message);
2671 fPlaylistMenu->AddItem(menuItem, index);
2675 void
2676 MainWin::_RemovePlaylistItem(int32 index)
2678 delete fPlaylistMenu->RemoveItem(index);
2682 void
2683 MainWin::_MarkPlaylistItem(int32 index)
2685 if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) {
2686 item->SetMarked(true);
2687 // ... and in case the menu is currently on screen:
2688 if (fPlaylistMenu->LockLooper()) {
2689 fPlaylistMenu->Invalidate();
2690 fPlaylistMenu->UnlockLooper();
2696 void
2697 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark)
2699 if (BMenuItem* item = menu->FindItem(command))
2700 item->SetMarked(mark);
2704 void
2705 MainWin::_AdoptGlobalSettings()
2707 mpSettings settings;
2708 Settings::Default()->Get(settings);
2710 fCloseWhenDonePlayingMovie = settings.closeWhenDonePlayingMovie;
2711 fCloseWhenDonePlayingSound = settings.closeWhenDonePlayingSound;
2712 fLoopMovies = settings.loopMovie;
2713 fLoopSounds = settings.loopSound;
2714 fScaleFullscreenControls = settings.scaleFullscreenControls;