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