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