repository_infos: Enable automatic updates on the main Haiku repostiory.
[haiku.git] / src / apps / mediaplayer / MainWin.cpp
blob8fe8605a8b7d3b042d65ac92790408fb8d26d43a
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 * Released under the terms of the MIT license.
9 */
12 #include "MainWin.h"
14 #include <math.h>
15 #include <stdio.h>
16 #include <string.h>
18 #include <Alert.h>
19 #include <Application.h>
20 #include <Autolock.h>
21 #include <Catalog.h>
22 #include <Debug.h>
23 #include <fs_attr.h>
24 #include <LayoutBuilder.h>
25 #include <Language.h>
26 #include <Locale.h>
27 #include <MediaRoster.h>
28 #include <Menu.h>
29 #include <MenuBar.h>
30 #include <MenuItem.h>
31 #include <MessageRunner.h>
32 #include <Messenger.h>
33 #include <PopUpMenu.h>
34 #include <PropertyInfo.h>
35 #include <RecentItems.h>
36 #include <Roster.h>
37 #include <Screen.h>
38 #include <String.h>
39 #include <TypeConstants.h>
40 #include <View.h>
42 #include "AudioProducer.h"
43 #include "ControllerObserver.h"
44 #include "DurationToString.h"
45 #include "FilePlaylistItem.h"
46 #include "MainApp.h"
47 #include "NetworkStreamWin.h"
48 #include "PeakView.h"
49 #include "PlaylistItem.h"
50 #include "PlaylistObserver.h"
51 #include "PlaylistWindow.h"
52 #include "Settings.h"
55 #undef B_TRANSLATION_CONTEXT
56 #define B_TRANSLATION_CONTEXT "MediaPlayer-Main"
57 #define MIN_WIDTH 250
60 int MainWin::sNoVideoWidth = MIN_WIDTH;
63 // XXX TODO: why is lround not defined?
64 #define lround(a) ((int)(0.99999 + (a)))
66 enum {
67 M_DUMMY = 0x100,
68 M_FILE_OPEN = 0x1000,
69 M_NETWORK_STREAM_OPEN,
70 M_FILE_INFO,
71 M_FILE_PLAYLIST,
72 M_FILE_CLOSE,
73 M_FILE_QUIT,
74 M_VIEW_SIZE,
75 M_TOGGLE_FULLSCREEN,
76 M_TOGGLE_ALWAYS_ON_TOP,
77 M_TOGGLE_NO_INTERFACE,
78 M_VOLUME_UP,
79 M_VOLUME_DOWN,
80 M_SKIP_NEXT,
81 M_SKIP_PREV,
82 M_WIND,
84 // The common display aspect ratios
85 M_ASPECT_SAME_AS_SOURCE,
86 M_ASPECT_NO_DISTORTION,
87 M_ASPECT_4_3,
88 M_ASPECT_16_9,
89 M_ASPECT_83_50,
90 M_ASPECT_7_4,
91 M_ASPECT_37_20,
92 M_ASPECT_47_20,
94 M_SELECT_AUDIO_TRACK = 0x00000800,
95 M_SELECT_AUDIO_TRACK_END = 0x00000fff,
96 M_SELECT_VIDEO_TRACK = 0x00010000,
97 M_SELECT_VIDEO_TRACK_END = 0x00010fff,
98 M_SELECT_SUB_TITLE_TRACK = 0x00020000,
99 M_SELECT_SUB_TITLE_TRACK_END = 0x00020fff,
101 M_SET_RATING,
103 M_SET_PLAYLIST_POSITION,
105 M_FILE_DELETE,
107 M_SLIDE_CONTROLS,
108 M_FINISH_SLIDING_CONTROLS
112 static property_info sPropertyInfo[] = {
113 { B_TRANSLATE("Next"), { B_EXECUTE_PROPERTY },
114 { B_DIRECT_SPECIFIER, 0 },
115 B_TRANSLATE("Skip to the next track."), 0
117 { B_TRANSLATE("Prev"), { B_EXECUTE_PROPERTY },
118 { B_DIRECT_SPECIFIER, 0 },
119 B_TRANSLATE("Skip to the previous track."), 0
121 { B_TRANSLATE("Play"), { B_EXECUTE_PROPERTY },
122 { B_DIRECT_SPECIFIER, 0 },
123 B_TRANSLATE("Start playing."), 0
125 { B_TRANSLATE("Stop"), { B_EXECUTE_PROPERTY },
126 { B_DIRECT_SPECIFIER, 0 },
127 B_TRANSLATE("Stop playing."), 0
129 { B_TRANSLATE("Pause"), { B_EXECUTE_PROPERTY },
130 { B_DIRECT_SPECIFIER, 0 },
131 B_TRANSLATE("Pause playback."), 0
133 { B_TRANSLATE("TogglePlaying"), { B_EXECUTE_PROPERTY },
134 { B_DIRECT_SPECIFIER, 0 },
135 B_TRANSLATE("Toggle pause/play."), 0
137 { B_TRANSLATE("Mute"), { B_EXECUTE_PROPERTY },
138 { B_DIRECT_SPECIFIER, 0 },
139 B_TRANSLATE("Toggle mute."), 0
141 { B_TRANSLATE("Volume"), { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
142 { B_DIRECT_SPECIFIER, 0 },
143 B_TRANSLATE("Gets/sets the volume (0.0-2.0)."), 0,
144 { B_FLOAT_TYPE }
146 { B_TRANSLATE("URI"), { B_GET_PROPERTY, 0 },
147 { B_DIRECT_SPECIFIER, 0 },
148 B_TRANSLATE("Gets the URI of the currently playing item."), 0,
149 { B_STRING_TYPE }
151 { B_TRANSLATE("ToggleFullscreen"), { B_EXECUTE_PROPERTY },
152 { B_DIRECT_SPECIFIER, 0 },
153 B_TRANSLATE("Toggle fullscreen."), 0
155 { B_TRANSLATE("Duration"), { B_GET_PROPERTY, 0 },
156 { B_DIRECT_SPECIFIER, 0 },
157 B_TRANSLATE("Gets the duration of the currently playing item "
158 "in microseconds."), 0,
159 { B_INT64_TYPE }
161 { B_TRANSLATE("Position"), { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
162 { B_DIRECT_SPECIFIER, 0 },
163 B_TRANSLATE("Gets/sets the current playing position in microseconds."),
164 0, { B_INT64_TYPE }
166 { B_TRANSLATE("Seek"), { B_SET_PROPERTY },
167 { B_DIRECT_SPECIFIER, 0 },
168 B_TRANSLATE("Seek by the specified amounts of microseconds."), 0,
169 { B_INT64_TYPE }
172 { 0 }
176 static const char* kRatingAttrName = "Media:Rating";
178 static const char* kDisabledSeekMessage = B_TRANSLATE("Drop files to play");
180 static const char* kApplicationName = B_TRANSLATE_SYSTEM_NAME(NAME);
183 MainWin::MainWin(bool isFirstWindow, BMessage* message)
185 BWindow(BRect(100, 100, 400, 300), kApplicationName, B_TITLED_WINDOW,
186 B_ASYNCHRONOUS_CONTROLS),
187 fCreationTime(system_time()),
188 fInfoWin(NULL),
189 fPlaylistWindow(NULL),
190 fHasFile(false),
191 fHasVideo(false),
192 fHasAudio(false),
193 fPlaylist(new Playlist),
194 fPlaylistObserver(new PlaylistObserver(this)),
195 fController(new Controller),
196 fControllerObserver(new ControllerObserver(this,
197 OBSERVE_FILE_CHANGES | OBSERVE_TRACK_CHANGES
198 | OBSERVE_PLAYBACK_STATE_CHANGES | OBSERVE_POSITION_CHANGES
199 | OBSERVE_VOLUME_CHANGES)),
200 fIsFullscreen(false),
201 fAlwaysOnTop(false),
202 fNoInterface(false),
203 fShowsFullscreenControls(false),
204 fSourceWidth(-1),
205 fSourceHeight(-1),
206 fWidthAspect(0),
207 fHeightAspect(0),
208 fSavedFrame(),
209 fNoVideoFrame(),
211 fMouseDownTracking(false),
212 fLastMousePos(0, 0),
213 fLastMouseMovedTime(system_time()),
214 fMouseMoveDist(0),
216 fGlobalSettingsListener(this),
217 fInitialSeekPosition(0),
218 fAllowWinding(true)
220 // Handle window position and size depending on whether this is the
221 // first window or not. Use the window size from the window that was
222 // last resized by the user.
223 static int pos = 0;
224 MoveBy(pos * 25, pos * 25);
225 pos = (pos + 1) % 15;
227 BRect frame = Settings::Default()->AudioPlayerWindowFrame();
228 if (frame.IsValid()) {
229 if (isFirstWindow) {
230 if (message == NULL) {
231 MoveTo(frame.LeftTop());
232 ResizeTo(frame.Width(), frame.Height());
233 } else {
234 // Delay moving to the initial position, since we don't
235 // know if we will be playing audio at all.
236 message->AddRect("window frame", frame);
239 if (sNoVideoWidth == MIN_WIDTH)
240 sNoVideoWidth = frame.IntegerWidth();
241 } else if (sNoVideoWidth > MIN_WIDTH) {
242 ResizeTo(sNoVideoWidth, Bounds().Height());
244 fNoVideoWidth = sNoVideoWidth;
246 BRect rect = Bounds();
248 // background
249 fBackground = new BView(rect, "background", B_FOLLOW_ALL,
250 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
251 fBackground->SetViewColor(0, 0, 0);
252 AddChild(fBackground);
254 // menu
255 fMenuBar = new BMenuBar(fBackground->Bounds(), "menu");
256 _CreateMenu();
257 fBackground->AddChild(fMenuBar);
258 fMenuBar->SetResizingMode(B_FOLLOW_NONE);
259 fMenuBar->ResizeToPreferred();
260 fMenuBarWidth = (int)fMenuBar->Frame().Width() + 1;
261 fMenuBarHeight = (int)fMenuBar->Frame().Height() + 1;
263 // video view
264 rect = BRect(0, fMenuBarHeight, fBackground->Bounds().right,
265 fMenuBarHeight + 10);
266 fVideoView = new VideoView(rect, "video display", B_FOLLOW_NONE);
267 fBackground->AddChild(fVideoView);
269 // controls
270 rect = BRect(0, fMenuBarHeight + 11, fBackground->Bounds().right,
271 fBackground->Bounds().bottom);
272 fControls = new ControllerView(rect, fController, fPlaylist);
273 fBackground->AddChild(fControls);
274 fControls->ResizeToPreferred();
275 fControlsHeight = (int)fControls->Frame().Height() + 1;
276 fControlsWidth = (int)fControls->Frame().Width() + 1;
277 fControls->SetResizingMode(B_FOLLOW_BOTTOM | B_FOLLOW_LEFT_RIGHT);
278 fControls->SetDisabledString(kDisabledSeekMessage);
280 fPlaylist->AddListener(fPlaylistObserver);
281 fController->SetVideoView(fVideoView);
282 fController->AddListener(fControllerObserver);
283 PeakView* peakView = fControls->GetPeakView();
284 peakView->SetPeakNotificationWhat(MSG_PEAK_NOTIFICATION);
285 fController->SetPeakListener(peakView);
287 _SetupWindow();
289 // setup the playlist window now, we need to have it
290 // running for the undo/redo playlist editing
291 fPlaylistWindow = new PlaylistWindow(BRect(150, 150, 500, 600), fPlaylist,
292 fController);
293 fPlaylistWindow->Hide();
294 fPlaylistWindow->Show();
295 // this makes sure the window thread is running without
296 // showing the window just yet
298 Settings::Default()->AddListener(&fGlobalSettingsListener);
299 _AdoptGlobalSettings();
301 AddShortcut('z', B_COMMAND_KEY, new BMessage(B_UNDO));
302 AddShortcut('y', B_COMMAND_KEY, new BMessage(B_UNDO));
303 AddShortcut('z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO));
304 AddShortcut('y', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO));
306 Hide();
307 Show();
309 if (message != NULL)
310 PostMessage(message);
312 BMediaRoster* roster = BMediaRoster::Roster();
313 roster->StartWatching(BMessenger(this, this), B_MEDIA_SERVER_STARTED);
314 roster->StartWatching(BMessenger(this, this), B_MEDIA_SERVER_QUIT);
318 MainWin::~MainWin()
320 // printf("MainWin::~MainWin\n");
322 BMediaRoster* roster = BMediaRoster::CurrentRoster();
323 roster->StopWatching(BMessenger(this, this), B_MEDIA_SERVER_STARTED);
324 roster->StopWatching(BMessenger(this, this), B_MEDIA_SERVER_QUIT);
326 Settings::Default()->RemoveListener(&fGlobalSettingsListener);
327 fPlaylist->RemoveListener(fPlaylistObserver);
328 fController->Lock();
329 fController->RemoveListener(fControllerObserver);
330 fController->SetPeakListener(NULL);
331 fController->SetVideoTarget(NULL);
332 fController->Unlock();
334 // give the views a chance to detach from any notifiers
335 // before we delete them
336 fBackground->RemoveSelf();
337 delete fBackground;
339 if (fInfoWin && fInfoWin->Lock())
340 fInfoWin->Quit();
342 if (fPlaylistWindow && fPlaylistWindow->Lock())
343 fPlaylistWindow->Quit();
345 delete fPlaylist;
346 fPlaylist = NULL;
348 // quit the Controller looper thread
349 thread_id controllerThread = fController->Thread();
350 fController->PostMessage(B_QUIT_REQUESTED);
351 status_t exitValue;
352 wait_for_thread(controllerThread, &exitValue);
356 // #pragma mark -
359 void
360 MainWin::FrameResized(float newWidth, float newHeight)
362 if (newWidth != Bounds().Width() || newHeight != Bounds().Height()) {
363 debugger("size wrong\n");
366 bool noMenu = fNoInterface || fIsFullscreen;
367 bool noControls = fNoInterface || fIsFullscreen;
369 // printf("FrameResized enter: newWidth %.0f, newHeight %.0f\n",
370 // newWidth, newHeight);
372 if (!fHasVideo)
373 sNoVideoWidth = fNoVideoWidth = (int)newWidth;
375 int maxVideoWidth = int(newWidth) + 1;
376 int maxVideoHeight = int(newHeight) + 1
377 - (noMenu ? 0 : fMenuBarHeight)
378 - (noControls ? 0 : fControlsHeight);
380 ASSERT(maxVideoHeight >= 0);
382 int y = 0;
384 if (noMenu) {
385 if (!fMenuBar->IsHidden(fMenuBar))
386 fMenuBar->Hide();
387 } else {
388 fMenuBar->MoveTo(0, y);
389 fMenuBar->ResizeTo(newWidth, fMenuBarHeight - 1);
390 if (fMenuBar->IsHidden(fMenuBar))
391 fMenuBar->Show();
392 y += fMenuBarHeight;
395 if (maxVideoHeight == 0) {
396 if (!fVideoView->IsHidden(fVideoView))
397 fVideoView->Hide();
398 } else {
399 _ResizeVideoView(0, y, maxVideoWidth, maxVideoHeight);
400 if (fVideoView->IsHidden(fVideoView))
401 fVideoView->Show();
402 y += maxVideoHeight;
405 if (noControls) {
406 if (!fControls->IsHidden(fControls))
407 fControls->Hide();
408 } else {
409 fControls->MoveTo(0, y);
410 fControls->ResizeTo(newWidth, fControlsHeight - 1);
411 if (fControls->IsHidden(fControls))
412 fControls->Show();
413 // y += fControlsHeight;
416 // printf("FrameResized leave\n");
420 void
421 MainWin::Zoom(BPoint /*position*/, float /*width*/, float /*height*/)
423 PostMessage(M_TOGGLE_FULLSCREEN);
427 void
428 MainWin::DispatchMessage(BMessage* msg, BHandler* handler)
430 if ((msg->what == B_MOUSE_DOWN)
431 && (handler == fBackground || handler == fVideoView
432 || handler == fControls)) {
433 _MouseDown(msg, dynamic_cast<BView*>(handler));
436 if ((msg->what == B_MOUSE_MOVED)
437 && (handler == fBackground || handler == fVideoView
438 || handler == fControls)) {
439 _MouseMoved(msg, dynamic_cast<BView*>(handler));
442 if ((msg->what == B_MOUSE_UP)
443 && (handler == fBackground || handler == fVideoView)) {
444 _MouseUp(msg);
447 if ((msg->what == B_KEY_DOWN)
448 && (handler == fBackground || handler == fVideoView)) {
449 // special case for PrintScreen key
450 if (msg->FindInt32("key") == B_PRINT_KEY) {
451 fVideoView->OverlayScreenshotPrepare();
452 BWindow::DispatchMessage(msg, handler);
453 fVideoView->OverlayScreenshotCleanup();
454 return;
457 // every other key gets dispatched to our _KeyDown first
458 if (_KeyDown(msg)) {
459 // it got handled, don't pass it on
460 return;
464 BWindow::DispatchMessage(msg, handler);
468 void
469 MainWin::MessageReceived(BMessage* msg)
471 // msg->PrintToStream();
472 switch (msg->what) {
473 case B_EXECUTE_PROPERTY:
474 case B_GET_PROPERTY:
475 case B_SET_PROPERTY:
477 BMessage reply(B_REPLY);
478 status_t result = B_BAD_SCRIPT_SYNTAX;
479 int32 index;
480 BMessage specifier;
481 int32 what;
482 const char* property;
484 if (msg->GetCurrentSpecifier(&index, &specifier, &what,
485 &property) != B_OK) {
486 return BWindow::MessageReceived(msg);
489 BPropertyInfo propertyInfo(sPropertyInfo);
490 switch (propertyInfo.FindMatch(msg, index, &specifier, what,
491 property)) {
492 case 0:
493 fControls->SkipForward();
494 result = B_OK;
495 break;
497 case 1:
498 fControls->SkipBackward();
499 result = B_OK;
500 break;
502 case 2:
503 fController->Play();
504 result = B_OK;
505 break;
507 case 3:
508 fController->Stop();
509 result = B_OK;
510 break;
512 case 4:
513 fController->Pause();
514 result = B_OK;
515 break;
517 case 5:
518 fController->TogglePlaying();
519 result = B_OK;
520 break;
522 case 6:
523 fController->ToggleMute();
524 result = B_OK;
525 break;
527 case 7:
529 if (msg->what == B_GET_PROPERTY) {
530 result = reply.AddFloat("result",
531 fController->Volume());
532 } else if (msg->what == B_SET_PROPERTY) {
533 float newVolume;
534 result = msg->FindFloat("data", &newVolume);
535 if (result == B_OK)
536 fController->SetVolume(newVolume);
538 break;
541 case 8:
543 if (msg->what == B_GET_PROPERTY) {
544 BAutolock _(fPlaylist);
545 const PlaylistItem* item = fController->Item();
546 if (item == NULL) {
547 result = B_NO_INIT;
548 break;
551 result = reply.AddString("result", item->LocationURI());
553 break;
556 case 9:
557 PostMessage(M_TOGGLE_FULLSCREEN);
558 break;
560 case 10:
561 if (msg->what != B_GET_PROPERTY)
562 break;
564 result = reply.AddInt64("result",
565 fController->TimeDuration());
566 break;
568 case 11:
570 if (msg->what == B_GET_PROPERTY) {
571 result = reply.AddInt64("result",
572 fController->TimePosition());
573 } else if (msg->what == B_SET_PROPERTY) {
574 int64 newTime;
575 result = msg->FindInt64("data", &newTime);
576 if (result == B_OK)
577 fController->SetTimePosition(newTime);
580 break;
583 case 12:
585 if (msg->what != B_SET_PROPERTY)
586 break;
588 bigtime_t seekBy;
589 result = msg->FindInt64("data", &seekBy);
590 if (result != B_OK)
591 break;
593 _Wind(seekBy, 0);
594 break;
597 default:
598 return BWindow::MessageReceived(msg);
601 if (result != B_OK) {
602 reply.what = B_MESSAGE_NOT_UNDERSTOOD;
603 reply.AddString("message", strerror(result));
604 reply.AddInt32("error", result);
607 msg->SendReply(&reply);
608 break;
611 case B_REFS_RECEIVED:
612 case M_URL_RECEIVED:
613 _RefsReceived(msg);
614 break;
616 case B_SIMPLE_DATA:
617 if (msg->HasRef("refs"))
618 _RefsReceived(msg);
619 break;
620 case M_OPEN_PREVIOUS_PLAYLIST:
621 OpenPlaylist(msg);
622 break;
624 case B_UNDO:
625 case B_REDO:
626 fPlaylistWindow->PostMessage(msg);
627 break;
629 case B_MEDIA_SERVER_STARTED:
631 printf("TODO: implement B_MEDIA_SERVER_STARTED\n");
633 // BAutolock _(fPlaylist);
634 // BMessage fakePlaylistMessage(MSG_PLAYLIST_CURRENT_ITEM_CHANGED);
635 // fakePlaylistMessage.AddInt32("index",
636 // fPlaylist->CurrentItemIndex());
637 // PostMessage(&fakePlaylistMessage);
638 break;
641 case B_MEDIA_SERVER_QUIT:
642 printf("TODO: implement B_MEDIA_SERVER_QUIT\n");
643 // if (fController->Lock()) {
644 // fController->CleanupNodes();
645 // fController->Unlock();
646 // }
647 break;
649 // PlaylistObserver messages
650 case MSG_PLAYLIST_ITEM_ADDED:
652 PlaylistItem* item;
653 int32 index;
654 if (msg->FindPointer("item", (void**)&item) == B_OK
655 && msg->FindInt32("index", &index) == B_OK) {
656 _AddPlaylistItem(item, index);
658 break;
660 case MSG_PLAYLIST_ITEM_REMOVED:
662 int32 index;
663 if (msg->FindInt32("index", &index) == B_OK)
664 _RemovePlaylistItem(index);
665 break;
667 case MSG_PLAYLIST_CURRENT_ITEM_CHANGED:
669 BAutolock _(fPlaylist);
671 int32 index;
672 // if false, the message was meant to only update the GUI
673 bool play;
674 if (msg->FindBool("play", &play) < B_OK || !play)
675 break;
676 if (msg->FindInt32("index", &index) < B_OK
677 || index != fPlaylist->CurrentItemIndex())
678 break;
679 PlaylistItemRef item(fPlaylist->ItemAt(index));
680 if (item.Get() != NULL) {
681 printf("open playlist item: %s\n", item->Name().String());
682 OpenPlaylistItem(item);
683 _MarkPlaylistItem(index);
685 break;
687 case MSG_PLAYLIST_IMPORT_FAILED:
689 BAlert* alert = new BAlert(B_TRANSLATE("Nothing to Play"),
690 B_TRANSLATE("None of the files you wanted to play appear "
691 "to be media files."), B_TRANSLATE("OK"));
692 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
693 alert->Go();
694 fControls->SetDisabledString(kDisabledSeekMessage);
695 break;
698 // ControllerObserver messages
699 case MSG_CONTROLLER_FILE_FINISHED:
701 BAutolock _(fPlaylist);
703 bool hadNext = fPlaylist->SetCurrentItemIndex(
704 fPlaylist->CurrentItemIndex() + 1);
705 if (!hadNext) {
706 // Reached end of playlist
707 // Handle "quit when done" settings
708 if ((fHasVideo && fCloseWhenDonePlayingMovie)
709 || (!fHasVideo && fCloseWhenDonePlayingSound))
710 PostMessage(B_QUIT_REQUESTED);
711 // Handle "loop by default" settings
712 if ((fHasVideo && fLoopMovies)
713 || (!fHasVideo && fLoopSounds)) {
714 if (fPlaylist->CountItems() > 1)
715 fPlaylist->SetCurrentItemIndex(0);
716 else
717 fController->Play();
720 break;
722 case MSG_CONTROLLER_FILE_CHANGED:
724 status_t result = B_ERROR;
725 msg->FindInt32("result", &result);
726 PlaylistItemRef itemRef;
727 PlaylistItem* item;
728 if (msg->FindPointer("item", (void**)&item) == B_OK) {
729 itemRef.SetTo(item, true);
730 // The reference was passed along with the message.
731 } else {
732 BAutolock _(fPlaylist);
733 itemRef.SetTo(fPlaylist->ItemAt(
734 fPlaylist->CurrentItemIndex()));
736 _PlaylistItemOpened(itemRef, result);
737 break;
739 case MSG_CONTROLLER_VIDEO_TRACK_CHANGED:
741 int32 index;
742 if (msg->FindInt32("index", &index) == B_OK) {
743 int32 i = 0;
744 while (BMenuItem* item = fVideoTrackMenu->ItemAt(i)) {
745 item->SetMarked(i == index);
746 i++;
749 break;
751 case MSG_CONTROLLER_AUDIO_TRACK_CHANGED:
753 int32 index;
754 if (msg->FindInt32("index", &index) == B_OK) {
755 int32 i = 0;
756 while (BMenuItem* item = fAudioTrackMenu->ItemAt(i)) {
757 item->SetMarked(i == index);
758 i++;
760 _UpdateAudioChannelCount(index);
762 break;
764 case MSG_CONTROLLER_SUB_TITLE_TRACK_CHANGED:
766 int32 index;
767 if (msg->FindInt32("index", &index) == B_OK) {
768 int32 i = 0;
769 while (BMenuItem* item = fSubTitleTrackMenu->ItemAt(i)) {
770 BMessage* message = item->Message();
771 if (message != NULL) {
772 item->SetMarked((int32)message->what
773 - M_SELECT_SUB_TITLE_TRACK == index);
775 i++;
778 break;
780 case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED:
782 uint32 state;
783 if (msg->FindInt32("state", (int32*)&state) == B_OK)
784 fControls->SetPlaybackState(state);
785 break;
787 case MSG_CONTROLLER_POSITION_CHANGED:
789 float position;
790 if (msg->FindFloat("position", &position) == B_OK) {
791 fControls->SetPosition(position, fController->TimePosition(),
792 fController->TimeDuration());
793 fAllowWinding = true;
795 break;
797 case MSG_CONTROLLER_SEEK_HANDLED:
798 break;
800 case MSG_CONTROLLER_VOLUME_CHANGED:
802 float volume;
803 if (msg->FindFloat("volume", &volume) == B_OK)
804 fControls->SetVolume(volume);
805 break;
807 case MSG_CONTROLLER_MUTED_CHANGED:
809 bool muted;
810 if (msg->FindBool("muted", &muted) == B_OK)
811 fControls->SetMuted(muted);
812 break;
815 // menu item messages
816 case M_FILE_OPEN:
818 BMessenger target(this);
819 BMessage result(B_REFS_RECEIVED);
820 BMessage appMessage(M_SHOW_OPEN_PANEL);
821 appMessage.AddMessenger("target", target);
822 appMessage.AddMessage("message", &result);
823 appMessage.AddString("title", B_TRANSLATE("Open clips"));
824 appMessage.AddString("label", B_TRANSLATE("Open"));
825 be_app->PostMessage(&appMessage);
826 break;
829 case M_NETWORK_STREAM_OPEN:
831 BMessenger target(this);
832 NetworkStreamWin* win = new NetworkStreamWin(target);
833 win->Show();
834 break;
837 case M_FILE_INFO:
838 ShowFileInfo();
839 break;
840 case M_FILE_PLAYLIST:
841 ShowPlaylistWindow();
842 break;
843 case M_FILE_CLOSE:
844 PostMessage(B_QUIT_REQUESTED);
845 break;
846 case M_FILE_QUIT:
847 be_app->PostMessage(B_QUIT_REQUESTED);
848 break;
850 case M_TOGGLE_FULLSCREEN:
851 _ToggleFullscreen();
852 break;
854 case M_TOGGLE_ALWAYS_ON_TOP:
855 _ToggleAlwaysOnTop();
856 break;
858 case M_TOGGLE_NO_INTERFACE:
859 _ToggleNoInterface();
860 break;
862 case M_VIEW_SIZE:
864 int32 size;
865 if (msg->FindInt32("size", &size) == B_OK) {
866 if (!fHasVideo)
867 break;
868 if (fIsFullscreen)
869 _ToggleFullscreen();
870 _ResizeWindow(size);
872 break;
876 case B_ACQUIRE_OVERLAY_LOCK:
877 printf("B_ACQUIRE_OVERLAY_LOCK\n");
878 fVideoView->OverlayLockAcquire();
879 break;
881 case B_RELEASE_OVERLAY_LOCK:
882 printf("B_RELEASE_OVERLAY_LOCK\n");
883 fVideoView->OverlayLockRelease();
884 break;
886 case B_MOUSE_WHEEL_CHANGED:
888 float dx = msg->FindFloat("be:wheel_delta_x");
889 float dy = msg->FindFloat("be:wheel_delta_y");
890 bool inv = modifiers() & B_COMMAND_KEY;
891 if (dx > 0.1)
892 PostMessage(inv ? M_VOLUME_DOWN : M_SKIP_PREV);
893 if (dx < -0.1)
894 PostMessage(inv ? M_VOLUME_UP : M_SKIP_NEXT);
895 if (dy > 0.1)
896 PostMessage(inv ? M_SKIP_PREV : M_VOLUME_DOWN);
897 if (dy < -0.1)
898 PostMessage(inv ? M_SKIP_NEXT : M_VOLUME_UP);
899 break;
902 case M_SKIP_NEXT:
903 fControls->SkipForward();
904 break;
906 case M_SKIP_PREV:
907 fControls->SkipBackward();
908 break;
910 case M_WIND:
912 bigtime_t howMuch;
913 int64 frames;
914 if (msg->FindInt64("how much", &howMuch) != B_OK
915 || msg->FindInt64("frames", &frames) != B_OK) {
916 break;
919 _Wind(howMuch, frames);
920 break;
923 case M_VOLUME_UP:
924 fController->VolumeUp();
925 break;
927 case M_VOLUME_DOWN:
928 fController->VolumeDown();
929 break;
931 case M_ASPECT_SAME_AS_SOURCE:
932 if (fHasVideo) {
933 int width;
934 int height;
935 int widthAspect;
936 int heightAspect;
937 fController->GetSize(&width, &height,
938 &widthAspect, &heightAspect);
939 VideoFormatChange(width, height, widthAspect, heightAspect);
941 break;
943 case M_ASPECT_NO_DISTORTION:
944 if (fHasVideo) {
945 int width;
946 int height;
947 fController->GetSize(&width, &height);
948 VideoFormatChange(width, height, width, height);
950 break;
952 case M_ASPECT_4_3:
953 VideoAspectChange(4, 3);
954 break;
956 case M_ASPECT_16_9: // 1.77 : 1
957 VideoAspectChange(16, 9);
958 break;
960 case M_ASPECT_83_50: // 1.66 : 1
961 VideoAspectChange(83, 50);
962 break;
964 case M_ASPECT_7_4: // 1.75 : 1
965 VideoAspectChange(7, 4);
966 break;
968 case M_ASPECT_37_20: // 1.85 : 1
969 VideoAspectChange(37, 20);
970 break;
972 case M_ASPECT_47_20: // 2.35 : 1
973 VideoAspectChange(47, 20);
974 break;
976 case M_SET_PLAYLIST_POSITION:
978 BAutolock _(fPlaylist);
980 int32 index;
981 if (msg->FindInt32("index", &index) == B_OK)
982 fPlaylist->SetCurrentItemIndex(index);
983 break;
986 case MSG_OBJECT_CHANGED:
987 // received from fGlobalSettingsListener
988 // TODO: find out which object, if we ever watch more than
989 // the global settings instance...
990 _AdoptGlobalSettings();
991 break;
993 case M_SLIDE_CONTROLS:
995 float offset;
996 if (msg->FindFloat("offset", &offset) == B_OK) {
997 fControls->MoveBy(0, offset);
998 fVideoView->SetSubTitleMaxBottom(fControls->Frame().top - 1);
999 UpdateIfNeeded();
1000 snooze(15000);
1002 break;
1004 case M_FINISH_SLIDING_CONTROLS:
1006 float offset;
1007 bool show;
1008 if (msg->FindFloat("offset", &offset) == B_OK
1009 && msg->FindBool("show", &show) == B_OK) {
1010 if (show) {
1011 fControls->MoveTo(fControls->Frame().left, offset);
1012 fVideoView->SetSubTitleMaxBottom(offset - 1);
1013 } else {
1014 fVideoView->SetSubTitleMaxBottom(
1015 fVideoView->Bounds().bottom);
1016 fControls->RemoveSelf();
1017 fControls->MoveTo(fVideoView->Frame().left,
1018 fVideoView->Frame().bottom + 1);
1019 fBackground->AddChild(fControls);
1020 fControls->SetSymbolScale(1.0f);
1021 while (!fControls->IsHidden())
1022 fControls->Hide();
1025 break;
1027 case M_HIDE_FULL_SCREEN_CONTROLS:
1028 if (fIsFullscreen) {
1029 BPoint videoViewWhere;
1030 if (msg->FindPoint("where", &videoViewWhere) == B_OK) {
1031 if (msg->FindBool("force")
1032 || !fControls->Frame().Contains(videoViewWhere)) {
1033 _ShowFullscreenControls(false);
1034 // hide the mouse cursor until the user moves it
1035 be_app->ObscureCursor();
1039 break;
1041 case M_SET_RATING:
1043 int32 rating;
1044 if (msg->FindInt32("rating", &rating) == B_OK)
1045 _SetRating(rating);
1046 break;
1049 default:
1050 if (msg->what >= M_SELECT_AUDIO_TRACK
1051 && msg->what <= M_SELECT_AUDIO_TRACK_END) {
1052 fController->SelectAudioTrack(msg->what - M_SELECT_AUDIO_TRACK);
1053 break;
1055 if (msg->what >= M_SELECT_VIDEO_TRACK
1056 && msg->what <= M_SELECT_VIDEO_TRACK_END) {
1057 fController->SelectVideoTrack(msg->what - M_SELECT_VIDEO_TRACK);
1058 break;
1060 if ((int32)msg->what >= M_SELECT_SUB_TITLE_TRACK - 1
1061 && msg->what <= M_SELECT_SUB_TITLE_TRACK_END) {
1062 fController->SelectSubTitleTrack((int32)msg->what
1063 - M_SELECT_SUB_TITLE_TRACK);
1064 break;
1066 // let BWindow handle the rest
1067 BWindow::MessageReceived(msg);
1072 void
1073 MainWin::WindowActivated(bool active)
1075 fController->PlayerActivated(active);
1079 bool
1080 MainWin::QuitRequested()
1082 BMessage message(M_PLAYER_QUIT);
1083 GetQuitMessage(&message);
1084 be_app->PostMessage(&message);
1085 return true;
1089 void
1090 MainWin::MenusBeginning()
1092 _SetupVideoAspectItems(fVideoAspectMenu);
1096 // #pragma mark -
1099 void
1100 MainWin::OpenPlaylist(const BMessage* playlistArchive)
1102 if (playlistArchive == NULL)
1103 return;
1105 BAutolock _(this);
1106 BAutolock playlistLocker(fPlaylist);
1108 if (fPlaylist->Unarchive(playlistArchive) != B_OK)
1109 return;
1111 int32 currentIndex;
1112 if (playlistArchive->FindInt32("index", &currentIndex) != B_OK)
1113 currentIndex = 0;
1114 fPlaylist->SetCurrentItemIndex(currentIndex);
1116 playlistLocker.Unlock();
1118 if (currentIndex != -1) {
1119 // Restore the current play position only if we have something to play
1120 playlistArchive->FindInt64("position", (int64*)&fInitialSeekPosition);
1123 if (IsHidden())
1124 Show();
1128 void
1129 MainWin::OpenPlaylistItem(const PlaylistItemRef& item)
1131 status_t ret = fController->SetToAsync(item);
1132 if (ret != B_OK) {
1133 fprintf(stderr, "MainWin::OpenPlaylistItem() - Failed to send message "
1134 "to Controller.\n");
1135 BString message = B_TRANSLATE("%app% encountered an internal error. "
1136 "The file could not be opened.");
1137 message.ReplaceFirst("%app%", kApplicationName);
1138 BAlert* alert = new BAlert(kApplicationName, message.String(),
1139 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1140 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1141 alert->Go();
1142 _PlaylistItemOpened(item, ret);
1143 } else {
1144 BString string;
1145 string << "Opening '" << item->Name() << "'.";
1146 fControls->SetDisabledString(string.String());
1151 void
1152 MainWin::ShowFileInfo()
1154 if (!fInfoWin)
1155 fInfoWin = new InfoWin(Frame().LeftTop(), fController);
1157 if (fInfoWin->Lock()) {
1158 if (fInfoWin->IsHidden())
1159 fInfoWin->Show();
1160 else
1161 fInfoWin->Activate();
1162 fInfoWin->Unlock();
1167 void
1168 MainWin::ShowPlaylistWindow()
1170 if (fPlaylistWindow->Lock()) {
1171 // make sure the window shows on the same workspace as ourself
1172 uint32 workspaces = Workspaces();
1173 if (fPlaylistWindow->Workspaces() != workspaces)
1174 fPlaylistWindow->SetWorkspaces(workspaces);
1176 // show or activate
1177 if (fPlaylistWindow->IsHidden())
1178 fPlaylistWindow->Show();
1179 else
1180 fPlaylistWindow->Activate();
1182 fPlaylistWindow->Unlock();
1187 void
1188 MainWin::VideoAspectChange(int forcedWidth, int forcedHeight, float widthScale)
1190 // Force specific source size and pixel width scale.
1191 if (fHasVideo) {
1192 int width;
1193 int height;
1194 fController->GetSize(&width, &height);
1195 VideoFormatChange(forcedWidth, forcedHeight,
1196 lround(width * widthScale), height);
1201 void
1202 MainWin::VideoAspectChange(float widthScale)
1204 // Called when video aspect ratio changes and the original
1205 // width/height should be restored too, display aspect is not known,
1206 // only pixel width scale.
1207 if (fHasVideo) {
1208 int width;
1209 int height;
1210 fController->GetSize(&width, &height);
1211 VideoFormatChange(width, height, lround(width * widthScale), height);
1216 void
1217 MainWin::VideoAspectChange(int widthAspect, int heightAspect)
1219 // Called when video aspect ratio changes and the original
1220 // width/height should be restored too.
1221 if (fHasVideo) {
1222 int width;
1223 int height;
1224 fController->GetSize(&width, &height);
1225 VideoFormatChange(width, height, widthAspect, heightAspect);
1230 void
1231 MainWin::VideoFormatChange(int width, int height, int widthAspect,
1232 int heightAspect)
1234 // Called when video format or aspect ratio changes.
1236 printf("VideoFormatChange enter: width %d, height %d, "
1237 "aspect ratio: %d:%d\n", width, height, widthAspect, heightAspect);
1239 // remember current view scale
1240 int percent = _CurrentVideoSizeInPercent();
1242 fSourceWidth = width;
1243 fSourceHeight = height;
1244 fWidthAspect = widthAspect;
1245 fHeightAspect = heightAspect;
1247 if (percent == 100)
1248 _ResizeWindow(100);
1249 else
1250 FrameResized(Bounds().Width(), Bounds().Height());
1252 printf("VideoFormatChange leave\n");
1256 void
1257 MainWin::GetQuitMessage(BMessage* message)
1259 message->AddPointer("instance", this);
1260 message->AddRect("window frame", Frame());
1261 message->AddBool("audio only", !fHasVideo);
1262 message->AddInt64("creation time", fCreationTime);
1264 if (!fHasVideo && fHasAudio) {
1265 // store playlist, current index and position if this is audio
1266 BMessage playlistArchive;
1268 BAutolock controllerLocker(fController);
1269 playlistArchive.AddInt64("position", fController->TimePosition());
1270 controllerLocker.Unlock();
1272 if (!fPlaylist)
1273 return;
1275 BAutolock playlistLocker(fPlaylist);
1276 if (fPlaylist->Archive(&playlistArchive) != B_OK
1277 || playlistArchive.AddInt32("index",
1278 fPlaylist->CurrentItemIndex()) != B_OK
1279 || message->AddMessage("playlist", &playlistArchive) != B_OK) {
1280 fprintf(stderr, "Failed to store current playlist.\n");
1286 BHandler*
1287 MainWin::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
1288 int32 what, const char* property)
1290 BPropertyInfo propertyInfo(sPropertyInfo);
1291 switch (propertyInfo.FindMatch(message, index, specifier, what, property)) {
1292 case 0:
1293 case 1:
1294 case 2:
1295 case 3:
1296 case 4:
1297 case 5:
1298 case 6:
1299 case 7:
1300 case 8:
1301 case 9:
1302 case 10:
1303 case 11:
1304 case 12:
1305 return this;
1308 return BWindow::ResolveSpecifier(message, index, specifier, what, property);
1312 status_t
1313 MainWin::GetSupportedSuites(BMessage* data)
1315 if (data == NULL)
1316 return B_BAD_VALUE;
1318 status_t status = data->AddString("suites", "suite/vnd.Haiku-MediaPlayer");
1319 if (status != B_OK)
1320 return status;
1322 BPropertyInfo propertyInfo(sPropertyInfo);
1323 status = data->AddFlat("messages", &propertyInfo);
1324 if (status != B_OK)
1325 return status;
1327 return BWindow::GetSupportedSuites(data);
1331 // #pragma mark -
1334 void
1335 MainWin::_RefsReceived(BMessage* message)
1337 // the playlist is replaced by dropped files
1338 // or the dropped files are appended to the end
1339 // of the existing playlist if <shift> is pressed
1340 bool append = false;
1341 if (message->FindBool("append to playlist", &append) != B_OK)
1342 append = modifiers() & B_SHIFT_KEY;
1344 BAutolock _(fPlaylist);
1345 int32 appendIndex = append ? APPEND_INDEX_APPEND_LAST
1346 : APPEND_INDEX_REPLACE_PLAYLIST;
1347 message->AddInt32("append_index", appendIndex);
1349 // forward the message to the playlist window,
1350 // so that undo/redo is used for modifying the playlist
1351 fPlaylistWindow->PostMessage(message);
1353 if (message->FindRect("window frame", &fNoVideoFrame) != B_OK)
1354 fNoVideoFrame = BRect();
1358 void
1359 MainWin::_PlaylistItemOpened(const PlaylistItemRef& item, status_t result)
1361 if (result != B_OK) {
1362 BAutolock _(fPlaylist);
1364 item->SetPlaybackFailed();
1365 bool allItemsFailed = true;
1366 int32 count = fPlaylist->CountItems();
1367 for (int32 i = 0; i < count; i++) {
1368 if (!fPlaylist->ItemAtFast(i)->PlaybackFailed()) {
1369 allItemsFailed = false;
1370 break;
1374 if (allItemsFailed) {
1375 // Display error if all files failed to play.
1376 BString message(B_TRANSLATE(
1377 "The file '%filename' could not be opened.\n\n"));;
1378 message.ReplaceAll("%filename", item->Name());
1380 if (result == B_MEDIA_NO_HANDLER) {
1381 // give a more detailed message for the most likely of all
1382 // errors
1383 message << B_TRANSLATE(
1384 "There is no decoder installed to handle the "
1385 "file format, or the decoder has trouble with the "
1386 "specific version of the format.");
1387 } else {
1388 message << B_TRANSLATE("Error: ") << strerror(result);
1390 BAlert* alert = new BAlert("error", message.String(),
1391 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1392 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1393 alert->Go();
1394 fControls->SetDisabledString(kDisabledSeekMessage);
1395 } else {
1396 // Just go to the next file and don't bother user (yet)
1397 fPlaylist->SetCurrentItemIndex(fPlaylist->CurrentItemIndex() + 1);
1400 fHasFile = false;
1401 fHasVideo = false;
1402 fHasAudio = false;
1403 SetTitle(kApplicationName);
1404 } else {
1405 fHasFile = true;
1406 fHasVideo = fController->VideoTrackCount() != 0;
1407 fHasAudio = fController->AudioTrackCount() != 0;
1408 SetTitle(item->Name().String());
1410 if (fInitialSeekPosition < 0) {
1411 fInitialSeekPosition
1412 = fController->TimeDuration() + fInitialSeekPosition;
1414 fController->SetTimePosition(fInitialSeekPosition);
1415 fInitialSeekPosition = 0;
1417 _SetupWindow();
1419 if (result == B_OK)
1420 _UpdatePlaylistItemFile();
1424 void
1425 MainWin::_SetupWindow()
1427 // printf("MainWin::_SetupWindow\n");
1428 // Populate the track menus
1429 _SetupTrackMenus(fAudioTrackMenu, fVideoTrackMenu, fSubTitleTrackMenu);
1430 _UpdateAudioChannelCount(fController->CurrentAudioTrack());
1432 fVideoMenu->SetEnabled(fHasVideo);
1433 fAudioMenu->SetEnabled(fHasAudio);
1434 int previousSourceWidth = fSourceWidth;
1435 int previousSourceHeight = fSourceHeight;
1436 int previousWidthAspect = fWidthAspect;
1437 int previousHeightAspect = fHeightAspect;
1438 if (fHasVideo) {
1439 fController->GetSize(&fSourceWidth, &fSourceHeight,
1440 &fWidthAspect, &fHeightAspect);
1441 } else {
1442 fSourceWidth = 0;
1443 fSourceHeight = 0;
1444 fWidthAspect = 1;
1445 fHeightAspect = 1;
1447 _UpdateControlsEnabledStatus();
1449 // Adopt the size and window layout if necessary
1450 if (previousSourceWidth != fSourceWidth
1451 || previousSourceHeight != fSourceHeight
1452 || previousWidthAspect != fWidthAspect
1453 || previousHeightAspect != fHeightAspect) {
1455 _SetWindowSizeLimits();
1457 if (!fIsFullscreen) {
1458 // Resize to 100% but stay on screen
1459 _ResizeWindow(100, !fHasVideo, true);
1460 } else {
1461 // Make sure we relayout the video view when in full screen mode
1462 FrameResized(Frame().Width(), Frame().Height());
1466 _ShowIfNeeded();
1468 fVideoView->MakeFocus();
1472 void
1473 MainWin::_CreateMenu()
1475 fFileMenu = new BMenu(kApplicationName);
1476 fPlaylistMenu = new BMenu(B_TRANSLATE("Playlist" B_UTF8_ELLIPSIS));
1477 fAudioMenu = new BMenu(B_TRANSLATE("Audio"));
1478 fVideoMenu = new BMenu(B_TRANSLATE("Video"));
1479 fVideoAspectMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
1480 fAudioTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track",
1481 "Audio Track Menu"));
1482 fVideoTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track",
1483 "Video Track Menu"));
1484 fSubTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
1485 fAttributesMenu = new BMenu(B_TRANSLATE("Attributes"));
1487 fMenuBar->AddItem(fFileMenu);
1488 fMenuBar->AddItem(fAudioMenu);
1489 fMenuBar->AddItem(fVideoMenu);
1490 fMenuBar->AddItem(fAttributesMenu);
1492 BMenuItem* item = new BMenuItem(B_TRANSLATE("New player" B_UTF8_ELLIPSIS),
1493 new BMessage(M_NEW_PLAYER), 'N');
1494 fFileMenu->AddItem(item);
1495 item->SetTarget(be_app);
1497 // Add recent files to "Open File" entry as sub-menu.
1498 BRecentFilesList recentFiles(10, false, NULL, kAppSig);
1499 item = new BMenuItem(recentFiles.NewFileListMenu(
1500 B_TRANSLATE("Open file" B_UTF8_ELLIPSIS), NULL, NULL, this, 10, true,
1501 NULL, kAppSig), new BMessage(M_FILE_OPEN));
1502 item->SetShortcut('O', 0);
1503 fFileMenu->AddItem(item);
1505 item = new BMenuItem(B_TRANSLATE("Open network stream"),
1506 new BMessage(M_NETWORK_STREAM_OPEN));
1507 fFileMenu->AddItem(item);
1509 fFileMenu->AddSeparatorItem();
1511 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("File info" B_UTF8_ELLIPSIS),
1512 new BMessage(M_FILE_INFO), 'I'));
1513 fFileMenu->AddItem(fPlaylistMenu);
1514 fPlaylistMenu->Superitem()->SetShortcut('P', B_COMMAND_KEY);
1515 fPlaylistMenu->Superitem()->SetMessage(new BMessage(M_FILE_PLAYLIST));
1517 fFileMenu->AddSeparatorItem();
1519 fNoInterfaceMenuItem = new BMenuItem(B_TRANSLATE("Hide interface"),
1520 new BMessage(M_TOGGLE_NO_INTERFACE), 'H');
1521 fFileMenu->AddItem(fNoInterfaceMenuItem);
1522 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Always on top"),
1523 new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
1525 item = new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
1526 new BMessage(M_SETTINGS), 'S');
1527 fFileMenu->AddItem(item);
1528 item->SetTarget(be_app);
1530 fFileMenu->AddSeparatorItem();
1532 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
1533 new BMessage(M_FILE_CLOSE), 'W'));
1534 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
1535 new BMessage(M_FILE_QUIT), 'Q'));
1537 fPlaylistMenu->SetRadioMode(true);
1539 fAudioMenu->AddItem(fAudioTrackMenu);
1541 fVideoMenu->AddItem(fVideoTrackMenu);
1542 fVideoMenu->AddItem(fSubTitleTrackMenu);
1543 fVideoMenu->AddSeparatorItem();
1544 BMessage* resizeMessage = new BMessage(M_VIEW_SIZE);
1545 resizeMessage->AddInt32("size", 50);
1546 fVideoMenu->AddItem(new BMenuItem(
1547 B_TRANSLATE("50% scale"), resizeMessage, '0'));
1549 resizeMessage = new BMessage(M_VIEW_SIZE);
1550 resizeMessage->AddInt32("size", 100);
1551 fVideoMenu->AddItem(new BMenuItem(
1552 B_TRANSLATE("100% scale"), resizeMessage, '1'));
1554 resizeMessage = new BMessage(M_VIEW_SIZE);
1555 resizeMessage->AddInt32("size", 200);
1556 fVideoMenu->AddItem(new BMenuItem(
1557 B_TRANSLATE("200% scale"), resizeMessage, '2'));
1559 resizeMessage = new BMessage(M_VIEW_SIZE);
1560 resizeMessage->AddInt32("size", 300);
1561 fVideoMenu->AddItem(new BMenuItem(
1562 B_TRANSLATE("300% scale"), resizeMessage, '3'));
1564 resizeMessage = new BMessage(M_VIEW_SIZE);
1565 resizeMessage->AddInt32("size", 400);
1566 fVideoMenu->AddItem(new BMenuItem(
1567 B_TRANSLATE("400% scale"), resizeMessage, '4'));
1569 fVideoMenu->AddSeparatorItem();
1571 fVideoMenu->AddItem(new BMenuItem(B_TRANSLATE("Full screen"),
1572 new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
1574 fVideoMenu->AddSeparatorItem();
1576 _SetupVideoAspectItems(fVideoAspectMenu);
1577 fVideoMenu->AddItem(fVideoAspectMenu);
1579 fRatingMenu = new BMenu(B_TRANSLATE("Rating"));
1580 fAttributesMenu->AddItem(fRatingMenu);
1581 for (int32 i = 1; i <= 10; i++) {
1582 char label[16];
1583 snprintf(label, sizeof(label), "%" B_PRId32, i);
1584 BMessage* setRatingMsg = new BMessage(M_SET_RATING);
1585 setRatingMsg->AddInt32("rating", i);
1586 fRatingMenu->AddItem(new BMenuItem(label, setRatingMsg));
1591 void
1592 MainWin::_SetupVideoAspectItems(BMenu* menu)
1594 BMenuItem* item;
1595 while ((item = menu->RemoveItem((int32)0)) != NULL)
1596 delete item;
1598 int width;
1599 int height;
1600 int widthAspect;
1601 int heightAspect;
1602 fController->GetSize(&width, &height, &widthAspect, &heightAspect);
1603 // We don't care if there is a video track at all. In that
1604 // case we should end up not marking any item.
1606 // NOTE: The item marking may end up marking for example both
1607 // "Stream Settings" and "16 : 9" if the stream settings happen to
1608 // be "16 : 9".
1610 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Stream settings"),
1611 new BMessage(M_ASPECT_SAME_AS_SOURCE), '1', B_SHIFT_KEY));
1612 item->SetMarked(widthAspect == fWidthAspect
1613 && heightAspect == fHeightAspect);
1615 menu->AddItem(item = new BMenuItem(B_TRANSLATE("No aspect correction"),
1616 new BMessage(M_ASPECT_NO_DISTORTION), '0', B_SHIFT_KEY));
1617 item->SetMarked(width == fWidthAspect && height == fHeightAspect);
1619 menu->AddSeparatorItem();
1621 menu->AddItem(item = new BMenuItem("4 : 3",
1622 new BMessage(M_ASPECT_4_3), 2, B_SHIFT_KEY));
1623 item->SetMarked(fWidthAspect == 4 && fHeightAspect == 3);
1624 menu->AddItem(item = new BMenuItem("16 : 9",
1625 new BMessage(M_ASPECT_16_9), 3, B_SHIFT_KEY));
1626 item->SetMarked(fWidthAspect == 16 && fHeightAspect == 9);
1628 menu->AddSeparatorItem();
1630 menu->AddItem(item = new BMenuItem("1.66 : 1",
1631 new BMessage(M_ASPECT_83_50)));
1632 item->SetMarked(fWidthAspect == 83 && fHeightAspect == 50);
1633 menu->AddItem(item = new BMenuItem("1.75 : 1",
1634 new BMessage(M_ASPECT_7_4)));
1635 item->SetMarked(fWidthAspect == 7 && fHeightAspect == 4);
1636 menu->AddItem(item = new BMenuItem(B_TRANSLATE("1.85 : 1 (American)"),
1637 new BMessage(M_ASPECT_37_20)));
1638 item->SetMarked(fWidthAspect == 37 && fHeightAspect == 20);
1639 menu->AddItem(item = new BMenuItem(B_TRANSLATE("2.35 : 1 (Cinemascope)"),
1640 new BMessage(M_ASPECT_47_20)));
1641 item->SetMarked(fWidthAspect == 47 && fHeightAspect == 20);
1645 void
1646 MainWin::_SetupTrackMenus(BMenu* audioTrackMenu, BMenu* videoTrackMenu,
1647 BMenu* subTitleTrackMenu)
1649 audioTrackMenu->RemoveItems(0, audioTrackMenu->CountItems(), true);
1650 videoTrackMenu->RemoveItems(0, videoTrackMenu->CountItems(), true);
1651 subTitleTrackMenu->RemoveItems(0, subTitleTrackMenu->CountItems(), true);
1653 char s[100];
1655 int count = fController->AudioTrackCount();
1656 int current = fController->CurrentAudioTrack();
1657 for (int i = 0; i < count; i++) {
1658 BMessage metaData;
1659 const char* languageString = NULL;
1660 if (fController->GetAudioMetaData(i, &metaData) == B_OK)
1661 metaData.FindString("language", &languageString);
1662 if (languageString != NULL) {
1663 BLanguage language(languageString);
1664 BString languageName;
1665 if (language.GetName(languageName) == B_OK)
1666 languageString = languageName.String();
1667 snprintf(s, sizeof(s), "%s", languageString);
1668 } else
1669 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1670 BMenuItem* item = new BMenuItem(s,
1671 new BMessage(M_SELECT_AUDIO_TRACK + i));
1672 item->SetMarked(i == current);
1673 audioTrackMenu->AddItem(item);
1675 if (count == 0) {
1676 audioTrackMenu->AddItem(new BMenuItem(B_TRANSLATE_CONTEXT("none",
1677 "Audio track menu"), new BMessage(M_DUMMY)));
1678 audioTrackMenu->ItemAt(0)->SetMarked(true);
1680 audioTrackMenu->SetEnabled(count > 1);
1682 count = fController->VideoTrackCount();
1683 current = fController->CurrentVideoTrack();
1684 for (int i = 0; i < count; i++) {
1685 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1686 BMenuItem* item = new BMenuItem(s,
1687 new BMessage(M_SELECT_VIDEO_TRACK + i));
1688 item->SetMarked(i == current);
1689 videoTrackMenu->AddItem(item);
1691 if (count == 0) {
1692 videoTrackMenu->AddItem(new BMenuItem(B_TRANSLATE("none"),
1693 new BMessage(M_DUMMY)));
1694 videoTrackMenu->ItemAt(0)->SetMarked(true);
1696 videoTrackMenu->SetEnabled(count > 1);
1698 count = fController->SubTitleTrackCount();
1699 if (count > 0) {
1700 current = fController->CurrentSubTitleTrack();
1701 BMenuItem* item = new BMenuItem(
1702 B_TRANSLATE_CONTEXT("Off", "Subtitles menu"),
1703 new BMessage(M_SELECT_SUB_TITLE_TRACK - 1));
1704 subTitleTrackMenu->AddItem(item);
1705 item->SetMarked(current == -1);
1707 subTitleTrackMenu->AddSeparatorItem();
1709 for (int i = 0; i < count; i++) {
1710 const char* name = fController->SubTitleTrackName(i);
1711 if (name != NULL)
1712 snprintf(s, sizeof(s), "%s", name);
1713 else
1714 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1715 item = new BMenuItem(s,
1716 new BMessage(M_SELECT_SUB_TITLE_TRACK + i));
1717 item->SetMarked(i == current);
1718 subTitleTrackMenu->AddItem(item);
1720 } else {
1721 subTitleTrackMenu->AddItem(new BMenuItem(
1722 B_TRANSLATE_CONTEXT("none", "Subtitles menu"),
1723 new BMessage(M_DUMMY)));
1724 subTitleTrackMenu->ItemAt(0)->SetMarked(true);
1726 subTitleTrackMenu->SetEnabled(count > 0);
1730 void
1731 MainWin::_UpdateAudioChannelCount(int32 audioTrackIndex)
1733 fControls->SetAudioChannelCount(fController->AudioTrackChannelCount());
1737 void
1738 MainWin::_GetMinimumWindowSize(int& width, int& height) const
1740 width = MIN_WIDTH;
1741 height = 0;
1742 if (!fNoInterface) {
1743 width = max_c(width, fMenuBarWidth);
1744 width = max_c(width, fControlsWidth);
1745 height = fMenuBarHeight + fControlsHeight;
1750 void
1751 MainWin::_GetUnscaledVideoSize(int& videoWidth, int& videoHeight) const
1753 if (fWidthAspect != 0 && fHeightAspect != 0) {
1754 videoWidth = fSourceHeight * fWidthAspect / fHeightAspect;
1755 videoHeight = fSourceWidth * fHeightAspect / fWidthAspect;
1756 // Use the scaling which produces an enlarged view.
1757 if (videoWidth > fSourceWidth) {
1758 // Enlarge width
1759 videoHeight = fSourceHeight;
1760 } else {
1761 // Enlarge height
1762 videoWidth = fSourceWidth;
1764 } else {
1765 videoWidth = fSourceWidth;
1766 videoHeight = fSourceHeight;
1771 void
1772 MainWin::_SetWindowSizeLimits()
1774 int minWidth;
1775 int minHeight;
1776 _GetMinimumWindowSize(minWidth, minHeight);
1777 SetSizeLimits(minWidth - 1, 32000, minHeight - 1,
1778 fHasVideo ? 32000 : minHeight - 1);
1783 MainWin::_CurrentVideoSizeInPercent() const
1785 if (!fHasVideo)
1786 return 0;
1788 int videoWidth;
1789 int videoHeight;
1790 _GetUnscaledVideoSize(videoWidth, videoHeight);
1792 int viewWidth = fVideoView->Bounds().IntegerWidth() + 1;
1793 int viewHeight = fVideoView->Bounds().IntegerHeight() + 1;
1795 int widthPercent = viewWidth * 100 / videoWidth;
1796 int heightPercent = viewHeight * 100 / videoHeight;
1798 if (widthPercent > heightPercent)
1799 return widthPercent;
1800 return heightPercent;
1804 void
1805 MainWin::_ZoomVideoView(int percentDiff)
1807 if (!fHasVideo)
1808 return;
1810 int percent = _CurrentVideoSizeInPercent();
1811 int newSize = percent * (100 + percentDiff) / 100;
1813 if (newSize < 25)
1814 newSize = 25;
1815 if (newSize > 400)
1816 newSize = 400;
1817 if (newSize != percent) {
1818 BMessage message(M_VIEW_SIZE);
1819 message.AddInt32("size", newSize);
1820 PostMessage(&message);
1825 void
1826 MainWin::_ResizeWindow(int percent, bool useNoVideoWidth, bool stayOnScreen)
1828 // Get required window size
1829 int videoWidth;
1830 int videoHeight;
1831 _GetUnscaledVideoSize(videoWidth, videoHeight);
1833 videoWidth = (videoWidth * percent) / 100;
1834 videoHeight = (videoHeight * percent) / 100;
1836 // Calculate and set the minimum window size
1837 int width;
1838 int height;
1839 _GetMinimumWindowSize(width, height);
1841 width = max_c(width, videoWidth) - 1;
1842 if (useNoVideoWidth)
1843 width = max_c(width, fNoVideoWidth);
1844 height = height + videoHeight - 1;
1846 if (stayOnScreen) {
1847 BRect screenFrame(BScreen(this).Frame());
1848 BRect frame(Frame());
1849 BRect decoratorFrame(DecoratorFrame());
1851 // Shrink the screen frame by the window border size
1852 screenFrame.top += frame.top - decoratorFrame.top;
1853 screenFrame.left += frame.left - decoratorFrame.left;
1854 screenFrame.right += frame.right - decoratorFrame.right;
1855 screenFrame.bottom += frame.bottom - decoratorFrame.bottom;
1857 // Update frame to what the new size would be
1858 frame.right = frame.left + width;
1859 frame.bottom = frame.top + height;
1861 if (!screenFrame.Contains(frame)) {
1862 // Resize the window so it doesn't extend outside the current
1863 // screen frame.
1864 // We don't use BWindow::MoveOnScreen() in order to resize the
1865 // window while keeping the same aspect ratio.
1866 if (frame.Width() > screenFrame.Width()
1867 || frame.Height() > screenFrame.Height()) {
1868 // too large
1869 int widthDiff
1870 = frame.IntegerWidth() - screenFrame.IntegerWidth();
1871 int heightDiff
1872 = frame.IntegerHeight() - screenFrame.IntegerHeight();
1874 float shrinkScale;
1875 if (widthDiff > heightDiff)
1876 shrinkScale = (float)(width - widthDiff) / width;
1877 else
1878 shrinkScale = (float)(height - heightDiff) / height;
1880 // Resize width/height and center window
1881 width = lround(width * shrinkScale);
1882 height = lround(height * shrinkScale);
1883 MoveTo((screenFrame.left + screenFrame.right - width) / 2,
1884 (screenFrame.top + screenFrame.bottom - height) / 2);
1885 } else {
1886 // just off-screen on one or more sides
1887 int offsetX = 0;
1888 int offsetY = 0;
1889 if (frame.left < screenFrame.left)
1890 offsetX = (int)(screenFrame.left - frame.left);
1891 else if (frame.right > screenFrame.right)
1892 offsetX = (int)(screenFrame.right - frame.right);
1893 if (frame.top < screenFrame.top)
1894 offsetY = (int)(screenFrame.top - frame.top);
1895 else if (frame.bottom > screenFrame.bottom)
1896 offsetY = (int)(screenFrame.bottom - frame.bottom);
1897 MoveBy(offsetX, offsetY);
1902 ResizeTo(width, height);
1906 void
1907 MainWin::_ResizeVideoView(int x, int y, int width, int height)
1909 // Keep aspect ratio, place video view inside
1910 // the background area (may create black bars).
1911 int videoWidth;
1912 int videoHeight;
1913 _GetUnscaledVideoSize(videoWidth, videoHeight);
1914 float scaledWidth = videoWidth;
1915 float scaledHeight = videoHeight;
1916 float factor = min_c(width / scaledWidth, height / scaledHeight);
1917 int renderWidth = lround(scaledWidth * factor);
1918 int renderHeight = lround(scaledHeight * factor);
1919 if (renderWidth > width)
1920 renderWidth = width;
1921 if (renderHeight > height)
1922 renderHeight = height;
1924 int xOffset = (width - renderWidth) / 2;
1925 int yOffset = (height - renderHeight) / 2;
1927 fVideoView->MoveTo(x, y);
1928 fVideoView->ResizeTo(width - 1, height - 1);
1930 BRect videoFrame(xOffset, yOffset,
1931 xOffset + renderWidth - 1, yOffset + renderHeight - 1);
1933 fVideoView->SetVideoFrame(videoFrame);
1934 fVideoView->SetSubTitleMaxBottom(height - 1);
1938 // #pragma mark -
1941 void
1942 MainWin::_MouseDown(BMessage* msg, BView* originalHandler)
1944 uint32 buttons = msg->FindInt32("buttons");
1946 // On Zeta, only "screen_where" is reliable, "where" and "be:view_where"
1947 // seem to be broken
1948 BPoint screenWhere;
1949 if (msg->FindPoint("screen_where", &screenWhere) != B_OK) {
1950 // TODO: remove
1951 // Workaround for BeOS R5, it has no "screen_where"
1952 if (!originalHandler || msg->FindPoint("where", &screenWhere) < B_OK)
1953 return;
1954 originalHandler->ConvertToScreen(&screenWhere);
1957 // double click handling
1959 if (msg->FindInt32("clicks") % 2 == 0) {
1960 BRect rect(screenWhere.x - 1, screenWhere.y - 1, screenWhere.x + 1,
1961 screenWhere.y + 1);
1962 if (rect.Contains(fMouseDownMousePos)) {
1963 if (buttons == B_PRIMARY_MOUSE_BUTTON)
1964 PostMessage(M_TOGGLE_FULLSCREEN);
1965 else if (buttons == B_SECONDARY_MOUSE_BUTTON)
1966 PostMessage(M_TOGGLE_NO_INTERFACE);
1968 return;
1972 fMouseDownMousePos = screenWhere;
1973 fMouseDownWindowPos = Frame().LeftTop();
1975 if (buttons == B_PRIMARY_MOUSE_BUTTON && !fIsFullscreen) {
1976 // start mouse tracking
1977 fVideoView->SetMouseEventMask(B_POINTER_EVENTS | B_NO_POINTER_HISTORY
1978 /* | B_LOCK_WINDOW_FOCUS */);
1979 fMouseDownTracking = true;
1982 // pop up a context menu if right mouse button is down
1984 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
1985 _ShowContextMenu(screenWhere);
1989 void
1990 MainWin::_MouseMoved(BMessage* msg, BView* originalHandler)
1992 // msg->PrintToStream();
1994 BPoint mousePos;
1995 uint32 buttons = msg->FindInt32("buttons");
1996 // On Zeta, only "screen_where" is reliable, "where"
1997 // and "be:view_where" seem to be broken
1998 if (msg->FindPoint("screen_where", &mousePos) != B_OK) {
1999 // TODO: remove
2000 // Workaround for BeOS R5, it has no "screen_where"
2001 if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK)
2002 return;
2003 originalHandler->ConvertToScreen(&mousePos);
2006 if (buttons == B_PRIMARY_MOUSE_BUTTON && fMouseDownTracking
2007 && !fIsFullscreen) {
2008 // printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y);
2009 float delta_x = mousePos.x - fMouseDownMousePos.x;
2010 float delta_y = mousePos.y - fMouseDownMousePos.y;
2011 float x = fMouseDownWindowPos.x + delta_x;
2012 float y = fMouseDownWindowPos.y + delta_y;
2013 // printf("move window to %.0f, %.0f\n", x, y);
2014 MoveTo(x, y);
2017 bigtime_t eventTime;
2018 if (msg->FindInt64("when", &eventTime) != B_OK)
2019 eventTime = system_time();
2021 if (buttons == 0 && fIsFullscreen) {
2022 BPoint moveDelta = mousePos - fLastMousePos;
2023 float moveDeltaDist
2024 = sqrtf(moveDelta.x * moveDelta.x + moveDelta.y * moveDelta.y);
2025 if (eventTime - fLastMouseMovedTime < 200000)
2026 fMouseMoveDist += moveDeltaDist;
2027 else
2028 fMouseMoveDist = moveDeltaDist;
2029 if (fMouseMoveDist > 5)
2030 _ShowFullscreenControls(true);
2033 fLastMousePos = mousePos;
2034 fLastMouseMovedTime =eventTime;
2038 void
2039 MainWin::_MouseUp(BMessage* msg)
2041 fMouseDownTracking = false;
2045 void
2046 MainWin::_ShowContextMenu(const BPoint& screenPoint)
2048 printf("Show context menu\n");
2049 BPopUpMenu* menu = new BPopUpMenu("context menu", false, false);
2050 BMenuItem* item;
2051 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Full screen"),
2052 new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
2053 item->SetMarked(fIsFullscreen);
2054 item->SetEnabled(fHasVideo);
2056 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Hide interface"),
2057 new BMessage(M_TOGGLE_NO_INTERFACE), 'H'));
2058 item->SetMarked(fNoInterface);
2059 item->SetEnabled(fHasVideo && !fIsFullscreen);
2061 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"),
2062 new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
2063 item->SetMarked(fAlwaysOnTop);
2064 item->SetEnabled(fHasVideo);
2066 BMenu* aspectSubMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
2067 _SetupVideoAspectItems(aspectSubMenu);
2068 aspectSubMenu->SetTargetForItems(this);
2069 menu->AddItem(item = new BMenuItem(aspectSubMenu));
2070 item->SetEnabled(fHasVideo);
2072 menu->AddSeparatorItem();
2074 // Add track selector menus
2075 BMenu* audioTrackMenu = new BMenu(B_TRANSLATE("Audio track"));
2076 BMenu* videoTrackMenu = new BMenu(B_TRANSLATE("Video track"));
2077 BMenu* subTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
2078 _SetupTrackMenus(audioTrackMenu, videoTrackMenu, subTitleTrackMenu);
2080 audioTrackMenu->SetTargetForItems(this);
2081 videoTrackMenu->SetTargetForItems(this);
2082 subTitleTrackMenu->SetTargetForItems(this);
2084 menu->AddItem(item = new BMenuItem(audioTrackMenu));
2085 item->SetEnabled(fHasAudio);
2087 menu->AddItem(item = new BMenuItem(videoTrackMenu));
2088 item->SetEnabled(fHasVideo);
2090 menu->AddItem(item = new BMenuItem(subTitleTrackMenu));
2091 item->SetEnabled(fHasVideo);
2093 menu->AddSeparatorItem();
2094 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), new BMessage(M_FILE_QUIT), 'Q'));
2096 menu->SetTargetForItems(this);
2097 BRect rect(screenPoint.x - 5, screenPoint.y - 5, screenPoint.x + 5,
2098 screenPoint.y + 5);
2099 menu->Go(screenPoint, true, true, rect, true);
2103 /*! Trap keys that are about to be send to background or renderer view.
2104 Return true if it shouldn't be passed to the view.
2106 bool
2107 MainWin::_KeyDown(BMessage* msg)
2109 uint32 key = msg->FindInt32("key");
2110 uint32 rawChar = msg->FindInt32("raw_char");
2111 uint32 modifier = msg->FindInt32("modifiers");
2113 // printf("key 0x%lx, rawChar 0x%lx, modifiers 0x%lx\n", key, rawChar,
2114 // modifier);
2116 // ignore the system modifier namespace
2117 if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
2118 == (B_CONTROL_KEY | B_COMMAND_KEY))
2119 return false;
2121 switch (rawChar) {
2122 case B_SPACE:
2123 fController->TogglePlaying();
2124 return true;
2126 case 'm':
2127 fController->ToggleMute();
2128 return true;
2130 case B_ESCAPE:
2131 if (!fIsFullscreen)
2132 break;
2134 PostMessage(M_TOGGLE_FULLSCREEN);
2135 return true;
2137 case B_ENTER: // Enter / Return
2138 if ((modifier & B_COMMAND_KEY) != 0) {
2139 PostMessage(M_TOGGLE_FULLSCREEN);
2140 return true;
2142 break;
2144 case B_TAB:
2145 case 'f':
2146 if ((modifier & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
2147 | B_MENU_KEY)) == 0) {
2148 PostMessage(M_TOGGLE_FULLSCREEN);
2149 return true;
2151 break;
2153 case B_UP_ARROW:
2154 if ((modifier & B_COMMAND_KEY) != 0)
2155 PostMessage(M_SKIP_NEXT);
2156 else
2157 PostMessage(M_VOLUME_UP);
2158 return true;
2160 case B_DOWN_ARROW:
2161 if ((modifier & B_COMMAND_KEY) != 0)
2162 PostMessage(M_SKIP_PREV);
2163 else
2164 PostMessage(M_VOLUME_DOWN);
2165 return true;
2167 case B_RIGHT_ARROW:
2168 if ((modifier & B_COMMAND_KEY) != 0)
2169 PostMessage(M_SKIP_NEXT);
2170 else if (fAllowWinding) {
2171 BMessage windMessage(M_WIND);
2172 if ((modifier & B_SHIFT_KEY) != 0) {
2173 windMessage.AddInt64("how much", 30000000LL);
2174 windMessage.AddInt64("frames", 5);
2175 } else {
2176 windMessage.AddInt64("how much", 5000000LL);
2177 windMessage.AddInt64("frames", 1);
2179 PostMessage(&windMessage);
2181 return true;
2183 case B_LEFT_ARROW:
2184 if ((modifier & B_COMMAND_KEY) != 0)
2185 PostMessage(M_SKIP_PREV);
2186 else if (fAllowWinding) {
2187 BMessage windMessage(M_WIND);
2188 if ((modifier & B_SHIFT_KEY) != 0) {
2189 windMessage.AddInt64("how much", -30000000LL);
2190 windMessage.AddInt64("frames", -5);
2191 } else {
2192 windMessage.AddInt64("how much", -5000000LL);
2193 windMessage.AddInt64("frames", -1);
2195 PostMessage(&windMessage);
2197 return true;
2199 case B_PAGE_UP:
2200 PostMessage(M_SKIP_NEXT);
2201 return true;
2203 case B_PAGE_DOWN:
2204 PostMessage(M_SKIP_PREV);
2205 return true;
2207 case '+':
2208 if ((modifier & B_COMMAND_KEY) == 0) {
2209 _ZoomVideoView(10);
2210 return true;
2212 break;
2214 case '-':
2215 if ((modifier & B_COMMAND_KEY) == 0) {
2216 _ZoomVideoView(-10);
2217 return true;
2219 break;
2221 case B_DELETE:
2222 case 'd': // d for delete
2223 case 't': // t for Trash
2224 if ((modifiers() & B_COMMAND_KEY) != 0) {
2225 BAutolock _(fPlaylist);
2226 BMessage removeMessage(M_PLAYLIST_MOVE_TO_TRASH);
2227 removeMessage.AddInt32("playlist index",
2228 fPlaylist->CurrentItemIndex());
2229 fPlaylistWindow->PostMessage(&removeMessage);
2230 return true;
2232 break;
2235 switch (key) {
2236 case 0x3a: // numeric keypad +
2237 if ((modifier & B_COMMAND_KEY) == 0) {
2238 _ZoomVideoView(10);
2239 return true;
2241 break;
2243 case 0x25: // numeric keypad -
2244 if ((modifier & B_COMMAND_KEY) == 0) {
2245 _ZoomVideoView(-10);
2246 return true;
2248 break;
2250 case 0x38: // numeric keypad up arrow
2251 PostMessage(M_VOLUME_UP);
2252 return true;
2254 case 0x59: // numeric keypad down arrow
2255 PostMessage(M_VOLUME_DOWN);
2256 return true;
2258 case 0x39: // numeric keypad page up
2259 case 0x4a: // numeric keypad right arrow
2260 PostMessage(M_SKIP_NEXT);
2261 return true;
2263 case 0x5a: // numeric keypad page down
2264 case 0x48: // numeric keypad left arrow
2265 PostMessage(M_SKIP_PREV);
2266 return true;
2268 // Playback controls along the bottom of the keyboard:
2269 // Z X C (V) B for US International
2270 case 0x4c:
2271 PostMessage(M_SKIP_PREV);
2272 return true;
2273 case 0x4d:
2274 fController->TogglePlaying();
2275 return true;
2276 case 0x4e:
2277 fController->Pause();
2278 return true;
2279 case 0x4f:
2280 fController->Stop();
2281 return true;
2282 case 0x50:
2283 PostMessage(M_SKIP_NEXT);
2284 return true;
2287 return false;
2291 // #pragma mark -
2294 void
2295 MainWin::_ToggleFullscreen()
2297 printf("_ToggleFullscreen enter\n");
2299 if (!fHasVideo) {
2300 printf("_ToggleFullscreen - ignoring, as we don't have a video\n");
2301 return;
2304 fIsFullscreen = !fIsFullscreen;
2306 if (fIsFullscreen) {
2307 // switch to fullscreen
2309 fSavedFrame = Frame();
2310 printf("saving current frame: %d %d %d %d\n", int(fSavedFrame.left),
2311 int(fSavedFrame.top), int(fSavedFrame.right),
2312 int(fSavedFrame.bottom));
2313 BScreen screen(this);
2314 BRect rect(screen.Frame());
2316 Hide();
2317 MoveTo(rect.left, rect.top);
2318 ResizeTo(rect.Width(), rect.Height());
2319 Show();
2321 } else {
2322 // switch back from full screen mode
2323 _ShowFullscreenControls(false, false);
2325 Hide();
2326 MoveTo(fSavedFrame.left, fSavedFrame.top);
2327 ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
2328 Show();
2331 fVideoView->SetFullscreen(fIsFullscreen);
2333 _MarkItem(fFileMenu, M_TOGGLE_FULLSCREEN, fIsFullscreen);
2335 printf("_ToggleFullscreen leave\n");
2338 void
2339 MainWin::_ToggleAlwaysOnTop()
2341 fAlwaysOnTop = !fAlwaysOnTop;
2342 SetFeel(fAlwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL);
2344 _MarkItem(fFileMenu, M_TOGGLE_ALWAYS_ON_TOP, fAlwaysOnTop);
2348 void
2349 MainWin::_ToggleNoInterface()
2351 printf("_ToggleNoInterface enter\n");
2353 if (fIsFullscreen || !fHasVideo) {
2354 // Fullscreen playback is always without interface and
2355 // audio playback is always with interface. So we ignore these
2356 // two states here.
2357 printf("_ToggleNoControls leave, doing nothing, we are fullscreen\n");
2358 return;
2361 fNoInterface = !fNoInterface;
2362 _SetWindowSizeLimits();
2364 if (fNoInterface) {
2365 MoveBy(0, fMenuBarHeight);
2366 ResizeBy(0, -(fControlsHeight + fMenuBarHeight));
2367 SetLook(B_BORDERED_WINDOW_LOOK);
2368 } else {
2369 MoveBy(0, -fMenuBarHeight);
2370 ResizeBy(0, fControlsHeight + fMenuBarHeight);
2371 SetLook(B_TITLED_WINDOW_LOOK);
2374 _MarkItem(fFileMenu, M_TOGGLE_NO_INTERFACE, fNoInterface);
2376 printf("_ToggleNoInterface leave\n");
2380 void
2381 MainWin::_ShowIfNeeded()
2383 // Only proceed if the window is already running
2384 if (find_thread(NULL) != Thread())
2385 return;
2387 if (!fHasVideo && fNoVideoFrame.IsValid()) {
2388 MoveTo(fNoVideoFrame.LeftTop());
2389 ResizeTo(fNoVideoFrame.Width(), fNoVideoFrame.Height());
2390 MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN);
2391 } else if (fHasVideo && IsHidden())
2392 CenterOnScreen();
2394 fNoVideoFrame = BRect();
2396 if (IsHidden()) {
2397 Show();
2398 UpdateIfNeeded();
2403 void
2404 MainWin::_ShowFullscreenControls(bool show, bool animate)
2406 if (fShowsFullscreenControls == show)
2407 return;
2409 fShowsFullscreenControls = show;
2410 fVideoView->SetFullscreenControlsVisible(show);
2412 if (show) {
2413 fControls->RemoveSelf();
2414 fControls->MoveTo(fVideoView->Bounds().left,
2415 fVideoView->Bounds().bottom + 1);
2416 fVideoView->AddChild(fControls);
2417 if (fScaleFullscreenControls)
2418 fControls->SetSymbolScale(1.5f);
2420 while (fControls->IsHidden())
2421 fControls->Show();
2424 if (animate) {
2425 // Slide the controls into view. We need to do this with
2426 // messages, otherwise we block the video playback for the
2427 // time of the animation.
2428 const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
2429 const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
2430 float height = fControls->Bounds().Height();
2431 float moveDist = show ? -height : height;
2432 float originalY = fControls->Frame().top;
2433 for (int32 i = 0; i < steps; i++) {
2434 BMessage message(M_SLIDE_CONTROLS);
2435 message.AddFloat("offset",
2436 floorf(moveDist * kAnimationOffsets[i]));
2437 PostMessage(&message, this);
2439 BMessage finalMessage(M_FINISH_SLIDING_CONTROLS);
2440 finalMessage.AddFloat("offset", originalY + moveDist);
2441 finalMessage.AddBool("show", show);
2442 PostMessage(&finalMessage, this);
2443 } else if (!show) {
2444 fControls->RemoveSelf();
2445 fControls->MoveTo(fVideoView->Frame().left,
2446 fVideoView->Frame().bottom + 1);
2447 fBackground->AddChild(fControls);
2448 fControls->SetSymbolScale(1.0f);
2450 while (!fControls->IsHidden())
2451 fControls->Hide();
2456 // #pragma mark -
2459 void
2460 MainWin::_Wind(bigtime_t howMuch, int64 frames)
2462 if (!fAllowWinding || !fController->Lock())
2463 return;
2465 if (frames != 0 && fHasVideo && !fController->IsPlaying()) {
2466 int64 newFrame = fController->CurrentFrame() + frames;
2467 fController->SetFramePosition(newFrame);
2468 } else {
2469 bigtime_t seekTime = fController->TimePosition() + howMuch;
2470 if (seekTime < 0) {
2471 fInitialSeekPosition = seekTime;
2472 PostMessage(M_SKIP_PREV);
2473 } else if (seekTime > fController->TimeDuration()) {
2474 fInitialSeekPosition = 0;
2475 PostMessage(M_SKIP_NEXT);
2476 } else
2477 fController->SetTimePosition(seekTime);
2480 fController->Unlock();
2481 fAllowWinding = false;
2485 // #pragma mark -
2488 void
2489 MainWin::_UpdatePlaylistItemFile()
2491 BAutolock locker(fPlaylist);
2492 const FilePlaylistItem* item
2493 = dynamic_cast<const FilePlaylistItem*>(fController->Item());
2494 if (item == NULL)
2495 return;
2497 if (!fHasVideo && !fHasAudio)
2498 return;
2500 BNode node(&item->Ref());
2501 if (node.InitCheck())
2502 return;
2504 locker.Unlock();
2506 // Set some standard attributes of the currently played file.
2507 // This should only be a temporary solution.
2509 // Write duration
2510 const char* kDurationAttrName = "Media:Length";
2511 attr_info info;
2512 status_t status = node.GetAttrInfo(kDurationAttrName, &info);
2513 if (status != B_OK || info.size == 0) {
2514 bigtime_t duration = fController->TimeDuration();
2515 // TODO: Tracker does not seem to care about endian for scalar types
2516 node.WriteAttr(kDurationAttrName, B_INT64_TYPE, 0, &duration,
2517 sizeof(int64));
2520 // Write audio bitrate
2521 if (fHasAudio) {
2522 status = node.GetAttrInfo("Audio:Bitrate", &info);
2523 if (status != B_OK || info.size == 0) {
2524 media_format format;
2525 if (fController->GetEncodedAudioFormat(&format) == B_OK
2526 && format.type == B_MEDIA_ENCODED_AUDIO) {
2527 int32 bitrate = (int32)(format.u.encoded_audio.bit_rate
2528 / 1000);
2529 char text[256];
2530 snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2531 node.WriteAttr("Audio:Bitrate", B_STRING_TYPE, 0, text,
2532 strlen(text) + 1);
2537 // Write video bitrate
2538 if (fHasVideo) {
2539 status = node.GetAttrInfo("Video:Bitrate", &info);
2540 if (status != B_OK || info.size == 0) {
2541 media_format format;
2542 if (fController->GetEncodedVideoFormat(&format) == B_OK
2543 && format.type == B_MEDIA_ENCODED_VIDEO) {
2544 int32 bitrate = (int32)(format.u.encoded_video.avg_bit_rate
2545 / 1000);
2546 char text[256];
2547 snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2548 node.WriteAttr("Video:Bitrate", B_STRING_TYPE, 0, text,
2549 strlen(text) + 1);
2554 _UpdateAttributesMenu(node);
2558 void
2559 MainWin::_UpdateAttributesMenu(const BNode& node)
2561 int32 rating = -1;
2563 attr_info info;
2564 status_t status = node.GetAttrInfo(kRatingAttrName, &info);
2565 if (status == B_OK && info.type == B_INT32_TYPE) {
2566 // Node has the Rating attribute.
2567 node.ReadAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating,
2568 sizeof(rating));
2571 for (int32 i = 0; BMenuItem* item = fRatingMenu->ItemAt(i); i++)
2572 item->SetMarked(i + 1 == rating);
2576 void
2577 MainWin::_SetRating(int32 rating)
2579 BAutolock locker(fPlaylist);
2580 const FilePlaylistItem* item
2581 = dynamic_cast<const FilePlaylistItem*>(fController->Item());
2582 if (item == NULL)
2583 return;
2585 BNode node(&item->Ref());
2586 if (node.InitCheck())
2587 return;
2589 locker.Unlock();
2591 node.WriteAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating, sizeof(rating));
2593 // TODO: The whole mechnism should work like this:
2594 // * There is already an attribute API for PlaylistItem, flesh it out!
2595 // * FilePlaylistItem node-monitors it's file somehow.
2596 // * FilePlaylistItem keeps attributes in sync and sends notications.
2597 // * MainWin updates the menu according to FilePlaylistItem notifications.
2598 // * PlaylistWin shows columns with attribute and other info.
2599 // * PlaylistWin updates also upon FilePlaylistItem notifications.
2600 // * This keeps attributes in sync when another app changes them.
2602 _UpdateAttributesMenu(node);
2606 void
2607 MainWin::_UpdateControlsEnabledStatus()
2609 uint32 enabledButtons = 0;
2610 if (fHasVideo || fHasAudio) {
2611 enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED
2612 | SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED;
2614 if (fHasAudio)
2615 enabledButtons |= VOLUME_ENABLED;
2617 BAutolock _(fPlaylist);
2618 bool canSkipPrevious, canSkipNext;
2619 fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext);
2620 if (canSkipPrevious)
2621 enabledButtons |= SKIP_BACK_ENABLED;
2622 if (canSkipNext)
2623 enabledButtons |= SKIP_FORWARD_ENABLED;
2625 fControls->SetEnabled(enabledButtons);
2627 fNoInterfaceMenuItem->SetEnabled(fHasVideo);
2628 fAttributesMenu->SetEnabled(fHasAudio || fHasVideo);
2632 void
2633 MainWin::_UpdatePlaylistMenu()
2635 if (!fPlaylist->Lock())
2636 return;
2638 fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true);
2640 int32 count = fPlaylist->CountItems();
2641 for (int32 i = 0; i < count; i++) {
2642 PlaylistItem* item = fPlaylist->ItemAtFast(i);
2643 _AddPlaylistItem(item, i);
2645 fPlaylistMenu->SetTargetForItems(this);
2647 _MarkPlaylistItem(fPlaylist->CurrentItemIndex());
2649 fPlaylist->Unlock();
2653 void
2654 MainWin::_AddPlaylistItem(PlaylistItem* item, int32 index)
2656 BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION);
2657 message->AddInt32("index", index);
2658 BMenuItem* menuItem = new BMenuItem(item->Name().String(), message);
2659 fPlaylistMenu->AddItem(menuItem, index);
2663 void
2664 MainWin::_RemovePlaylistItem(int32 index)
2666 delete fPlaylistMenu->RemoveItem(index);
2670 void
2671 MainWin::_MarkPlaylistItem(int32 index)
2673 if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) {
2674 item->SetMarked(true);
2675 // ... and in case the menu is currently on screen:
2676 if (fPlaylistMenu->LockLooper()) {
2677 fPlaylistMenu->Invalidate();
2678 fPlaylistMenu->UnlockLooper();
2684 void
2685 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark)
2687 if (BMenuItem* item = menu->FindItem(command))
2688 item->SetMarked(mark);
2692 void
2693 MainWin::_AdoptGlobalSettings()
2695 mpSettings settings;
2696 Settings::Default()->Get(settings);
2698 fCloseWhenDonePlayingMovie = settings.closeWhenDonePlayingMovie;
2699 fCloseWhenDonePlayingSound = settings.closeWhenDonePlayingSound;
2700 fLoopMovies = settings.loopMovie;
2701 fLoopSounds = settings.loopSound;
2702 fScaleFullscreenControls = settings.scaleFullscreenControls;