xref: /haiku/src/apps/mediaplayer/MainWin.cpp (revision 5d0fd0e4220b461e2021d5768ebaa936c13417f8)
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 
60 int MainWin::sNoVideoWidth = MIN_WIDTH;
61 
62 
63 // XXX TODO: why is lround not defined?
64 #define lround(a) ((int)(0.99999 + (a)))
65 
66 enum {
67 	M_DUMMY = 0x100,
68 	M_FILE_OPEN = 0x1000,
69 	M_NETWORK_STREAM_OPEN,
70 	M_FILE_INFO,
71 	M_FILE_PLAYLIST,
72 	M_FILE_CLOSE,
73 	M_FILE_QUIT,
74 	M_VIEW_SIZE,
75 	M_TOGGLE_FULLSCREEN,
76 	M_TOGGLE_ALWAYS_ON_TOP,
77 	M_TOGGLE_NO_INTERFACE,
78 	M_VOLUME_UP,
79 	M_VOLUME_DOWN,
80 	M_SKIP_NEXT,
81 	M_SKIP_PREV,
82 	M_WIND,
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 
112 static 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 
186 static const char* kRatingAttrName = "Media:Rating";
187 
188 static const char* kDisabledSeekMessage = B_TRANSLATE("Drop files to play");
189 
190 static const char* kApplicationName = B_TRANSLATE_SYSTEM_NAME(NAME);
191 
192 
193 MainWin::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 
328 MainWin::~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 
369 void
370 MainWin::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 
430 void
431 MainWin::Zoom(BPoint /*position*/, float /*width*/, float /*height*/)
432 {
433 	PostMessage(M_TOGGLE_FULLSCREEN);
434 }
435 
436 
437 void
438 MainWin::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 
478 void
479 MainWin::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 
1105 void
1106 MainWin::WindowActivated(bool active)
1107 {
1108 	fController->PlayerActivated(active);
1109 }
1110 
1111 
1112 bool
1113 MainWin::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 
1123 void
1124 MainWin::MenusBeginning()
1125 {
1126 	_SetupVideoAspectItems(fVideoAspectMenu);
1127 }
1128 
1129 
1130 // #pragma mark -
1131 
1132 
1133 void
1134 MainWin::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 
1162 void
1163 MainWin::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 
1185 void
1186 MainWin::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 
1201 void
1202 MainWin::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 
1221 void
1222 MainWin::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 
1235 void
1236 MainWin::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 
1250 void
1251 MainWin::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 
1264 void
1265 MainWin::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 
1290 void
1291 MainWin::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 
1320 BHandler*
1321 MainWin::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 
1333 status_t
1334 MainWin::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 
1355 void
1356 MainWin::_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 
1379 void
1380 MainWin::_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 
1448 void
1449 MainWin::_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 
1496 void
1497 MainWin::_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 
1615 void
1616 MainWin::_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 
1669 void
1670 MainWin::_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 
1754 void
1755 MainWin::_UpdateAudioChannelCount(int32 audioTrackIndex)
1756 {
1757 	fControls->SetAudioChannelCount(fController->AudioTrackChannelCount());
1758 }
1759 
1760 
1761 void
1762 MainWin::_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 
1774 void
1775 MainWin::_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 
1795 void
1796 MainWin::_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 
1806 int
1807 MainWin::_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 
1828 void
1829 MainWin::_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 
1849 void
1850 MainWin::_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 
1930 void
1931 MainWin::_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 
1965 void
1966 MainWin::_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 
2013 void
2014 MainWin::_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 
2062 void
2063 MainWin::_MouseUp(BMessage* msg)
2064 {
2065 	fMouseDownTracking = false;
2066 }
2067 
2068 
2069 void
2070 MainWin::_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 */
2130 bool
2131 MainWin::_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 
2318 void
2319 MainWin::_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 
2362 void
2363 MainWin::_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 
2372 void
2373 MainWin::_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 
2404 void
2405 MainWin::_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 
2427 void
2428 MainWin::_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 
2483 void
2484 MainWin::_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 
2512 void
2513 MainWin::_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 
2582 void
2583 MainWin::_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 
2600 void
2601 MainWin::_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 
2630 void
2631 MainWin::_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 
2656 void
2657 MainWin::_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 
2677 void
2678 MainWin::_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 
2687 void
2688 MainWin::_RemovePlaylistItem(int32 index)
2689 {
2690 	delete fPlaylistMenu->RemoveItem(index);
2691 }
2692 
2693 
2694 void
2695 MainWin::_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 
2708 void
2709 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark)
2710 {
2711 	if (BMenuItem* item = menu->FindItem(command))
2712 		item->SetMarked(mark);
2713 }
2714 
2715 
2716 void
2717 MainWin::_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