BPicture: Fix archive constructor.
[haiku.git] / src / apps / mediaplayer / MainWin.cpp
blob524fad01b09b973da4a5ae7cb249cf365fb723b9
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 <Language.h>
37 #include <Locale.h>
38 #include <MediaRoster.h>
39 #include <Menu.h>
40 #include <MenuBar.h>
41 #include <MenuItem.h>
42 #include <MessageRunner.h>
43 #include <Messenger.h>
44 #include <PopUpMenu.h>
45 #include <PropertyInfo.h>
46 #include <RecentItems.h>
47 #include <Roster.h>
48 #include <Screen.h>
49 #include <String.h>
50 #include <TypeConstants.h>
51 #include <View.h>
53 #include "AudioProducer.h"
54 #include "ControllerObserver.h"
55 #include "DurationToString.h"
56 #include "FilePlaylistItem.h"
57 #include "MainApp.h"
58 #include "PeakView.h"
59 #include "PlaylistItem.h"
60 #include "PlaylistObserver.h"
61 #include "PlaylistWindow.h"
62 #include "Settings.h"
65 #undef B_TRANSLATION_CONTEXT
66 #define B_TRANSLATION_CONTEXT "MediaPlayer-Main"
67 #define MIN_WIDTH 250
70 int MainWin::sNoVideoWidth = MIN_WIDTH;
73 // XXX TODO: why is lround not defined?
74 #define lround(a) ((int)(0.99999 + (a)))
76 enum {
77 M_DUMMY = 0x100,
78 M_FILE_OPEN = 0x1000,
79 M_FILE_INFO,
80 M_FILE_PLAYLIST,
81 M_FILE_CLOSE,
82 M_FILE_QUIT,
83 M_VIEW_SIZE,
84 M_TOGGLE_FULLSCREEN,
85 M_TOGGLE_ALWAYS_ON_TOP,
86 M_TOGGLE_NO_INTERFACE,
87 M_VOLUME_UP,
88 M_VOLUME_DOWN,
89 M_SKIP_NEXT,
90 M_SKIP_PREV,
91 M_WIND,
93 // The common display aspect ratios
94 M_ASPECT_SAME_AS_SOURCE,
95 M_ASPECT_NO_DISTORTION,
96 M_ASPECT_4_3,
97 M_ASPECT_16_9,
98 M_ASPECT_83_50,
99 M_ASPECT_7_4,
100 M_ASPECT_37_20,
101 M_ASPECT_47_20,
103 M_SELECT_AUDIO_TRACK = 0x00000800,
104 M_SELECT_AUDIO_TRACK_END = 0x00000fff,
105 M_SELECT_VIDEO_TRACK = 0x00010000,
106 M_SELECT_VIDEO_TRACK_END = 0x00010fff,
107 M_SELECT_SUB_TITLE_TRACK = 0x00020000,
108 M_SELECT_SUB_TITLE_TRACK_END = 0x00020fff,
110 M_SET_RATING,
112 M_SET_PLAYLIST_POSITION,
114 M_FILE_DELETE,
116 M_SLIDE_CONTROLS,
117 M_FINISH_SLIDING_CONTROLS
121 static property_info sPropertyInfo[] = {
122 { B_TRANSLATE("Next"), { B_EXECUTE_PROPERTY },
123 { B_DIRECT_SPECIFIER, 0 },
124 B_TRANSLATE("Skip to the next track."), 0
126 { B_TRANSLATE("Prev"), { B_EXECUTE_PROPERTY },
127 { B_DIRECT_SPECIFIER, 0 },
128 B_TRANSLATE("Skip to the previous track."), 0
130 { B_TRANSLATE("Play"), { B_EXECUTE_PROPERTY },
131 { B_DIRECT_SPECIFIER, 0 },
132 B_TRANSLATE("Start playing."), 0
134 { B_TRANSLATE("Stop"), { B_EXECUTE_PROPERTY },
135 { B_DIRECT_SPECIFIER, 0 },
136 B_TRANSLATE("Stop playing."), 0
138 { B_TRANSLATE("Pause"), { B_EXECUTE_PROPERTY },
139 { B_DIRECT_SPECIFIER, 0 },
140 B_TRANSLATE("Pause playback."), 0
142 { B_TRANSLATE("TogglePlaying"), { B_EXECUTE_PROPERTY },
143 { B_DIRECT_SPECIFIER, 0 },
144 B_TRANSLATE("Toggle pause/play."), 0
146 { B_TRANSLATE("Mute"), { B_EXECUTE_PROPERTY },
147 { B_DIRECT_SPECIFIER, 0 },
148 B_TRANSLATE("Toggle mute."), 0
150 { B_TRANSLATE("Volume"), { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
151 { B_DIRECT_SPECIFIER, 0 },
152 B_TRANSLATE("Gets/sets the volume (0.0-2.0)."), 0,
153 { B_FLOAT_TYPE }
155 { B_TRANSLATE("URI"), { B_GET_PROPERTY, 0 },
156 { B_DIRECT_SPECIFIER, 0 },
157 B_TRANSLATE("Gets the URI of the currently playing item."), 0,
158 { B_STRING_TYPE }
160 { B_TRANSLATE("ToggleFullscreen"), { B_EXECUTE_PROPERTY },
161 { B_DIRECT_SPECIFIER, 0 },
162 B_TRANSLATE("Toggle fullscreen."), 0
164 { B_TRANSLATE("Duration"), { B_GET_PROPERTY, 0 },
165 { B_DIRECT_SPECIFIER, 0 },
166 B_TRANSLATE("Gets the duration of the currently playing item "
167 "in microseconds."), 0,
168 { B_INT64_TYPE }
170 { B_TRANSLATE("Position"), { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
171 { B_DIRECT_SPECIFIER, 0 },
172 B_TRANSLATE("Gets/sets the current playing position in microseconds."),
173 0, { B_INT64_TYPE }
175 { B_TRANSLATE("Seek"), { B_SET_PROPERTY },
176 { B_DIRECT_SPECIFIER, 0 },
177 B_TRANSLATE("Seek by the specified amounts of microseconds."), 0,
178 { B_INT64_TYPE }
180 { 0, { 0 }, { 0 }, 0, 0 }
184 static const char* kRatingAttrName = "Media:Rating";
186 static const char* kDisabledSeekMessage = B_TRANSLATE("Drop files to play");
188 static const char* kApplicationName = B_TRANSLATE_SYSTEM_NAME(NAME);
191 MainWin::MainWin(bool isFirstWindow, BMessage* message)
193 BWindow(BRect(100, 100, 400, 300), kApplicationName, B_TITLED_WINDOW,
194 B_ASYNCHRONOUS_CONTROLS),
195 fCreationTime(system_time()),
196 fInfoWin(NULL),
197 fPlaylistWindow(NULL),
198 fHasFile(false),
199 fHasVideo(false),
200 fHasAudio(false),
201 fPlaylist(new Playlist),
202 fPlaylistObserver(new PlaylistObserver(this)),
203 fController(new Controller),
204 fControllerObserver(new ControllerObserver(this,
205 OBSERVE_FILE_CHANGES | OBSERVE_TRACK_CHANGES
206 | OBSERVE_PLAYBACK_STATE_CHANGES | OBSERVE_POSITION_CHANGES
207 | OBSERVE_VOLUME_CHANGES)),
208 fIsFullscreen(false),
209 fAlwaysOnTop(false),
210 fNoInterface(false),
211 fShowsFullscreenControls(false),
212 fSourceWidth(-1),
213 fSourceHeight(-1),
214 fWidthAspect(0),
215 fHeightAspect(0),
216 fSavedFrame(),
217 fNoVideoFrame(),
219 fMouseDownTracking(false),
220 fLastMousePos(0, 0),
221 fLastMouseMovedTime(system_time()),
222 fMouseMoveDist(0),
224 fGlobalSettingsListener(this),
225 fInitialSeekPosition(0),
226 fAllowWinding(true)
228 // Handle window position and size depending on whether this is the
229 // first window or not. Use the window size from the window that was
230 // last resized by the user.
231 static int pos = 0;
232 MoveBy(pos * 25, pos * 25);
233 pos = (pos + 1) % 15;
235 BRect frame = Settings::Default()->AudioPlayerWindowFrame();
236 if (frame.IsValid()) {
237 if (isFirstWindow) {
238 if (message == NULL) {
239 MoveTo(frame.LeftTop());
240 ResizeTo(frame.Width(), frame.Height());
241 } else {
242 // Delay moving to the initial position, since we don't
243 // know if we will be playing audio at all.
244 message->AddRect("window frame", frame);
247 if (sNoVideoWidth == MIN_WIDTH)
248 sNoVideoWidth = frame.IntegerWidth();
249 } else if (sNoVideoWidth > MIN_WIDTH) {
250 ResizeTo(sNoVideoWidth, Bounds().Height());
252 fNoVideoWidth = sNoVideoWidth;
254 BRect rect = Bounds();
256 // background
257 fBackground = new BView(rect, "background", B_FOLLOW_ALL,
258 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
259 fBackground->SetViewColor(0, 0, 0);
260 AddChild(fBackground);
262 // menu
263 fMenuBar = new BMenuBar(fBackground->Bounds(), "menu");
264 _CreateMenu();
265 fBackground->AddChild(fMenuBar);
266 fMenuBar->SetResizingMode(B_FOLLOW_NONE);
267 fMenuBar->ResizeToPreferred();
268 fMenuBarWidth = (int)fMenuBar->Frame().Width() + 1;
269 fMenuBarHeight = (int)fMenuBar->Frame().Height() + 1;
271 // video view
272 rect = BRect(0, fMenuBarHeight, fBackground->Bounds().right,
273 fMenuBarHeight + 10);
274 fVideoView = new VideoView(rect, "video display", B_FOLLOW_NONE);
275 fBackground->AddChild(fVideoView);
277 // controls
278 rect = BRect(0, fMenuBarHeight + 11, fBackground->Bounds().right,
279 fBackground->Bounds().bottom);
280 fControls = new ControllerView(rect, fController, fPlaylist);
281 fBackground->AddChild(fControls);
282 fControls->ResizeToPreferred();
283 fControlsHeight = (int)fControls->Frame().Height() + 1;
284 fControlsWidth = (int)fControls->Frame().Width() + 1;
285 fControls->SetResizingMode(B_FOLLOW_BOTTOM | B_FOLLOW_LEFT_RIGHT);
286 fControls->SetDisabledString(kDisabledSeekMessage);
288 fPlaylist->AddListener(fPlaylistObserver);
289 fController->SetVideoView(fVideoView);
290 fController->AddListener(fControllerObserver);
291 PeakView* peakView = fControls->GetPeakView();
292 peakView->SetPeakNotificationWhat(MSG_PEAK_NOTIFICATION);
293 fController->SetPeakListener(peakView);
295 _SetupWindow();
297 // setup the playlist window now, we need to have it
298 // running for the undo/redo playlist editing
299 fPlaylistWindow = new PlaylistWindow(BRect(150, 150, 500, 600), fPlaylist,
300 fController);
301 fPlaylistWindow->Hide();
302 fPlaylistWindow->Show();
303 // this makes sure the window thread is running without
304 // showing the window just yet
306 Settings::Default()->AddListener(&fGlobalSettingsListener);
307 _AdoptGlobalSettings();
309 AddShortcut('z', B_COMMAND_KEY, new BMessage(B_UNDO));
310 AddShortcut('y', B_COMMAND_KEY, new BMessage(B_UNDO));
311 AddShortcut('z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO));
312 AddShortcut('y', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO));
314 Hide();
315 Show();
317 if (message != NULL)
318 PostMessage(message);
320 BMediaRoster* roster = BMediaRoster::Roster();
321 roster->StartWatching(BMessenger(this, this), B_MEDIA_SERVER_STARTED);
322 roster->StartWatching(BMessenger(this, this), B_MEDIA_SERVER_QUIT);
326 MainWin::~MainWin()
328 // printf("MainWin::~MainWin\n");
330 BMediaRoster* roster = BMediaRoster::CurrentRoster();
331 roster->StopWatching(BMessenger(this, this), B_MEDIA_SERVER_STARTED);
332 roster->StopWatching(BMessenger(this, this), B_MEDIA_SERVER_QUIT);
334 Settings::Default()->RemoveListener(&fGlobalSettingsListener);
335 fPlaylist->RemoveListener(fPlaylistObserver);
336 fController->Lock();
337 fController->RemoveListener(fControllerObserver);
338 fController->SetPeakListener(NULL);
339 fController->SetVideoTarget(NULL);
340 fController->Unlock();
342 // give the views a chance to detach from any notifiers
343 // before we delete them
344 fBackground->RemoveSelf();
345 delete fBackground;
347 if (fInfoWin && fInfoWin->Lock())
348 fInfoWin->Quit();
350 if (fPlaylistWindow && fPlaylistWindow->Lock())
351 fPlaylistWindow->Quit();
353 delete fPlaylist;
354 fPlaylist = NULL;
356 // quit the Controller looper thread
357 thread_id controllerThread = fController->Thread();
358 fController->PostMessage(B_QUIT_REQUESTED);
359 status_t exitValue;
360 wait_for_thread(controllerThread, &exitValue);
364 // #pragma mark -
367 void
368 MainWin::FrameResized(float newWidth, float newHeight)
370 if (newWidth != Bounds().Width() || newHeight != Bounds().Height()) {
371 debugger("size wrong\n");
374 bool noMenu = fNoInterface || fIsFullscreen;
375 bool noControls = fNoInterface || fIsFullscreen;
377 // printf("FrameResized enter: newWidth %.0f, newHeight %.0f\n",
378 // newWidth, newHeight);
380 if (!fHasVideo)
381 sNoVideoWidth = fNoVideoWidth = (int)newWidth;
383 int maxVideoWidth = int(newWidth) + 1;
384 int maxVideoHeight = int(newHeight) + 1
385 - (noMenu ? 0 : fMenuBarHeight)
386 - (noControls ? 0 : fControlsHeight);
388 ASSERT(maxVideoHeight >= 0);
390 int y = 0;
392 if (noMenu) {
393 if (!fMenuBar->IsHidden(fMenuBar))
394 fMenuBar->Hide();
395 } else {
396 fMenuBar->MoveTo(0, y);
397 fMenuBar->ResizeTo(newWidth, fMenuBarHeight - 1);
398 if (fMenuBar->IsHidden(fMenuBar))
399 fMenuBar->Show();
400 y += fMenuBarHeight;
403 if (maxVideoHeight == 0) {
404 if (!fVideoView->IsHidden(fVideoView))
405 fVideoView->Hide();
406 } else {
407 _ResizeVideoView(0, y, maxVideoWidth, maxVideoHeight);
408 if (fVideoView->IsHidden(fVideoView))
409 fVideoView->Show();
410 y += maxVideoHeight;
413 if (noControls) {
414 if (!fControls->IsHidden(fControls))
415 fControls->Hide();
416 } else {
417 fControls->MoveTo(0, y);
418 fControls->ResizeTo(newWidth, fControlsHeight - 1);
419 if (fControls->IsHidden(fControls))
420 fControls->Show();
421 // y += fControlsHeight;
424 // printf("FrameResized leave\n");
428 void
429 MainWin::Zoom(BPoint /*position*/, float /*width*/, float /*height*/)
431 PostMessage(M_TOGGLE_FULLSCREEN);
435 void
436 MainWin::DispatchMessage(BMessage* msg, BHandler* handler)
438 if ((msg->what == B_MOUSE_DOWN)
439 && (handler == fBackground || handler == fVideoView
440 || handler == fControls)) {
441 _MouseDown(msg, dynamic_cast<BView*>(handler));
444 if ((msg->what == B_MOUSE_MOVED)
445 && (handler == fBackground || handler == fVideoView
446 || handler == fControls)) {
447 _MouseMoved(msg, dynamic_cast<BView*>(handler));
450 if ((msg->what == B_MOUSE_UP)
451 && (handler == fBackground || handler == fVideoView)) {
452 _MouseUp(msg);
455 if ((msg->what == B_KEY_DOWN)
456 && (handler == fBackground || handler == fVideoView)) {
457 // special case for PrintScreen key
458 if (msg->FindInt32("key") == B_PRINT_KEY) {
459 fVideoView->OverlayScreenshotPrepare();
460 BWindow::DispatchMessage(msg, handler);
461 fVideoView->OverlayScreenshotCleanup();
462 return;
465 // every other key gets dispatched to our _KeyDown first
466 if (_KeyDown(msg)) {
467 // it got handled, don't pass it on
468 return;
472 BWindow::DispatchMessage(msg, handler);
476 void
477 MainWin::MessageReceived(BMessage* msg)
479 // msg->PrintToStream();
480 switch (msg->what) {
481 case B_EXECUTE_PROPERTY:
482 case B_GET_PROPERTY:
483 case B_SET_PROPERTY:
485 BMessage reply(B_REPLY);
486 status_t result = B_BAD_SCRIPT_SYNTAX;
487 int32 index;
488 BMessage specifier;
489 int32 what;
490 const char* property;
492 if (msg->GetCurrentSpecifier(&index, &specifier, &what,
493 &property) != B_OK) {
494 return BWindow::MessageReceived(msg);
497 BPropertyInfo propertyInfo(sPropertyInfo);
498 switch (propertyInfo.FindMatch(msg, index, &specifier, what,
499 property)) {
500 case 0:
501 fControls->SkipForward();
502 result = B_OK;
503 break;
505 case 1:
506 fControls->SkipBackward();
507 result = B_OK;
508 break;
510 case 2:
511 fController->Play();
512 result = B_OK;
513 break;
515 case 3:
516 fController->Stop();
517 result = B_OK;
518 break;
520 case 4:
521 fController->Pause();
522 result = B_OK;
523 break;
525 case 5:
526 fController->TogglePlaying();
527 result = B_OK;
528 break;
530 case 6:
531 fController->ToggleMute();
532 result = B_OK;
533 break;
535 case 7:
537 if (msg->what == B_GET_PROPERTY) {
538 result = reply.AddFloat("result",
539 fController->Volume());
540 } else if (msg->what == B_SET_PROPERTY) {
541 float newVolume;
542 result = msg->FindFloat("data", &newVolume);
543 if (result == B_OK)
544 fController->SetVolume(newVolume);
546 break;
549 case 8:
551 if (msg->what == B_GET_PROPERTY) {
552 BAutolock _(fPlaylist);
553 const PlaylistItem* item = fController->Item();
554 if (item == NULL) {
555 result = B_NO_INIT;
556 break;
559 result = reply.AddString("result", item->LocationURI());
561 break;
564 case 9:
565 PostMessage(M_TOGGLE_FULLSCREEN);
566 break;
568 case 10:
569 if (msg->what != B_GET_PROPERTY)
570 break;
572 result = reply.AddInt64("result",
573 fController->TimeDuration());
574 break;
576 case 11:
578 if (msg->what == B_GET_PROPERTY) {
579 result = reply.AddInt64("result",
580 fController->TimePosition());
581 } else if (msg->what == B_SET_PROPERTY) {
582 int64 newTime;
583 result = msg->FindInt64("data", &newTime);
584 if (result == B_OK)
585 fController->SetTimePosition(newTime);
588 break;
591 case 12:
593 if (msg->what != B_SET_PROPERTY)
594 break;
596 bigtime_t seekBy;
597 result = msg->FindInt64("data", &seekBy);
598 if (result != B_OK)
599 break;
601 _Wind(seekBy, 0);
602 break;
605 default:
606 return BWindow::MessageReceived(msg);
609 if (result != B_OK) {
610 reply.what = B_MESSAGE_NOT_UNDERSTOOD;
611 reply.AddString("message", strerror(result));
612 reply.AddInt32("error", result);
615 msg->SendReply(&reply);
616 break;
619 case B_REFS_RECEIVED:
620 _RefsReceived(msg);
621 break;
622 case B_SIMPLE_DATA:
623 if (msg->HasRef("refs"))
624 _RefsReceived(msg);
625 break;
626 case M_OPEN_PREVIOUS_PLAYLIST:
627 OpenPlaylist(msg);
628 break;
630 case B_UNDO:
631 case B_REDO:
632 fPlaylistWindow->PostMessage(msg);
633 break;
635 case B_MEDIA_SERVER_STARTED:
637 printf("TODO: implement B_MEDIA_SERVER_STARTED\n");
639 // BAutolock _(fPlaylist);
640 // BMessage fakePlaylistMessage(MSG_PLAYLIST_CURRENT_ITEM_CHANGED);
641 // fakePlaylistMessage.AddInt32("index",
642 // fPlaylist->CurrentItemIndex());
643 // PostMessage(&fakePlaylistMessage);
644 break;
647 case B_MEDIA_SERVER_QUIT:
648 printf("TODO: implement B_MEDIA_SERVER_QUIT\n");
649 // if (fController->Lock()) {
650 // fController->CleanupNodes();
651 // fController->Unlock();
652 // }
653 break;
655 // PlaylistObserver messages
656 case MSG_PLAYLIST_ITEM_ADDED:
658 PlaylistItem* item;
659 int32 index;
660 if (msg->FindPointer("item", (void**)&item) == B_OK
661 && msg->FindInt32("index", &index) == B_OK) {
662 _AddPlaylistItem(item, index);
664 break;
666 case MSG_PLAYLIST_ITEM_REMOVED:
668 int32 index;
669 if (msg->FindInt32("index", &index) == B_OK)
670 _RemovePlaylistItem(index);
671 break;
673 case MSG_PLAYLIST_CURRENT_ITEM_CHANGED:
675 BAutolock _(fPlaylist);
677 int32 index;
678 // if false, the message was meant to only update the GUI
679 bool play;
680 if (msg->FindBool("play", &play) < B_OK || !play)
681 break;
682 if (msg->FindInt32("index", &index) < B_OK
683 || index != fPlaylist->CurrentItemIndex())
684 break;
685 PlaylistItemRef item(fPlaylist->ItemAt(index));
686 if (item.Get() != NULL) {
687 printf("open playlist item: %s\n", item->Name().String());
688 OpenPlaylistItem(item);
689 _MarkPlaylistItem(index);
691 break;
693 case MSG_PLAYLIST_IMPORT_FAILED:
695 BAlert* alert = new BAlert(B_TRANSLATE("Nothing to Play"),
696 B_TRANSLATE("None of the files you wanted to play appear "
697 "to be media files."), B_TRANSLATE("OK"));
698 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
699 alert->Go();
700 fControls->SetDisabledString(kDisabledSeekMessage);
701 break;
704 // ControllerObserver messages
705 case MSG_CONTROLLER_FILE_FINISHED:
707 BAutolock _(fPlaylist);
709 bool hadNext = fPlaylist->SetCurrentItemIndex(
710 fPlaylist->CurrentItemIndex() + 1);
711 if (!hadNext) {
712 // Reached end of playlist
713 // Handle "quit when done" settings
714 if ((fHasVideo && fCloseWhenDonePlayingMovie)
715 || (!fHasVideo && fCloseWhenDonePlayingSound))
716 PostMessage(B_QUIT_REQUESTED);
717 // Handle "loop by default" settings
718 if ((fHasVideo && fLoopMovies)
719 || (!fHasVideo && fLoopSounds)) {
720 if (fPlaylist->CountItems() > 1)
721 fPlaylist->SetCurrentItemIndex(0);
722 else
723 fController->Play();
726 break;
728 case MSG_CONTROLLER_FILE_CHANGED:
730 status_t result = B_ERROR;
731 msg->FindInt32("result", &result);
732 PlaylistItemRef itemRef;
733 PlaylistItem* item;
734 if (msg->FindPointer("item", (void**)&item) == B_OK) {
735 itemRef.SetTo(item, true);
736 // The reference was passed along with the message.
737 } else {
738 BAutolock _(fPlaylist);
739 itemRef.SetTo(fPlaylist->ItemAt(
740 fPlaylist->CurrentItemIndex()));
742 _PlaylistItemOpened(itemRef, result);
743 break;
745 case MSG_CONTROLLER_VIDEO_TRACK_CHANGED:
747 int32 index;
748 if (msg->FindInt32("index", &index) == B_OK) {
749 int32 i = 0;
750 while (BMenuItem* item = fVideoTrackMenu->ItemAt(i)) {
751 item->SetMarked(i == index);
752 i++;
755 break;
757 case MSG_CONTROLLER_AUDIO_TRACK_CHANGED:
759 int32 index;
760 if (msg->FindInt32("index", &index) == B_OK) {
761 int32 i = 0;
762 while (BMenuItem* item = fAudioTrackMenu->ItemAt(i)) {
763 item->SetMarked(i == index);
764 i++;
766 _UpdateAudioChannelCount(index);
768 break;
770 case MSG_CONTROLLER_SUB_TITLE_TRACK_CHANGED:
772 int32 index;
773 if (msg->FindInt32("index", &index) == B_OK) {
774 int32 i = 0;
775 while (BMenuItem* item = fSubTitleTrackMenu->ItemAt(i)) {
776 BMessage* message = item->Message();
777 if (message != NULL) {
778 item->SetMarked((int32)message->what
779 - M_SELECT_SUB_TITLE_TRACK == index);
781 i++;
784 break;
786 case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED:
788 uint32 state;
789 if (msg->FindInt32("state", (int32*)&state) == B_OK)
790 fControls->SetPlaybackState(state);
791 break;
793 case MSG_CONTROLLER_POSITION_CHANGED:
795 float position;
796 if (msg->FindFloat("position", &position) == B_OK) {
797 fControls->SetPosition(position, fController->TimePosition(),
798 fController->TimeDuration());
799 fAllowWinding = true;
801 break;
803 case MSG_CONTROLLER_SEEK_HANDLED:
804 break;
806 case MSG_CONTROLLER_VOLUME_CHANGED:
808 float volume;
809 if (msg->FindFloat("volume", &volume) == B_OK)
810 fControls->SetVolume(volume);
811 break;
813 case MSG_CONTROLLER_MUTED_CHANGED:
815 bool muted;
816 if (msg->FindBool("muted", &muted) == B_OK)
817 fControls->SetMuted(muted);
818 break;
821 // menu item messages
822 case M_FILE_OPEN:
824 BMessenger target(this);
825 BMessage result(B_REFS_RECEIVED);
826 BMessage appMessage(M_SHOW_OPEN_PANEL);
827 appMessage.AddMessenger("target", target);
828 appMessage.AddMessage("message", &result);
829 appMessage.AddString("title", B_TRANSLATE("Open clips"));
830 appMessage.AddString("label", B_TRANSLATE("Open"));
831 be_app->PostMessage(&appMessage);
832 break;
834 case M_FILE_INFO:
835 ShowFileInfo();
836 break;
837 case M_FILE_PLAYLIST:
838 ShowPlaylistWindow();
839 break;
840 case M_FILE_CLOSE:
841 PostMessage(B_QUIT_REQUESTED);
842 break;
843 case M_FILE_QUIT:
844 be_app->PostMessage(B_QUIT_REQUESTED);
845 break;
847 case M_TOGGLE_FULLSCREEN:
848 _ToggleFullscreen();
849 break;
851 case M_TOGGLE_ALWAYS_ON_TOP:
852 _ToggleAlwaysOnTop();
853 break;
855 case M_TOGGLE_NO_INTERFACE:
856 _ToggleNoInterface();
857 break;
859 case M_VIEW_SIZE:
861 int32 size;
862 if (msg->FindInt32("size", &size) == B_OK) {
863 if (!fHasVideo)
864 break;
865 if (fIsFullscreen)
866 _ToggleFullscreen();
867 _ResizeWindow(size);
869 break;
873 case B_ACQUIRE_OVERLAY_LOCK:
874 printf("B_ACQUIRE_OVERLAY_LOCK\n");
875 fVideoView->OverlayLockAcquire();
876 break;
878 case B_RELEASE_OVERLAY_LOCK:
879 printf("B_RELEASE_OVERLAY_LOCK\n");
880 fVideoView->OverlayLockRelease();
881 break;
883 case B_MOUSE_WHEEL_CHANGED:
885 float dx = msg->FindFloat("be:wheel_delta_x");
886 float dy = msg->FindFloat("be:wheel_delta_y");
887 bool inv = modifiers() & B_COMMAND_KEY;
888 if (dx > 0.1)
889 PostMessage(inv ? M_VOLUME_DOWN : M_SKIP_PREV);
890 if (dx < -0.1)
891 PostMessage(inv ? M_VOLUME_UP : M_SKIP_NEXT);
892 if (dy > 0.1)
893 PostMessage(inv ? M_SKIP_PREV : M_VOLUME_DOWN);
894 if (dy < -0.1)
895 PostMessage(inv ? M_SKIP_NEXT : M_VOLUME_UP);
896 break;
899 case M_SKIP_NEXT:
900 fControls->SkipForward();
901 break;
903 case M_SKIP_PREV:
904 fControls->SkipBackward();
905 break;
907 case M_WIND:
909 bigtime_t howMuch;
910 int64 frames;
911 if (msg->FindInt64("how much", &howMuch) != B_OK
912 || msg->FindInt64("frames", &frames) != B_OK) {
913 break;
916 _Wind(howMuch, frames);
917 break;
920 case M_VOLUME_UP:
921 fController->VolumeUp();
922 break;
924 case M_VOLUME_DOWN:
925 fController->VolumeDown();
926 break;
928 case M_ASPECT_SAME_AS_SOURCE:
929 if (fHasVideo) {
930 int width;
931 int height;
932 int widthAspect;
933 int heightAspect;
934 fController->GetSize(&width, &height,
935 &widthAspect, &heightAspect);
936 VideoFormatChange(width, height, widthAspect, heightAspect);
938 break;
940 case M_ASPECT_NO_DISTORTION:
941 if (fHasVideo) {
942 int width;
943 int height;
944 fController->GetSize(&width, &height);
945 VideoFormatChange(width, height, width, height);
947 break;
949 case M_ASPECT_4_3:
950 VideoAspectChange(4, 3);
951 break;
953 case M_ASPECT_16_9: // 1.77 : 1
954 VideoAspectChange(16, 9);
955 break;
957 case M_ASPECT_83_50: // 1.66 : 1
958 VideoAspectChange(83, 50);
959 break;
961 case M_ASPECT_7_4: // 1.75 : 1
962 VideoAspectChange(7, 4);
963 break;
965 case M_ASPECT_37_20: // 1.85 : 1
966 VideoAspectChange(37, 20);
967 break;
969 case M_ASPECT_47_20: // 2.35 : 1
970 VideoAspectChange(47, 20);
971 break;
973 case M_SET_PLAYLIST_POSITION:
975 BAutolock _(fPlaylist);
977 int32 index;
978 if (msg->FindInt32("index", &index) == B_OK)
979 fPlaylist->SetCurrentItemIndex(index);
980 break;
983 case MSG_OBJECT_CHANGED:
984 // received from fGlobalSettingsListener
985 // TODO: find out which object, if we ever watch more than
986 // the global settings instance...
987 _AdoptGlobalSettings();
988 break;
990 case M_SLIDE_CONTROLS:
992 float offset;
993 if (msg->FindFloat("offset", &offset) == B_OK) {
994 fControls->MoveBy(0, offset);
995 fVideoView->SetSubTitleMaxBottom(fControls->Frame().top - 1);
996 UpdateIfNeeded();
997 snooze(15000);
999 break;
1001 case M_FINISH_SLIDING_CONTROLS:
1003 float offset;
1004 bool show;
1005 if (msg->FindFloat("offset", &offset) == B_OK
1006 && msg->FindBool("show", &show) == B_OK) {
1007 if (show) {
1008 fControls->MoveTo(fControls->Frame().left, offset);
1009 fVideoView->SetSubTitleMaxBottom(offset - 1);
1010 } else {
1011 fVideoView->SetSubTitleMaxBottom(
1012 fVideoView->Bounds().bottom);
1013 fControls->RemoveSelf();
1014 fControls->MoveTo(fVideoView->Frame().left,
1015 fVideoView->Frame().bottom + 1);
1016 fBackground->AddChild(fControls);
1017 fControls->SetSymbolScale(1.0f);
1018 while (!fControls->IsHidden())
1019 fControls->Hide();
1022 break;
1024 case M_HIDE_FULL_SCREEN_CONTROLS:
1025 if (fIsFullscreen) {
1026 BPoint videoViewWhere;
1027 if (msg->FindPoint("where", &videoViewWhere) == B_OK) {
1028 if (msg->FindBool("force")
1029 || !fControls->Frame().Contains(videoViewWhere)) {
1030 _ShowFullscreenControls(false);
1031 // hide the mouse cursor until the user moves it
1032 be_app->ObscureCursor();
1036 break;
1038 case M_SET_RATING:
1040 int32 rating;
1041 if (msg->FindInt32("rating", &rating) == B_OK)
1042 _SetRating(rating);
1043 break;
1046 default:
1047 if (msg->what >= M_SELECT_AUDIO_TRACK
1048 && msg->what <= M_SELECT_AUDIO_TRACK_END) {
1049 fController->SelectAudioTrack(msg->what - M_SELECT_AUDIO_TRACK);
1050 break;
1052 if (msg->what >= M_SELECT_VIDEO_TRACK
1053 && msg->what <= M_SELECT_VIDEO_TRACK_END) {
1054 fController->SelectVideoTrack(msg->what - M_SELECT_VIDEO_TRACK);
1055 break;
1057 if ((int32)msg->what >= M_SELECT_SUB_TITLE_TRACK - 1
1058 && msg->what <= M_SELECT_SUB_TITLE_TRACK_END) {
1059 fController->SelectSubTitleTrack((int32)msg->what
1060 - M_SELECT_SUB_TITLE_TRACK);
1061 break;
1063 // let BWindow handle the rest
1064 BWindow::MessageReceived(msg);
1069 void
1070 MainWin::WindowActivated(bool active)
1072 fController->PlayerActivated(active);
1076 bool
1077 MainWin::QuitRequested()
1079 BMessage message(M_PLAYER_QUIT);
1080 GetQuitMessage(&message);
1081 be_app->PostMessage(&message);
1082 return true;
1086 void
1087 MainWin::MenusBeginning()
1089 _SetupVideoAspectItems(fVideoAspectMenu);
1093 // #pragma mark -
1096 void
1097 MainWin::OpenPlaylist(const BMessage* playlistArchive)
1099 if (playlistArchive == NULL)
1100 return;
1102 BAutolock _(this);
1103 BAutolock playlistLocker(fPlaylist);
1105 if (fPlaylist->Unarchive(playlistArchive) != B_OK)
1106 return;
1108 int32 currentIndex;
1109 if (playlistArchive->FindInt32("index", &currentIndex) != B_OK)
1110 currentIndex = 0;
1111 fPlaylist->SetCurrentItemIndex(currentIndex);
1113 playlistLocker.Unlock();
1115 if (currentIndex != -1) {
1116 // Restore the current play position only if we have something to play
1117 playlistArchive->FindInt64("position", (int64*)&fInitialSeekPosition);
1120 if (IsHidden())
1121 Show();
1125 void
1126 MainWin::OpenPlaylistItem(const PlaylistItemRef& item)
1128 status_t ret = fController->SetToAsync(item);
1129 if (ret != B_OK) {
1130 fprintf(stderr, "MainWin::OpenPlaylistItem() - Failed to send message "
1131 "to Controller.\n");
1132 BString message = B_TRANSLATE("%app% encountered an internal error. "
1133 "The file could not be opened.");
1134 message.ReplaceFirst("%app%", kApplicationName);
1135 BAlert* alert = new BAlert(kApplicationName, message.String(),
1136 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1137 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1138 alert->Go();
1139 _PlaylistItemOpened(item, ret);
1140 } else {
1141 BString string;
1142 string << "Opening '" << item->Name() << "'.";
1143 fControls->SetDisabledString(string.String());
1148 void
1149 MainWin::ShowFileInfo()
1151 if (!fInfoWin)
1152 fInfoWin = new InfoWin(Frame().LeftTop(), fController);
1154 if (fInfoWin->Lock()) {
1155 if (fInfoWin->IsHidden())
1156 fInfoWin->Show();
1157 else
1158 fInfoWin->Activate();
1159 fInfoWin->Unlock();
1164 void
1165 MainWin::ShowPlaylistWindow()
1167 if (fPlaylistWindow->Lock()) {
1168 // make sure the window shows on the same workspace as ourself
1169 uint32 workspaces = Workspaces();
1170 if (fPlaylistWindow->Workspaces() != workspaces)
1171 fPlaylistWindow->SetWorkspaces(workspaces);
1173 // show or activate
1174 if (fPlaylistWindow->IsHidden())
1175 fPlaylistWindow->Show();
1176 else
1177 fPlaylistWindow->Activate();
1179 fPlaylistWindow->Unlock();
1184 void
1185 MainWin::VideoAspectChange(int forcedWidth, int forcedHeight, float widthScale)
1187 // Force specific source size and pixel width scale.
1188 if (fHasVideo) {
1189 int width;
1190 int height;
1191 fController->GetSize(&width, &height);
1192 VideoFormatChange(forcedWidth, forcedHeight,
1193 lround(width * widthScale), height);
1198 void
1199 MainWin::VideoAspectChange(float widthScale)
1201 // Called when video aspect ratio changes and the original
1202 // width/height should be restored too, display aspect is not known,
1203 // only pixel width scale.
1204 if (fHasVideo) {
1205 int width;
1206 int height;
1207 fController->GetSize(&width, &height);
1208 VideoFormatChange(width, height, lround(width * widthScale), height);
1213 void
1214 MainWin::VideoAspectChange(int widthAspect, int heightAspect)
1216 // Called when video aspect ratio changes and the original
1217 // width/height should be restored too.
1218 if (fHasVideo) {
1219 int width;
1220 int height;
1221 fController->GetSize(&width, &height);
1222 VideoFormatChange(width, height, widthAspect, heightAspect);
1227 void
1228 MainWin::VideoFormatChange(int width, int height, int widthAspect,
1229 int heightAspect)
1231 // Called when video format or aspect ratio changes.
1233 printf("VideoFormatChange enter: width %d, height %d, "
1234 "aspect ratio: %d:%d\n", width, height, widthAspect, heightAspect);
1236 // remember current view scale
1237 int percent = _CurrentVideoSizeInPercent();
1239 fSourceWidth = width;
1240 fSourceHeight = height;
1241 fWidthAspect = widthAspect;
1242 fHeightAspect = heightAspect;
1244 if (percent == 100)
1245 _ResizeWindow(100);
1246 else
1247 FrameResized(Bounds().Width(), Bounds().Height());
1249 printf("VideoFormatChange leave\n");
1253 void
1254 MainWin::GetQuitMessage(BMessage* message)
1256 message->AddPointer("instance", this);
1257 message->AddRect("window frame", Frame());
1258 message->AddBool("audio only", !fHasVideo);
1259 message->AddInt64("creation time", fCreationTime);
1261 if (!fHasVideo && fHasAudio) {
1262 // store playlist, current index and position if this is audio
1263 BMessage playlistArchive;
1265 BAutolock controllerLocker(fController);
1266 playlistArchive.AddInt64("position", fController->TimePosition());
1267 controllerLocker.Unlock();
1269 if (!fPlaylist)
1270 return;
1272 BAutolock playlistLocker(fPlaylist);
1273 if (fPlaylist->Archive(&playlistArchive) != B_OK
1274 || playlistArchive.AddInt32("index",
1275 fPlaylist->CurrentItemIndex()) != B_OK
1276 || message->AddMessage("playlist", &playlistArchive) != B_OK) {
1277 fprintf(stderr, "Failed to store current playlist.\n");
1283 BHandler*
1284 MainWin::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
1285 int32 what, const char* property)
1287 BPropertyInfo propertyInfo(sPropertyInfo);
1288 switch (propertyInfo.FindMatch(message, index, specifier, what, property)) {
1289 case 0:
1290 case 1:
1291 case 2:
1292 case 3:
1293 case 4:
1294 case 5:
1295 case 6:
1296 case 7:
1297 case 8:
1298 case 9:
1299 case 10:
1300 case 11:
1301 case 12:
1302 return this;
1305 return BWindow::ResolveSpecifier(message, index, specifier, what, property);
1309 status_t
1310 MainWin::GetSupportedSuites(BMessage* data)
1312 if (data == NULL)
1313 return B_BAD_VALUE;
1315 status_t status = data->AddString("suites", "suite/vnd.Haiku-MediaPlayer");
1316 if (status != B_OK)
1317 return status;
1319 BPropertyInfo propertyInfo(sPropertyInfo);
1320 status = data->AddFlat("messages", &propertyInfo);
1321 if (status != B_OK)
1322 return status;
1324 return BWindow::GetSupportedSuites(data);
1328 // #pragma mark -
1331 void
1332 MainWin::_RefsReceived(BMessage* message)
1334 // the playlist is replaced by dropped files
1335 // or the dropped files are appended to the end
1336 // of the existing playlist if <shift> is pressed
1337 bool append = false;
1338 if (message->FindBool("append to playlist", &append) != B_OK)
1339 append = modifiers() & B_SHIFT_KEY;
1341 BAutolock _(fPlaylist);
1342 int32 appendIndex = append ? APPEND_INDEX_APPEND_LAST
1343 : APPEND_INDEX_REPLACE_PLAYLIST;
1344 message->AddInt32("append_index", appendIndex);
1346 // forward the message to the playlist window,
1347 // so that undo/redo is used for modifying the playlist
1348 fPlaylistWindow->PostMessage(message);
1350 if (message->FindRect("window frame", &fNoVideoFrame) != B_OK)
1351 fNoVideoFrame = BRect();
1355 void
1356 MainWin::_PlaylistItemOpened(const PlaylistItemRef& item, status_t result)
1358 if (result != B_OK) {
1359 BAutolock _(fPlaylist);
1361 item->SetPlaybackFailed();
1362 bool allItemsFailed = true;
1363 int32 count = fPlaylist->CountItems();
1364 for (int32 i = 0; i < count; i++) {
1365 if (!fPlaylist->ItemAtFast(i)->PlaybackFailed()) {
1366 allItemsFailed = false;
1367 break;
1371 if (allItemsFailed) {
1372 // Display error if all files failed to play.
1373 BString message(B_TRANSLATE(
1374 "The file '%filename' could not be opened.\n\n"));;
1375 message.ReplaceAll("%filename", item->Name());
1377 if (result == B_MEDIA_NO_HANDLER) {
1378 // give a more detailed message for the most likely of all
1379 // errors
1380 message << B_TRANSLATE(
1381 "There is no decoder installed to handle the "
1382 "file format, or the decoder has trouble with the "
1383 "specific version of the format.");
1384 } else {
1385 message << B_TRANSLATE("Error: ") << strerror(result);
1387 BAlert* alert = new BAlert("error", message.String(),
1388 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1389 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1390 alert->Go();
1391 fControls->SetDisabledString(kDisabledSeekMessage);
1392 } else {
1393 // Just go to the next file and don't bother user (yet)
1394 fPlaylist->SetCurrentItemIndex(fPlaylist->CurrentItemIndex() + 1);
1397 fHasFile = false;
1398 fHasVideo = false;
1399 fHasAudio = false;
1400 SetTitle(kApplicationName);
1401 } else {
1402 fHasFile = true;
1403 fHasVideo = fController->VideoTrackCount() != 0;
1404 fHasAudio = fController->AudioTrackCount() != 0;
1405 SetTitle(item->Name().String());
1407 if (fInitialSeekPosition < 0) {
1408 fInitialSeekPosition
1409 = fController->TimeDuration() + fInitialSeekPosition;
1411 fController->SetTimePosition(fInitialSeekPosition);
1412 fInitialSeekPosition = 0;
1414 _SetupWindow();
1416 if (result == B_OK)
1417 _UpdatePlaylistItemFile();
1421 void
1422 MainWin::_SetupWindow()
1424 // printf("MainWin::_SetupWindow\n");
1425 // Populate the track menus
1426 _SetupTrackMenus(fAudioTrackMenu, fVideoTrackMenu, fSubTitleTrackMenu);
1427 _UpdateAudioChannelCount(fController->CurrentAudioTrack());
1429 fVideoMenu->SetEnabled(fHasVideo);
1430 fAudioMenu->SetEnabled(fHasAudio);
1431 int previousSourceWidth = fSourceWidth;
1432 int previousSourceHeight = fSourceHeight;
1433 int previousWidthAspect = fWidthAspect;
1434 int previousHeightAspect = fHeightAspect;
1435 if (fHasVideo) {
1436 fController->GetSize(&fSourceWidth, &fSourceHeight,
1437 &fWidthAspect, &fHeightAspect);
1438 } else {
1439 fSourceWidth = 0;
1440 fSourceHeight = 0;
1441 fWidthAspect = 1;
1442 fHeightAspect = 1;
1444 _UpdateControlsEnabledStatus();
1446 // Adopt the size and window layout if necessary
1447 if (previousSourceWidth != fSourceWidth
1448 || previousSourceHeight != fSourceHeight
1449 || previousWidthAspect != fWidthAspect
1450 || previousHeightAspect != fHeightAspect) {
1452 _SetWindowSizeLimits();
1454 if (!fIsFullscreen) {
1455 // Resize to 100% but stay on screen
1456 _ResizeWindow(100, !fHasVideo, true);
1457 } else {
1458 // Make sure we relayout the video view when in full screen mode
1459 FrameResized(Frame().Width(), Frame().Height());
1463 _ShowIfNeeded();
1465 fVideoView->MakeFocus();
1469 void
1470 MainWin::_CreateMenu()
1472 fFileMenu = new BMenu(kApplicationName);
1473 fPlaylistMenu = new BMenu(B_TRANSLATE("Playlist" B_UTF8_ELLIPSIS));
1474 fAudioMenu = new BMenu(B_TRANSLATE("Audio"));
1475 fVideoMenu = new BMenu(B_TRANSLATE("Video"));
1476 fVideoAspectMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
1477 fAudioTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track",
1478 "Audio Track Menu"));
1479 fVideoTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track",
1480 "Video Track Menu"));
1481 fSubTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
1482 fAttributesMenu = new BMenu(B_TRANSLATE("Attributes"));
1484 fMenuBar->AddItem(fFileMenu);
1485 fMenuBar->AddItem(fAudioMenu);
1486 fMenuBar->AddItem(fVideoMenu);
1487 fMenuBar->AddItem(fAttributesMenu);
1489 BMenuItem* item = new BMenuItem(B_TRANSLATE("New player" B_UTF8_ELLIPSIS),
1490 new BMessage(M_NEW_PLAYER), 'N');
1491 fFileMenu->AddItem(item);
1492 item->SetTarget(be_app);
1494 // Add recent files to "Open File" entry as sub-menu.
1495 BRecentFilesList recentFiles(10, false, NULL, kAppSig);
1496 item = new BMenuItem(recentFiles.NewFileListMenu(
1497 B_TRANSLATE("Open file" B_UTF8_ELLIPSIS), NULL, NULL, this, 10, true,
1498 NULL, kAppSig), new BMessage(M_FILE_OPEN));
1499 item->SetShortcut('O', 0);
1500 fFileMenu->AddItem(item);
1502 fFileMenu->AddSeparatorItem();
1504 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("File info" B_UTF8_ELLIPSIS),
1505 new BMessage(M_FILE_INFO), 'I'));
1506 fFileMenu->AddItem(fPlaylistMenu);
1507 fPlaylistMenu->Superitem()->SetShortcut('P', B_COMMAND_KEY);
1508 fPlaylistMenu->Superitem()->SetMessage(new BMessage(M_FILE_PLAYLIST));
1510 fFileMenu->AddSeparatorItem();
1512 fNoInterfaceMenuItem = new BMenuItem(B_TRANSLATE("Hide interface"),
1513 new BMessage(M_TOGGLE_NO_INTERFACE), 'H');
1514 fFileMenu->AddItem(fNoInterfaceMenuItem);
1515 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Always on top"),
1516 new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
1518 item = new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
1519 new BMessage(M_SETTINGS), 'S');
1520 fFileMenu->AddItem(item);
1521 item->SetTarget(be_app);
1523 fFileMenu->AddSeparatorItem();
1525 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
1526 new BMessage(M_FILE_CLOSE), 'W'));
1527 fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
1528 new BMessage(M_FILE_QUIT), 'Q'));
1530 fPlaylistMenu->SetRadioMode(true);
1532 fAudioMenu->AddItem(fAudioTrackMenu);
1534 fVideoMenu->AddItem(fVideoTrackMenu);
1535 fVideoMenu->AddItem(fSubTitleTrackMenu);
1536 fVideoMenu->AddSeparatorItem();
1537 BMessage* resizeMessage = new BMessage(M_VIEW_SIZE);
1538 resizeMessage->AddInt32("size", 50);
1539 fVideoMenu->AddItem(new BMenuItem(
1540 B_TRANSLATE("50% scale"), resizeMessage, '0'));
1542 resizeMessage = new BMessage(M_VIEW_SIZE);
1543 resizeMessage->AddInt32("size", 100);
1544 fVideoMenu->AddItem(new BMenuItem(
1545 B_TRANSLATE("100% scale"), resizeMessage, '1'));
1547 resizeMessage = new BMessage(M_VIEW_SIZE);
1548 resizeMessage->AddInt32("size", 200);
1549 fVideoMenu->AddItem(new BMenuItem(
1550 B_TRANSLATE("200% scale"), resizeMessage, '2'));
1552 resizeMessage = new BMessage(M_VIEW_SIZE);
1553 resizeMessage->AddInt32("size", 300);
1554 fVideoMenu->AddItem(new BMenuItem(
1555 B_TRANSLATE("300% scale"), resizeMessage, '3'));
1557 resizeMessage = new BMessage(M_VIEW_SIZE);
1558 resizeMessage->AddInt32("size", 400);
1559 fVideoMenu->AddItem(new BMenuItem(
1560 B_TRANSLATE("400% scale"), resizeMessage, '4'));
1562 fVideoMenu->AddSeparatorItem();
1564 fVideoMenu->AddItem(new BMenuItem(B_TRANSLATE("Full screen"),
1565 new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
1567 fVideoMenu->AddSeparatorItem();
1569 _SetupVideoAspectItems(fVideoAspectMenu);
1570 fVideoMenu->AddItem(fVideoAspectMenu);
1572 fRatingMenu = new BMenu(B_TRANSLATE("Rating"));
1573 fAttributesMenu->AddItem(fRatingMenu);
1574 for (int32 i = 1; i <= 10; i++) {
1575 char label[16];
1576 snprintf(label, sizeof(label), "%" B_PRId32, i);
1577 BMessage* setRatingMsg = new BMessage(M_SET_RATING);
1578 setRatingMsg->AddInt32("rating", i);
1579 fRatingMenu->AddItem(new BMenuItem(label, setRatingMsg));
1584 void
1585 MainWin::_SetupVideoAspectItems(BMenu* menu)
1587 BMenuItem* item;
1588 while ((item = menu->RemoveItem((int32)0)) != NULL)
1589 delete item;
1591 int width;
1592 int height;
1593 int widthAspect;
1594 int heightAspect;
1595 fController->GetSize(&width, &height, &widthAspect, &heightAspect);
1596 // We don't care if there is a video track at all. In that
1597 // case we should end up not marking any item.
1599 // NOTE: The item marking may end up marking for example both
1600 // "Stream Settings" and "16 : 9" if the stream settings happen to
1601 // be "16 : 9".
1603 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Stream settings"),
1604 new BMessage(M_ASPECT_SAME_AS_SOURCE), '1', B_SHIFT_KEY));
1605 item->SetMarked(widthAspect == fWidthAspect
1606 && heightAspect == fHeightAspect);
1608 menu->AddItem(item = new BMenuItem(B_TRANSLATE("No aspect correction"),
1609 new BMessage(M_ASPECT_NO_DISTORTION), '0', B_SHIFT_KEY));
1610 item->SetMarked(width == fWidthAspect && height == fHeightAspect);
1612 menu->AddSeparatorItem();
1614 menu->AddItem(item = new BMenuItem("4 : 3",
1615 new BMessage(M_ASPECT_4_3), 2, B_SHIFT_KEY));
1616 item->SetMarked(fWidthAspect == 4 && fHeightAspect == 3);
1617 menu->AddItem(item = new BMenuItem("16 : 9",
1618 new BMessage(M_ASPECT_16_9), 3, B_SHIFT_KEY));
1619 item->SetMarked(fWidthAspect == 16 && fHeightAspect == 9);
1621 menu->AddSeparatorItem();
1623 menu->AddItem(item = new BMenuItem("1.66 : 1",
1624 new BMessage(M_ASPECT_83_50)));
1625 item->SetMarked(fWidthAspect == 83 && fHeightAspect == 50);
1626 menu->AddItem(item = new BMenuItem("1.75 : 1",
1627 new BMessage(M_ASPECT_7_4)));
1628 item->SetMarked(fWidthAspect == 7 && fHeightAspect == 4);
1629 menu->AddItem(item = new BMenuItem(B_TRANSLATE("1.85 : 1 (American)"),
1630 new BMessage(M_ASPECT_37_20)));
1631 item->SetMarked(fWidthAspect == 37 && fHeightAspect == 20);
1632 menu->AddItem(item = new BMenuItem(B_TRANSLATE("2.35 : 1 (Cinemascope)"),
1633 new BMessage(M_ASPECT_47_20)));
1634 item->SetMarked(fWidthAspect == 47 && fHeightAspect == 20);
1638 void
1639 MainWin::_SetupTrackMenus(BMenu* audioTrackMenu, BMenu* videoTrackMenu,
1640 BMenu* subTitleTrackMenu)
1642 audioTrackMenu->RemoveItems(0, audioTrackMenu->CountItems(), true);
1643 videoTrackMenu->RemoveItems(0, videoTrackMenu->CountItems(), true);
1644 subTitleTrackMenu->RemoveItems(0, subTitleTrackMenu->CountItems(), true);
1646 char s[100];
1648 int count = fController->AudioTrackCount();
1649 int current = fController->CurrentAudioTrack();
1650 for (int i = 0; i < count; i++) {
1651 BMessage metaData;
1652 const char* languageString = NULL;
1653 if (fController->GetAudioMetaData(i, &metaData) == B_OK)
1654 metaData.FindString("language", &languageString);
1655 if (languageString != NULL) {
1656 BLanguage language(languageString);
1657 BString languageName;
1658 if (language.GetName(languageName) == B_OK)
1659 languageString = languageName.String();
1660 snprintf(s, sizeof(s), "%s", languageString);
1661 } else
1662 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1663 BMenuItem* item = new BMenuItem(s,
1664 new BMessage(M_SELECT_AUDIO_TRACK + i));
1665 item->SetMarked(i == current);
1666 audioTrackMenu->AddItem(item);
1668 if (count == 0) {
1669 audioTrackMenu->AddItem(new BMenuItem(B_TRANSLATE_CONTEXT("none",
1670 "Audio track menu"), new BMessage(M_DUMMY)));
1671 audioTrackMenu->ItemAt(0)->SetMarked(true);
1673 audioTrackMenu->SetEnabled(count > 1);
1675 count = fController->VideoTrackCount();
1676 current = fController->CurrentVideoTrack();
1677 for (int i = 0; i < count; i++) {
1678 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1679 BMenuItem* item = new BMenuItem(s,
1680 new BMessage(M_SELECT_VIDEO_TRACK + i));
1681 item->SetMarked(i == current);
1682 videoTrackMenu->AddItem(item);
1684 if (count == 0) {
1685 videoTrackMenu->AddItem(new BMenuItem(B_TRANSLATE("none"),
1686 new BMessage(M_DUMMY)));
1687 videoTrackMenu->ItemAt(0)->SetMarked(true);
1689 videoTrackMenu->SetEnabled(count > 1);
1691 count = fController->SubTitleTrackCount();
1692 if (count > 0) {
1693 current = fController->CurrentSubTitleTrack();
1694 BMenuItem* item = new BMenuItem(
1695 B_TRANSLATE_CONTEXT("Off", "Subtitles menu"),
1696 new BMessage(M_SELECT_SUB_TITLE_TRACK - 1));
1697 subTitleTrackMenu->AddItem(item);
1698 item->SetMarked(current == -1);
1700 subTitleTrackMenu->AddSeparatorItem();
1702 for (int i = 0; i < count; i++) {
1703 const char* name = fController->SubTitleTrackName(i);
1704 if (name != NULL)
1705 snprintf(s, sizeof(s), "%s", name);
1706 else
1707 snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1708 item = new BMenuItem(s,
1709 new BMessage(M_SELECT_SUB_TITLE_TRACK + i));
1710 item->SetMarked(i == current);
1711 subTitleTrackMenu->AddItem(item);
1713 } else {
1714 subTitleTrackMenu->AddItem(new BMenuItem(
1715 B_TRANSLATE_CONTEXT("none", "Subtitles menu"),
1716 new BMessage(M_DUMMY)));
1717 subTitleTrackMenu->ItemAt(0)->SetMarked(true);
1719 subTitleTrackMenu->SetEnabled(count > 0);
1723 void
1724 MainWin::_UpdateAudioChannelCount(int32 audioTrackIndex)
1726 fControls->SetAudioChannelCount(fController->AudioTrackChannelCount());
1730 void
1731 MainWin::_GetMinimumWindowSize(int& width, int& height) const
1733 width = MIN_WIDTH;
1734 height = 0;
1735 if (!fNoInterface) {
1736 width = max_c(width, fMenuBarWidth);
1737 width = max_c(width, fControlsWidth);
1738 height = fMenuBarHeight + fControlsHeight;
1743 void
1744 MainWin::_GetUnscaledVideoSize(int& videoWidth, int& videoHeight) const
1746 if (fWidthAspect != 0 && fHeightAspect != 0) {
1747 videoWidth = fSourceHeight * fWidthAspect / fHeightAspect;
1748 videoHeight = fSourceWidth * fHeightAspect / fWidthAspect;
1749 // Use the scaling which produces an enlarged view.
1750 if (videoWidth > fSourceWidth) {
1751 // Enlarge width
1752 videoHeight = fSourceHeight;
1753 } else {
1754 // Enlarge height
1755 videoWidth = fSourceWidth;
1757 } else {
1758 videoWidth = fSourceWidth;
1759 videoHeight = fSourceHeight;
1764 void
1765 MainWin::_SetWindowSizeLimits()
1767 int minWidth;
1768 int minHeight;
1769 _GetMinimumWindowSize(minWidth, minHeight);
1770 SetSizeLimits(minWidth - 1, 32000, minHeight - 1,
1771 fHasVideo ? 32000 : minHeight - 1);
1776 MainWin::_CurrentVideoSizeInPercent() const
1778 if (!fHasVideo)
1779 return 0;
1781 int videoWidth;
1782 int videoHeight;
1783 _GetUnscaledVideoSize(videoWidth, videoHeight);
1785 int viewWidth = fVideoView->Bounds().IntegerWidth() + 1;
1786 int viewHeight = fVideoView->Bounds().IntegerHeight() + 1;
1788 int widthPercent = viewWidth * 100 / videoWidth;
1789 int heightPercent = viewHeight * 100 / videoHeight;
1791 if (widthPercent > heightPercent)
1792 return widthPercent;
1793 return heightPercent;
1797 void
1798 MainWin::_ZoomVideoView(int percentDiff)
1800 if (!fHasVideo)
1801 return;
1803 int percent = _CurrentVideoSizeInPercent();
1804 int newSize = percent * (100 + percentDiff) / 100;
1806 if (newSize < 25)
1807 newSize = 25;
1808 if (newSize > 400)
1809 newSize = 400;
1810 if (newSize != percent) {
1811 BMessage message(M_VIEW_SIZE);
1812 message.AddInt32("size", newSize);
1813 PostMessage(&message);
1818 void
1819 MainWin::_ResizeWindow(int percent, bool useNoVideoWidth, bool stayOnScreen)
1821 // Get required window size
1822 int videoWidth;
1823 int videoHeight;
1824 _GetUnscaledVideoSize(videoWidth, videoHeight);
1826 videoWidth = (videoWidth * percent) / 100;
1827 videoHeight = (videoHeight * percent) / 100;
1829 // Calculate and set the minimum window size
1830 int width;
1831 int height;
1832 _GetMinimumWindowSize(width, height);
1834 width = max_c(width, videoWidth) - 1;
1835 if (useNoVideoWidth)
1836 width = max_c(width, fNoVideoWidth);
1837 height = height + videoHeight - 1;
1839 if (stayOnScreen) {
1840 BRect screenFrame(BScreen(this).Frame());
1841 BRect frame(Frame());
1842 BRect decoratorFrame(DecoratorFrame());
1844 // Shrink the screen frame by the window border size
1845 screenFrame.top += frame.top - decoratorFrame.top;
1846 screenFrame.left += frame.left - decoratorFrame.left;
1847 screenFrame.right += frame.right - decoratorFrame.right;
1848 screenFrame.bottom += frame.bottom - decoratorFrame.bottom;
1850 // Update frame to what the new size would be
1851 frame.right = frame.left + width;
1852 frame.bottom = frame.top + height;
1854 if (!screenFrame.Contains(frame)) {
1855 // Resize the window so it doesn't extend outside the current
1856 // screen frame.
1857 if (frame.Width() > screenFrame.Width()
1858 || frame.Height() > screenFrame.Height()) {
1859 // too large
1860 int widthDiff
1861 = frame.IntegerWidth() - screenFrame.IntegerWidth();
1862 int heightDiff
1863 = frame.IntegerHeight() - screenFrame.IntegerHeight();
1865 float shrinkScale;
1866 if (widthDiff > heightDiff)
1867 shrinkScale = (float)(width - widthDiff) / width;
1868 else
1869 shrinkScale = (float)(height - heightDiff) / height;
1871 // Resize width/height and center window
1872 width = lround(width * shrinkScale);
1873 height = lround(height * shrinkScale);
1874 MoveTo((screenFrame.left + screenFrame.right - width) / 2,
1875 (screenFrame.top + screenFrame.bottom - height) / 2);
1876 } else {
1877 // just off-screen on one or more sides
1878 int offsetX = 0;
1879 int offsetY = 0;
1880 if (frame.left < screenFrame.left)
1881 offsetX = (int)(screenFrame.left - frame.left);
1882 else if (frame.right > screenFrame.right)
1883 offsetX = (int)(screenFrame.right - frame.right);
1884 if (frame.top < screenFrame.top)
1885 offsetY = (int)(screenFrame.top - frame.top);
1886 else if (frame.bottom > screenFrame.bottom)
1887 offsetY = (int)(screenFrame.bottom - frame.bottom);
1888 MoveBy(offsetX, offsetY);
1893 ResizeTo(width, height);
1897 void
1898 MainWin::_ResizeVideoView(int x, int y, int width, int height)
1900 // Keep aspect ratio, place video view inside
1901 // the background area (may create black bars).
1902 int videoWidth;
1903 int videoHeight;
1904 _GetUnscaledVideoSize(videoWidth, videoHeight);
1905 float scaledWidth = videoWidth;
1906 float scaledHeight = videoHeight;
1907 float factor = min_c(width / scaledWidth, height / scaledHeight);
1908 int renderWidth = lround(scaledWidth * factor);
1909 int renderHeight = lround(scaledHeight * factor);
1910 if (renderWidth > width)
1911 renderWidth = width;
1912 if (renderHeight > height)
1913 renderHeight = height;
1915 int xOffset = (width - renderWidth) / 2;
1916 int yOffset = (height - renderHeight) / 2;
1918 fVideoView->MoveTo(x, y);
1919 fVideoView->ResizeTo(width - 1, height - 1);
1921 BRect videoFrame(xOffset, yOffset,
1922 xOffset + renderWidth - 1, yOffset + renderHeight - 1);
1924 fVideoView->SetVideoFrame(videoFrame);
1925 fVideoView->SetSubTitleMaxBottom(height - 1);
1929 // #pragma mark -
1932 void
1933 MainWin::_MouseDown(BMessage* msg, BView* originalHandler)
1935 uint32 buttons = msg->FindInt32("buttons");
1937 // On Zeta, only "screen_where" is reliable, "where" and "be:view_where"
1938 // seem to be broken
1939 BPoint screenWhere;
1940 if (msg->FindPoint("screen_where", &screenWhere) != B_OK) {
1941 // TODO: remove
1942 // Workaround for BeOS R5, it has no "screen_where"
1943 if (!originalHandler || msg->FindPoint("where", &screenWhere) < B_OK)
1944 return;
1945 originalHandler->ConvertToScreen(&screenWhere);
1948 // double click handling
1950 if (msg->FindInt32("clicks") % 2 == 0) {
1951 BRect rect(screenWhere.x - 1, screenWhere.y - 1, screenWhere.x + 1,
1952 screenWhere.y + 1);
1953 if (rect.Contains(fMouseDownMousePos)) {
1954 if (buttons == B_PRIMARY_MOUSE_BUTTON)
1955 PostMessage(M_TOGGLE_FULLSCREEN);
1956 else if (buttons == B_SECONDARY_MOUSE_BUTTON)
1957 PostMessage(M_TOGGLE_NO_INTERFACE);
1959 return;
1963 fMouseDownMousePos = screenWhere;
1964 fMouseDownWindowPos = Frame().LeftTop();
1966 if (buttons == B_PRIMARY_MOUSE_BUTTON && !fIsFullscreen) {
1967 // start mouse tracking
1968 fVideoView->SetMouseEventMask(B_POINTER_EVENTS | B_NO_POINTER_HISTORY
1969 /* | B_LOCK_WINDOW_FOCUS */);
1970 fMouseDownTracking = true;
1973 // pop up a context menu if right mouse button is down
1975 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
1976 _ShowContextMenu(screenWhere);
1980 void
1981 MainWin::_MouseMoved(BMessage* msg, BView* originalHandler)
1983 // msg->PrintToStream();
1985 BPoint mousePos;
1986 uint32 buttons = msg->FindInt32("buttons");
1987 // On Zeta, only "screen_where" is reliable, "where"
1988 // and "be:view_where" seem to be broken
1989 if (msg->FindPoint("screen_where", &mousePos) != B_OK) {
1990 // TODO: remove
1991 // Workaround for BeOS R5, it has no "screen_where"
1992 if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK)
1993 return;
1994 originalHandler->ConvertToScreen(&mousePos);
1997 if (buttons == B_PRIMARY_MOUSE_BUTTON && fMouseDownTracking
1998 && !fIsFullscreen) {
1999 // printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y);
2000 float delta_x = mousePos.x - fMouseDownMousePos.x;
2001 float delta_y = mousePos.y - fMouseDownMousePos.y;
2002 float x = fMouseDownWindowPos.x + delta_x;
2003 float y = fMouseDownWindowPos.y + delta_y;
2004 // printf("move window to %.0f, %.0f\n", x, y);
2005 MoveTo(x, y);
2008 bigtime_t eventTime;
2009 if (msg->FindInt64("when", &eventTime) != B_OK)
2010 eventTime = system_time();
2012 if (buttons == 0 && fIsFullscreen) {
2013 BPoint moveDelta = mousePos - fLastMousePos;
2014 float moveDeltaDist
2015 = sqrtf(moveDelta.x * moveDelta.x + moveDelta.y * moveDelta.y);
2016 if (eventTime - fLastMouseMovedTime < 200000)
2017 fMouseMoveDist += moveDeltaDist;
2018 else
2019 fMouseMoveDist = moveDeltaDist;
2020 if (fMouseMoveDist > 5)
2021 _ShowFullscreenControls(true);
2024 fLastMousePos = mousePos;
2025 fLastMouseMovedTime =eventTime;
2029 void
2030 MainWin::_MouseUp(BMessage* msg)
2032 fMouseDownTracking = false;
2036 void
2037 MainWin::_ShowContextMenu(const BPoint& screenPoint)
2039 printf("Show context menu\n");
2040 BPopUpMenu* menu = new BPopUpMenu("context menu", false, false);
2041 BMenuItem* item;
2042 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Full screen"),
2043 new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
2044 item->SetMarked(fIsFullscreen);
2045 item->SetEnabled(fHasVideo);
2047 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Hide interface"),
2048 new BMessage(M_TOGGLE_NO_INTERFACE), 'H'));
2049 item->SetMarked(fNoInterface);
2050 item->SetEnabled(fHasVideo && !fIsFullscreen);
2052 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"),
2053 new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
2054 item->SetMarked(fAlwaysOnTop);
2055 item->SetEnabled(fHasVideo);
2057 BMenu* aspectSubMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
2058 _SetupVideoAspectItems(aspectSubMenu);
2059 aspectSubMenu->SetTargetForItems(this);
2060 menu->AddItem(item = new BMenuItem(aspectSubMenu));
2061 item->SetEnabled(fHasVideo);
2063 menu->AddSeparatorItem();
2065 // Add track selector menus
2066 BMenu* audioTrackMenu = new BMenu(B_TRANSLATE("Audio track"));
2067 BMenu* videoTrackMenu = new BMenu(B_TRANSLATE("Video track"));
2068 BMenu* subTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
2069 _SetupTrackMenus(audioTrackMenu, videoTrackMenu, subTitleTrackMenu);
2071 audioTrackMenu->SetTargetForItems(this);
2072 videoTrackMenu->SetTargetForItems(this);
2073 subTitleTrackMenu->SetTargetForItems(this);
2075 menu->AddItem(item = new BMenuItem(audioTrackMenu));
2076 item->SetEnabled(fHasAudio);
2078 menu->AddItem(item = new BMenuItem(videoTrackMenu));
2079 item->SetEnabled(fHasVideo);
2081 menu->AddItem(item = new BMenuItem(subTitleTrackMenu));
2082 item->SetEnabled(fHasVideo);
2084 menu->AddSeparatorItem();
2085 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), new BMessage(M_FILE_QUIT), 'Q'));
2087 menu->SetTargetForItems(this);
2088 BRect rect(screenPoint.x - 5, screenPoint.y - 5, screenPoint.x + 5,
2089 screenPoint.y + 5);
2090 menu->Go(screenPoint, true, true, rect, true);
2094 /*! Trap keys that are about to be send to background or renderer view.
2095 Return true if it shouldn't be passed to the view.
2097 bool
2098 MainWin::_KeyDown(BMessage* msg)
2100 uint32 key = msg->FindInt32("key");
2101 uint32 rawChar = msg->FindInt32("raw_char");
2102 uint32 modifier = msg->FindInt32("modifiers");
2104 // printf("key 0x%lx, rawChar 0x%lx, modifiers 0x%lx\n", key, rawChar,
2105 // modifier);
2107 // ignore the system modifier namespace
2108 if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
2109 == (B_CONTROL_KEY | B_COMMAND_KEY))
2110 return false;
2112 switch (rawChar) {
2113 case B_SPACE:
2114 fController->TogglePlaying();
2115 return true;
2117 case 'm':
2118 fController->ToggleMute();
2119 return true;
2121 case B_ESCAPE:
2122 if (!fIsFullscreen)
2123 break;
2125 PostMessage(M_TOGGLE_FULLSCREEN);
2126 return true;
2128 case B_ENTER: // Enter / Return
2129 if ((modifier & B_COMMAND_KEY) != 0) {
2130 PostMessage(M_TOGGLE_FULLSCREEN);
2131 return true;
2133 break;
2135 case B_TAB:
2136 case 'f':
2137 if ((modifier & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
2138 | B_MENU_KEY)) == 0) {
2139 PostMessage(M_TOGGLE_FULLSCREEN);
2140 return true;
2142 break;
2144 case B_UP_ARROW:
2145 if ((modifier & B_COMMAND_KEY) != 0)
2146 PostMessage(M_SKIP_NEXT);
2147 else
2148 PostMessage(M_VOLUME_UP);
2149 return true;
2151 case B_DOWN_ARROW:
2152 if ((modifier & B_COMMAND_KEY) != 0)
2153 PostMessage(M_SKIP_PREV);
2154 else
2155 PostMessage(M_VOLUME_DOWN);
2156 return true;
2158 case B_RIGHT_ARROW:
2159 if ((modifier & B_COMMAND_KEY) != 0)
2160 PostMessage(M_SKIP_NEXT);
2161 else if (fAllowWinding) {
2162 BMessage windMessage(M_WIND);
2163 if ((modifier & B_SHIFT_KEY) != 0) {
2164 windMessage.AddInt64("how much", 30000000LL);
2165 windMessage.AddInt64("frames", 5);
2166 } else {
2167 windMessage.AddInt64("how much", 5000000LL);
2168 windMessage.AddInt64("frames", 1);
2170 PostMessage(&windMessage);
2172 return true;
2174 case B_LEFT_ARROW:
2175 if ((modifier & B_COMMAND_KEY) != 0)
2176 PostMessage(M_SKIP_PREV);
2177 else if (fAllowWinding) {
2178 BMessage windMessage(M_WIND);
2179 if ((modifier & B_SHIFT_KEY) != 0) {
2180 windMessage.AddInt64("how much", -30000000LL);
2181 windMessage.AddInt64("frames", -5);
2182 } else {
2183 windMessage.AddInt64("how much", -5000000LL);
2184 windMessage.AddInt64("frames", -1);
2186 PostMessage(&windMessage);
2188 return true;
2190 case B_PAGE_UP:
2191 PostMessage(M_SKIP_NEXT);
2192 return true;
2194 case B_PAGE_DOWN:
2195 PostMessage(M_SKIP_PREV);
2196 return true;
2198 case '+':
2199 if ((modifier & B_COMMAND_KEY) == 0) {
2200 _ZoomVideoView(10);
2201 return true;
2203 break;
2205 case '-':
2206 if ((modifier & B_COMMAND_KEY) == 0) {
2207 _ZoomVideoView(-10);
2208 return true;
2210 break;
2212 case B_DELETE:
2213 case 'd': // d for delete
2214 case 't': // t for Trash
2215 if ((modifiers() & B_COMMAND_KEY) != 0) {
2216 BAutolock _(fPlaylist);
2217 BMessage removeMessage(M_PLAYLIST_MOVE_TO_TRASH);
2218 removeMessage.AddInt32("playlist index",
2219 fPlaylist->CurrentItemIndex());
2220 fPlaylistWindow->PostMessage(&removeMessage);
2221 return true;
2223 break;
2226 switch (key) {
2227 case 0x3a: // numeric keypad +
2228 if ((modifier & B_COMMAND_KEY) == 0) {
2229 _ZoomVideoView(10);
2230 return true;
2232 break;
2234 case 0x25: // numeric keypad -
2235 if ((modifier & B_COMMAND_KEY) == 0) {
2236 _ZoomVideoView(-10);
2237 return true;
2239 break;
2241 case 0x38: // numeric keypad up arrow
2242 PostMessage(M_VOLUME_UP);
2243 return true;
2245 case 0x59: // numeric keypad down arrow
2246 PostMessage(M_VOLUME_DOWN);
2247 return true;
2249 case 0x39: // numeric keypad page up
2250 case 0x4a: // numeric keypad right arrow
2251 PostMessage(M_SKIP_NEXT);
2252 return true;
2254 case 0x5a: // numeric keypad page down
2255 case 0x48: // numeric keypad left arrow
2256 PostMessage(M_SKIP_PREV);
2257 return true;
2259 // Playback controls along the bottom of the keyboard:
2260 // Z X C (V) B for US International
2261 case 0x4c:
2262 PostMessage(M_SKIP_PREV);
2263 return true;
2264 case 0x4d:
2265 fController->TogglePlaying();
2266 return true;
2267 case 0x4e:
2268 fController->Pause();
2269 return true;
2270 case 0x4f:
2271 fController->Stop();
2272 return true;
2273 case 0x50:
2274 PostMessage(M_SKIP_NEXT);
2275 return true;
2278 return false;
2282 // #pragma mark -
2285 void
2286 MainWin::_ToggleFullscreen()
2288 printf("_ToggleFullscreen enter\n");
2290 if (!fHasVideo) {
2291 printf("_ToggleFullscreen - ignoring, as we don't have a video\n");
2292 return;
2295 fIsFullscreen = !fIsFullscreen;
2297 if (fIsFullscreen) {
2298 // switch to fullscreen
2300 fSavedFrame = Frame();
2301 printf("saving current frame: %d %d %d %d\n", int(fSavedFrame.left),
2302 int(fSavedFrame.top), int(fSavedFrame.right),
2303 int(fSavedFrame.bottom));
2304 BScreen screen(this);
2305 BRect rect(screen.Frame());
2307 Hide();
2308 MoveTo(rect.left, rect.top);
2309 ResizeTo(rect.Width(), rect.Height());
2310 Show();
2312 } else {
2313 // switch back from full screen mode
2314 _ShowFullscreenControls(false, false);
2316 Hide();
2317 MoveTo(fSavedFrame.left, fSavedFrame.top);
2318 ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
2319 Show();
2322 fVideoView->SetFullscreen(fIsFullscreen);
2324 _MarkItem(fFileMenu, M_TOGGLE_FULLSCREEN, fIsFullscreen);
2326 printf("_ToggleFullscreen leave\n");
2329 void
2330 MainWin::_ToggleAlwaysOnTop()
2332 fAlwaysOnTop = !fAlwaysOnTop;
2333 SetFeel(fAlwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL);
2335 _MarkItem(fFileMenu, M_TOGGLE_ALWAYS_ON_TOP, fAlwaysOnTop);
2339 void
2340 MainWin::_ToggleNoInterface()
2342 printf("_ToggleNoInterface enter\n");
2344 if (fIsFullscreen || !fHasVideo) {
2345 // Fullscreen playback is always without interface and
2346 // audio playback is always with interface. So we ignore these
2347 // two states here.
2348 printf("_ToggleNoControls leave, doing nothing, we are fullscreen\n");
2349 return;
2352 fNoInterface = !fNoInterface;
2353 _SetWindowSizeLimits();
2355 if (fNoInterface) {
2356 MoveBy(0, fMenuBarHeight);
2357 ResizeBy(0, -(fControlsHeight + fMenuBarHeight));
2358 SetLook(B_BORDERED_WINDOW_LOOK);
2359 } else {
2360 MoveBy(0, -fMenuBarHeight);
2361 ResizeBy(0, fControlsHeight + fMenuBarHeight);
2362 SetLook(B_TITLED_WINDOW_LOOK);
2365 _MarkItem(fFileMenu, M_TOGGLE_NO_INTERFACE, fNoInterface);
2367 printf("_ToggleNoInterface leave\n");
2371 void
2372 MainWin::_ShowIfNeeded()
2374 // Only proceed if the window is already running
2375 if (find_thread(NULL) != Thread())
2376 return;
2378 if (!fHasVideo && fNoVideoFrame.IsValid()) {
2379 MoveTo(fNoVideoFrame.LeftTop());
2380 ResizeTo(fNoVideoFrame.Width(), fNoVideoFrame.Height());
2381 } else if (fHasVideo && IsHidden())
2382 CenterOnScreen();
2384 fNoVideoFrame = BRect();
2386 if (IsHidden()) {
2387 Show();
2388 UpdateIfNeeded();
2393 void
2394 MainWin::_ShowFullscreenControls(bool show, bool animate)
2396 if (fShowsFullscreenControls == show)
2397 return;
2399 fShowsFullscreenControls = show;
2400 fVideoView->SetFullscreenControlsVisible(show);
2402 if (show) {
2403 fControls->RemoveSelf();
2404 fControls->MoveTo(fVideoView->Bounds().left,
2405 fVideoView->Bounds().bottom + 1);
2406 fVideoView->AddChild(fControls);
2407 if (fScaleFullscreenControls)
2408 fControls->SetSymbolScale(1.5f);
2410 while (fControls->IsHidden())
2411 fControls->Show();
2414 if (animate) {
2415 // Slide the controls into view. We need to do this with
2416 // messages, otherwise we block the video playback for the
2417 // time of the animation.
2418 const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
2419 const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
2420 float height = fControls->Bounds().Height();
2421 float moveDist = show ? -height : height;
2422 float originalY = fControls->Frame().top;
2423 for (int32 i = 0; i < steps; i++) {
2424 BMessage message(M_SLIDE_CONTROLS);
2425 message.AddFloat("offset",
2426 floorf(moveDist * kAnimationOffsets[i]));
2427 PostMessage(&message, this);
2429 BMessage finalMessage(M_FINISH_SLIDING_CONTROLS);
2430 finalMessage.AddFloat("offset", originalY + moveDist);
2431 finalMessage.AddBool("show", show);
2432 PostMessage(&finalMessage, this);
2433 } else if (!show) {
2434 fControls->RemoveSelf();
2435 fControls->MoveTo(fVideoView->Frame().left,
2436 fVideoView->Frame().bottom + 1);
2437 fBackground->AddChild(fControls);
2438 fControls->SetSymbolScale(1.0f);
2440 while (!fControls->IsHidden())
2441 fControls->Hide();
2446 // #pragma mark -
2449 void
2450 MainWin::_Wind(bigtime_t howMuch, int64 frames)
2452 if (!fAllowWinding || !fController->Lock())
2453 return;
2455 if (frames != 0 && fHasVideo && !fController->IsPlaying()) {
2456 int64 newFrame = fController->CurrentFrame() + frames;
2457 fController->SetFramePosition(newFrame);
2458 } else {
2459 bigtime_t seekTime = fController->TimePosition() + howMuch;
2460 if (seekTime < 0) {
2461 fInitialSeekPosition = seekTime;
2462 PostMessage(M_SKIP_PREV);
2463 } else if (seekTime > fController->TimeDuration()) {
2464 fInitialSeekPosition = 0;
2465 PostMessage(M_SKIP_NEXT);
2466 } else
2467 fController->SetTimePosition(seekTime);
2470 fController->Unlock();
2471 fAllowWinding = false;
2475 // #pragma mark -
2478 void
2479 MainWin::_UpdatePlaylistItemFile()
2481 BAutolock locker(fPlaylist);
2482 const FilePlaylistItem* item
2483 = dynamic_cast<const FilePlaylistItem*>(fController->Item());
2484 if (item == NULL)
2485 return;
2487 if (!fHasVideo && !fHasAudio)
2488 return;
2490 BNode node(&item->Ref());
2491 if (node.InitCheck())
2492 return;
2494 locker.Unlock();
2496 // Set some standard attributes of the currently played file.
2497 // This should only be a temporary solution.
2499 // Write duration
2500 const char* kDurationAttrName = "Media:Length";
2501 attr_info info;
2502 status_t status = node.GetAttrInfo(kDurationAttrName, &info);
2503 if (status != B_OK || info.size == 0) {
2504 bigtime_t duration = fController->TimeDuration();
2505 // TODO: Tracker does not seem to care about endian for scalar types
2506 node.WriteAttr(kDurationAttrName, B_INT64_TYPE, 0, &duration,
2507 sizeof(int64));
2510 // Write audio bitrate
2511 if (fHasAudio) {
2512 status = node.GetAttrInfo("Audio:Bitrate", &info);
2513 if (status != B_OK || info.size == 0) {
2514 media_format format;
2515 if (fController->GetEncodedAudioFormat(&format) == B_OK
2516 && format.type == B_MEDIA_ENCODED_AUDIO) {
2517 int32 bitrate = (int32)(format.u.encoded_audio.bit_rate
2518 / 1000);
2519 char text[256];
2520 snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2521 node.WriteAttr("Audio:Bitrate", B_STRING_TYPE, 0, text,
2522 strlen(text) + 1);
2527 // Write video bitrate
2528 if (fHasVideo) {
2529 status = node.GetAttrInfo("Video:Bitrate", &info);
2530 if (status != B_OK || info.size == 0) {
2531 media_format format;
2532 if (fController->GetEncodedVideoFormat(&format) == B_OK
2533 && format.type == B_MEDIA_ENCODED_VIDEO) {
2534 int32 bitrate = (int32)(format.u.encoded_video.avg_bit_rate
2535 / 1000);
2536 char text[256];
2537 snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2538 node.WriteAttr("Video:Bitrate", B_STRING_TYPE, 0, text,
2539 strlen(text) + 1);
2544 _UpdateAttributesMenu(node);
2548 void
2549 MainWin::_UpdateAttributesMenu(const BNode& node)
2551 int32 rating = -1;
2553 attr_info info;
2554 status_t status = node.GetAttrInfo(kRatingAttrName, &info);
2555 if (status == B_OK && info.type == B_INT32_TYPE) {
2556 // Node has the Rating attribute.
2557 node.ReadAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating,
2558 sizeof(rating));
2561 for (int32 i = 0; BMenuItem* item = fRatingMenu->ItemAt(i); i++)
2562 item->SetMarked(i + 1 == rating);
2566 void
2567 MainWin::_SetRating(int32 rating)
2569 BAutolock locker(fPlaylist);
2570 const FilePlaylistItem* item
2571 = dynamic_cast<const FilePlaylistItem*>(fController->Item());
2572 if (item == NULL)
2573 return;
2575 BNode node(&item->Ref());
2576 if (node.InitCheck())
2577 return;
2579 locker.Unlock();
2581 node.WriteAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating, sizeof(rating));
2583 // TODO: The whole mechnism should work like this:
2584 // * There is already an attribute API for PlaylistItem, flesh it out!
2585 // * FilePlaylistItem node-monitors it's file somehow.
2586 // * FilePlaylistItem keeps attributes in sync and sends notications.
2587 // * MainWin updates the menu according to FilePlaylistItem notifications.
2588 // * PlaylistWin shows columns with attribute and other info.
2589 // * PlaylistWin updates also upon FilePlaylistItem notifications.
2590 // * This keeps attributes in sync when another app changes them.
2592 _UpdateAttributesMenu(node);
2596 void
2597 MainWin::_UpdateControlsEnabledStatus()
2599 uint32 enabledButtons = 0;
2600 if (fHasVideo || fHasAudio) {
2601 enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED
2602 | SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED;
2604 if (fHasAudio)
2605 enabledButtons |= VOLUME_ENABLED;
2607 BAutolock _(fPlaylist);
2608 bool canSkipPrevious, canSkipNext;
2609 fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext);
2610 if (canSkipPrevious)
2611 enabledButtons |= SKIP_BACK_ENABLED;
2612 if (canSkipNext)
2613 enabledButtons |= SKIP_FORWARD_ENABLED;
2615 fControls->SetEnabled(enabledButtons);
2617 fNoInterfaceMenuItem->SetEnabled(fHasVideo);
2618 fAttributesMenu->SetEnabled(fHasAudio || fHasVideo);
2622 void
2623 MainWin::_UpdatePlaylistMenu()
2625 if (!fPlaylist->Lock())
2626 return;
2628 fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true);
2630 int32 count = fPlaylist->CountItems();
2631 for (int32 i = 0; i < count; i++) {
2632 PlaylistItem* item = fPlaylist->ItemAtFast(i);
2633 _AddPlaylistItem(item, i);
2635 fPlaylistMenu->SetTargetForItems(this);
2637 _MarkPlaylistItem(fPlaylist->CurrentItemIndex());
2639 fPlaylist->Unlock();
2643 void
2644 MainWin::_AddPlaylistItem(PlaylistItem* item, int32 index)
2646 BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION);
2647 message->AddInt32("index", index);
2648 BMenuItem* menuItem = new BMenuItem(item->Name().String(), message);
2649 fPlaylistMenu->AddItem(menuItem, index);
2653 void
2654 MainWin::_RemovePlaylistItem(int32 index)
2656 delete fPlaylistMenu->RemoveItem(index);
2660 void
2661 MainWin::_MarkPlaylistItem(int32 index)
2663 if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) {
2664 item->SetMarked(true);
2665 // ... and in case the menu is currently on screen:
2666 if (fPlaylistMenu->LockLooper()) {
2667 fPlaylistMenu->Invalidate();
2668 fPlaylistMenu->UnlockLooper();
2674 void
2675 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark)
2677 if (BMenuItem* item = menu->FindItem(command))
2678 item->SetMarked(mark);
2682 void
2683 MainWin::_AdoptGlobalSettings()
2685 mpSettings settings;
2686 Settings::Default()->Get(settings);
2688 fCloseWhenDonePlayingMovie = settings.closeWhenDonePlayingMovie;
2689 fCloseWhenDonePlayingSound = settings.closeWhenDonePlayingSound;
2690 fLoopMovies = settings.loopMovie;
2691 fLoopSounds = settings.loopSound;
2692 fScaleFullscreenControls = settings.scaleFullscreenControls;