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