xref: /haiku/src/apps/mediaplayer/MainWin.cpp (revision 3995592cdf304335132305e27c40cbb0b1ac46e3)
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 			bool hadNext = fPlaylist->SetCurrentItemIndex(
733 				fPlaylist->CurrentItemIndex() + 1);
734 			if (!hadNext) {
735 				// Reached end of playlist
736 				// Handle "quit when done" settings
737 				if ((fHasVideo && fCloseWhenDonePlayingMovie)
738 					|| (!fHasVideo && fCloseWhenDonePlayingSound))
739 					PostMessage(B_QUIT_REQUESTED);
740 				// Handle "loop by default" settings
741 				if ((fHasVideo && fLoopMovies)
742 					|| (!fHasVideo && fLoopSounds)) {
743 					if (fPlaylist->CountItems() > 1)
744 						fPlaylist->SetCurrentItemIndex(0);
745 					else
746 						fController->Play();
747 				}
748 			}
749 			break;
750 		}
751 		case MSG_CONTROLLER_FILE_CHANGED:
752 		{
753 			status_t result = B_ERROR;
754 			msg->FindInt32("result", &result);
755 			PlaylistItemRef itemRef;
756 			PlaylistItem* item;
757 			if (msg->FindPointer("item", (void**)&item) == B_OK) {
758 				itemRef.SetTo(item, true);
759 					// The reference was passed along with the message.
760 			} else {
761 				BAutolock _(fPlaylist);
762 				itemRef.SetTo(fPlaylist->ItemAt(
763 					fPlaylist->CurrentItemIndex()));
764 			}
765 			_PlaylistItemOpened(itemRef, result);
766 			break;
767 		}
768 		case MSG_CONTROLLER_VIDEO_TRACK_CHANGED:
769 		{
770 			int32 index;
771 			if (msg->FindInt32("index", &index) == B_OK) {
772 				int32 i = 0;
773 				while (BMenuItem* item = fVideoTrackMenu->ItemAt(i)) {
774 					item->SetMarked(i == index);
775 					i++;
776 				}
777 			}
778 			break;
779 		}
780 		case MSG_CONTROLLER_AUDIO_TRACK_CHANGED:
781 		{
782 			int32 index;
783 			if (msg->FindInt32("index", &index) == B_OK) {
784 				int32 i = 0;
785 				while (BMenuItem* item = fAudioTrackMenu->ItemAt(i)) {
786 					item->SetMarked(i == index);
787 					i++;
788 				}
789 				_UpdateAudioChannelCount(index);
790 			}
791 			break;
792 		}
793 		case MSG_CONTROLLER_SUB_TITLE_TRACK_CHANGED:
794 		{
795 			int32 index;
796 			if (msg->FindInt32("index", &index) == B_OK) {
797 				int32 i = 0;
798 				while (BMenuItem* item = fSubTitleTrackMenu->ItemAt(i)) {
799 					BMessage* message = item->Message();
800 					if (message != NULL) {
801 						item->SetMarked((int32)message->what
802 							- M_SELECT_SUB_TITLE_TRACK == index);
803 					}
804 					i++;
805 				}
806 			}
807 			break;
808 		}
809 		case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED:
810 		{
811 			uint32 state;
812 			if (msg->FindInt32("state", (int32*)&state) == B_OK)
813 				fControls->SetPlaybackState(state);
814 			break;
815 		}
816 		case MSG_CONTROLLER_POSITION_CHANGED:
817 		{
818 			float position;
819 			if (msg->FindFloat("position", &position) == B_OK) {
820 				fControls->SetPosition(position, fController->TimePosition(),
821 					fController->TimeDuration());
822 				fAllowWinding = true;
823 			}
824 			break;
825 		}
826 		case MSG_CONTROLLER_SEEK_HANDLED:
827 			break;
828 
829 		case MSG_CONTROLLER_VOLUME_CHANGED:
830 		{
831 			float volume;
832 			if (msg->FindFloat("volume", &volume) == B_OK)
833 				fControls->SetVolume(volume);
834 			break;
835 		}
836 		case MSG_CONTROLLER_MUTED_CHANGED:
837 		{
838 			bool muted;
839 			if (msg->FindBool("muted", &muted) == B_OK)
840 				fControls->SetMuted(muted);
841 			break;
842 		}
843 
844 		// menu item messages
845 		case M_FILE_OPEN:
846 		{
847 			BMessenger target(this);
848 			BMessage result(B_REFS_RECEIVED);
849 			BMessage appMessage(M_SHOW_OPEN_PANEL);
850 			appMessage.AddMessenger("target", target);
851 			appMessage.AddMessage("message", &result);
852 			appMessage.AddString("title", B_TRANSLATE("Open clips"));
853 			appMessage.AddString("label", B_TRANSLATE("Open"));
854 			be_app->PostMessage(&appMessage);
855 			break;
856 		}
857 
858 		case M_NETWORK_STREAM_OPEN:
859 		{
860 			BMessenger target(this);
861 			NetworkStreamWin* win = new NetworkStreamWin(target);
862 			win->Show();
863 			break;
864 		}
865 
866 		case M_FILE_INFO:
867 			ShowFileInfo();
868 			break;
869 		case M_FILE_PLAYLIST:
870 			ShowPlaylistWindow();
871 			break;
872 		case M_FILE_CLOSE:
873 			PostMessage(B_QUIT_REQUESTED);
874 			break;
875 		case M_FILE_QUIT:
876 			be_app->PostMessage(B_QUIT_REQUESTED);
877 			break;
878 
879 		case M_TOGGLE_FULLSCREEN:
880 			_ToggleFullscreen();
881 			break;
882 
883 		case M_TOGGLE_ALWAYS_ON_TOP:
884 			_ToggleAlwaysOnTop();
885 			break;
886 
887 		case M_TOGGLE_NO_INTERFACE:
888 			_ToggleNoInterface();
889 			break;
890 
891 		case M_VIEW_SIZE:
892 		{
893 			int32 size;
894 			if (msg->FindInt32("size", &size) == B_OK) {
895 				if (!fHasVideo)
896 					break;
897 				if (fIsFullscreen)
898 					_ToggleFullscreen();
899 				_ResizeWindow(size);
900 			}
901 			break;
902 		}
903 
904 /*
905 		case B_ACQUIRE_OVERLAY_LOCK:
906 			printf("B_ACQUIRE_OVERLAY_LOCK\n");
907 			fVideoView->OverlayLockAcquire();
908 			break;
909 
910 		case B_RELEASE_OVERLAY_LOCK:
911 			printf("B_RELEASE_OVERLAY_LOCK\n");
912 			fVideoView->OverlayLockRelease();
913 			break;
914 */
915 		case B_MOUSE_WHEEL_CHANGED:
916 		{
917 			float dx = msg->FindFloat("be:wheel_delta_x");
918 			float dy = msg->FindFloat("be:wheel_delta_y");
919 			bool inv = modifiers() & B_COMMAND_KEY;
920 			if (dx > 0.1)
921 				PostMessage(inv ? M_VOLUME_DOWN : M_SKIP_PREV);
922 			if (dx < -0.1)
923 				PostMessage(inv ? M_VOLUME_UP : M_SKIP_NEXT);
924 			if (dy > 0.1)
925 				PostMessage(inv ? M_SKIP_PREV : M_VOLUME_DOWN);
926 			if (dy < -0.1)
927 				PostMessage(inv ? M_SKIP_NEXT : M_VOLUME_UP);
928 			break;
929 		}
930 
931 		case M_SKIP_NEXT:
932 			fControls->SkipForward();
933 			break;
934 
935 		case M_SKIP_PREV:
936 			fControls->SkipBackward();
937 			break;
938 
939 		case M_WIND:
940 		{
941 			bigtime_t howMuch;
942 			int64 frames;
943 			if (msg->FindInt64("how much", &howMuch) != B_OK
944 				|| msg->FindInt64("frames", &frames) != B_OK) {
945 				break;
946 			}
947 
948 			_Wind(howMuch, frames);
949 			break;
950 		}
951 
952 		case M_VOLUME_UP:
953 			fController->VolumeUp();
954 			break;
955 
956 		case M_VOLUME_DOWN:
957 			fController->VolumeDown();
958 			break;
959 
960 		case M_ASPECT_SAME_AS_SOURCE:
961 			if (fHasVideo) {
962 				int width;
963 				int height;
964 				int widthAspect;
965 				int heightAspect;
966 				fController->GetSize(&width, &height,
967 					&widthAspect, &heightAspect);
968 				VideoFormatChange(width, height, widthAspect, heightAspect);
969 			}
970 			break;
971 
972 		case M_ASPECT_NO_DISTORTION:
973 			if (fHasVideo) {
974 				int width;
975 				int height;
976 				fController->GetSize(&width, &height);
977 				VideoFormatChange(width, height, width, height);
978 			}
979 			break;
980 
981 		case M_ASPECT_4_3:
982 			VideoAspectChange(4, 3);
983 			break;
984 
985 		case M_ASPECT_16_9: // 1.77 : 1
986 			VideoAspectChange(16, 9);
987 			break;
988 
989 		case M_ASPECT_83_50: // 1.66 : 1
990 			VideoAspectChange(83, 50);
991 			break;
992 
993 		case M_ASPECT_7_4: // 1.75 : 1
994 			VideoAspectChange(7, 4);
995 			break;
996 
997 		case M_ASPECT_37_20: // 1.85 : 1
998 			VideoAspectChange(37, 20);
999 			break;
1000 
1001 		case M_ASPECT_47_20: // 2.35 : 1
1002 			VideoAspectChange(47, 20);
1003 			break;
1004 
1005 		case M_SET_PLAYLIST_POSITION:
1006 		{
1007 			BAutolock _(fPlaylist);
1008 
1009 			int32 index;
1010 			if (msg->FindInt32("index", &index) == B_OK)
1011 				fPlaylist->SetCurrentItemIndex(index);
1012 			break;
1013 		}
1014 
1015 		case MSG_OBJECT_CHANGED:
1016 			// received from fGlobalSettingsListener
1017 			// TODO: find out which object, if we ever watch more than
1018 			// the global settings instance...
1019 			_AdoptGlobalSettings();
1020 			break;
1021 
1022 		case M_SLIDE_CONTROLS:
1023 		{
1024 			float offset;
1025 			if (msg->FindFloat("offset", &offset) == B_OK) {
1026 				fControls->MoveBy(0, offset);
1027 				fVideoView->SetSubTitleMaxBottom(fControls->Frame().top - 1);
1028 				UpdateIfNeeded();
1029 				snooze(15000);
1030 			}
1031 			break;
1032 		}
1033 		case M_FINISH_SLIDING_CONTROLS:
1034 		{
1035 			float offset;
1036 			bool show;
1037 			if (msg->FindFloat("offset", &offset) == B_OK
1038 				&& msg->FindBool("show", &show) == B_OK) {
1039 				if (show) {
1040 					fControls->MoveTo(fControls->Frame().left, offset);
1041 					fVideoView->SetSubTitleMaxBottom(offset - 1);
1042 				} else {
1043 					fVideoView->SetSubTitleMaxBottom(
1044 						fVideoView->Bounds().bottom);
1045 					fControls->RemoveSelf();
1046 					fControls->MoveTo(fVideoView->Frame().left,
1047 						fVideoView->Frame().bottom + 1);
1048 					fBackground->AddChild(fControls);
1049 					fControls->SetSymbolScale(1.0f);
1050 					while (!fControls->IsHidden())
1051 						fControls->Hide();
1052 				}
1053 			}
1054 			break;
1055 		}
1056 		case M_HIDE_FULL_SCREEN_CONTROLS:
1057 			if (fIsFullscreen) {
1058 				BPoint videoViewWhere;
1059 				if (msg->FindPoint("where", &videoViewWhere) == B_OK) {
1060 					if (msg->FindBool("force")
1061 						|| !fControls->Frame().Contains(videoViewWhere)) {
1062 						_ShowFullscreenControls(false);
1063 						// hide the mouse cursor until the user moves it
1064 						be_app->ObscureCursor();
1065 					}
1066 				}
1067 			}
1068 			break;
1069 
1070 		case M_SET_RATING:
1071 		{
1072 			int32 rating;
1073 			if (msg->FindInt32("rating", &rating) == B_OK)
1074 				_SetRating(rating);
1075 			break;
1076 		}
1077 
1078 		default:
1079 			if (msg->what >= M_SELECT_AUDIO_TRACK
1080 				&& msg->what <= M_SELECT_AUDIO_TRACK_END) {
1081 				fController->SelectAudioTrack(msg->what - M_SELECT_AUDIO_TRACK);
1082 				break;
1083 			}
1084 			if (msg->what >= M_SELECT_VIDEO_TRACK
1085 				&& msg->what <= M_SELECT_VIDEO_TRACK_END) {
1086 				fController->SelectVideoTrack(msg->what - M_SELECT_VIDEO_TRACK);
1087 				break;
1088 			}
1089 			if ((int32)msg->what >= M_SELECT_SUB_TITLE_TRACK - 1
1090 				&& msg->what <= M_SELECT_SUB_TITLE_TRACK_END) {
1091 				fController->SelectSubTitleTrack((int32)msg->what
1092 					- M_SELECT_SUB_TITLE_TRACK);
1093 				break;
1094 			}
1095 			// let BWindow handle the rest
1096 			BWindow::MessageReceived(msg);
1097 	}
1098 }
1099 
1100 
1101 void
1102 MainWin::WindowActivated(bool active)
1103 {
1104 	fController->PlayerActivated(active);
1105 }
1106 
1107 
1108 bool
1109 MainWin::QuitRequested()
1110 {
1111 	BMessage message(M_PLAYER_QUIT);
1112 	GetQuitMessage(&message);
1113 	be_app->PostMessage(&message);
1114 	return true;
1115 }
1116 
1117 
1118 void
1119 MainWin::MenusBeginning()
1120 {
1121 	_SetupVideoAspectItems(fVideoAspectMenu);
1122 }
1123 
1124 
1125 // #pragma mark -
1126 
1127 
1128 void
1129 MainWin::OpenPlaylist(const BMessage* playlistArchive)
1130 {
1131 	if (playlistArchive == NULL)
1132 		return;
1133 
1134 	BAutolock _(this);
1135 	BAutolock playlistLocker(fPlaylist);
1136 
1137 	if (fPlaylist->Unarchive(playlistArchive) != B_OK)
1138 		return;
1139 
1140 	int32 currentIndex;
1141 	if (playlistArchive->FindInt32("index", &currentIndex) != B_OK)
1142 		currentIndex = 0;
1143 	fPlaylist->SetCurrentItemIndex(currentIndex);
1144 
1145 	playlistLocker.Unlock();
1146 
1147 	if (currentIndex != -1) {
1148 		// Restore the current play position only if we have something to play
1149 		playlistArchive->FindInt64("position", (int64*)&fInitialSeekPosition);
1150 	}
1151 
1152 	if (IsHidden())
1153 		Show();
1154 }
1155 
1156 
1157 void
1158 MainWin::OpenPlaylistItem(const PlaylistItemRef& item)
1159 {
1160 	status_t ret = fController->SetToAsync(item);
1161 	if (ret != B_OK) {
1162 		fprintf(stderr, "MainWin::OpenPlaylistItem() - Failed to send message "
1163 			"to Controller.\n");
1164 		BString message = B_TRANSLATE("%app% encountered an internal error. "
1165 			"The file could not be opened.");
1166 		message.ReplaceFirst("%app%", kApplicationName);
1167 		BAlert* alert = new BAlert(kApplicationName, message.String(),
1168 			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1169 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1170 		alert->Go();
1171 		_PlaylistItemOpened(item, ret);
1172 	} else {
1173 		BString string;
1174 		string << "Opening '" << item->Name() << "'.";
1175 		fControls->SetDisabledString(string.String());
1176 	}
1177 }
1178 
1179 
1180 void
1181 MainWin::ShowFileInfo()
1182 {
1183 	if (!fInfoWin)
1184 		fInfoWin = new InfoWin(Frame().LeftTop(), fController);
1185 
1186 	if (fInfoWin->Lock()) {
1187 		if (fInfoWin->IsHidden())
1188 			fInfoWin->Show();
1189 		else
1190 			fInfoWin->Activate();
1191 		fInfoWin->Unlock();
1192 	}
1193 }
1194 
1195 
1196 void
1197 MainWin::ShowPlaylistWindow()
1198 {
1199 	if (fPlaylistWindow->Lock()) {
1200 		// make sure the window shows on the same workspace as ourself
1201 		uint32 workspaces = Workspaces();
1202 		if (fPlaylistWindow->Workspaces() != workspaces)
1203 			fPlaylistWindow->SetWorkspaces(workspaces);
1204 
1205 		// show or activate
1206 		if (fPlaylistWindow->IsHidden())
1207 			fPlaylistWindow->Show();
1208 		else
1209 			fPlaylistWindow->Activate();
1210 
1211 		fPlaylistWindow->Unlock();
1212 	}
1213 }
1214 
1215 
1216 void
1217 MainWin::VideoAspectChange(int forcedWidth, int forcedHeight, float widthScale)
1218 {
1219 	// Force specific source size and pixel width scale.
1220 	if (fHasVideo) {
1221 		int width;
1222 		int height;
1223 		fController->GetSize(&width, &height);
1224 		VideoFormatChange(forcedWidth, forcedHeight,
1225 			lround(width * widthScale), height);
1226 	}
1227 }
1228 
1229 
1230 void
1231 MainWin::VideoAspectChange(float widthScale)
1232 {
1233 	// Called when video aspect ratio changes and the original
1234 	// width/height should be restored too, display aspect is not known,
1235 	// only pixel width scale.
1236 	if (fHasVideo) {
1237 		int width;
1238 		int height;
1239 		fController->GetSize(&width, &height);
1240 		VideoFormatChange(width, height, lround(width * widthScale), height);
1241 	}
1242 }
1243 
1244 
1245 void
1246 MainWin::VideoAspectChange(int widthAspect, int heightAspect)
1247 {
1248 	// Called when video aspect ratio changes and the original
1249 	// width/height should be restored too.
1250 	if (fHasVideo) {
1251 		int width;
1252 		int height;
1253 		fController->GetSize(&width, &height);
1254 		VideoFormatChange(width, height, widthAspect, heightAspect);
1255 	}
1256 }
1257 
1258 
1259 void
1260 MainWin::VideoFormatChange(int width, int height, int widthAspect,
1261 	int heightAspect)
1262 {
1263 	// Called when video format or aspect ratio changes.
1264 
1265 	printf("VideoFormatChange enter: width %d, height %d, "
1266 		"aspect ratio: %d:%d\n", width, height, widthAspect, heightAspect);
1267 
1268 	// remember current view scale
1269 	int percent = _CurrentVideoSizeInPercent();
1270 
1271  	fSourceWidth = width;
1272  	fSourceHeight = height;
1273  	fWidthAspect = widthAspect;
1274  	fHeightAspect = heightAspect;
1275 
1276 	if (percent == 100)
1277 		_ResizeWindow(100);
1278 	else
1279 	 	FrameResized(Bounds().Width(), Bounds().Height());
1280 
1281 	printf("VideoFormatChange leave\n");
1282 }
1283 
1284 
1285 void
1286 MainWin::GetQuitMessage(BMessage* message)
1287 {
1288 	message->AddPointer("instance", this);
1289 	message->AddRect("window frame", Frame());
1290 	message->AddBool("audio only", !fHasVideo);
1291 	message->AddInt64("creation time", fCreationTime);
1292 
1293 	if (!fHasVideo && fHasAudio) {
1294 		// store playlist, current index and position if this is audio
1295 		BMessage playlistArchive;
1296 
1297 		BAutolock controllerLocker(fController);
1298 		playlistArchive.AddInt64("position", fController->TimePosition());
1299 		controllerLocker.Unlock();
1300 
1301 		if (!fPlaylist)
1302 			return;
1303 
1304 		BAutolock playlistLocker(fPlaylist);
1305 		if (fPlaylist->Archive(&playlistArchive) != B_OK
1306 			|| playlistArchive.AddInt32("index",
1307 				fPlaylist->CurrentItemIndex()) != B_OK
1308 			|| message->AddMessage("playlist", &playlistArchive) != B_OK) {
1309 			fprintf(stderr, "Failed to store current playlist.\n");
1310 		}
1311 	}
1312 }
1313 
1314 
1315 BHandler*
1316 MainWin::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
1317 	int32 what, const char* property)
1318 {
1319 	BPropertyInfo propertyInfo(sPropertyInfo);
1320 	if (propertyInfo.FindMatch(message, index, specifier, what, property)
1321 			!= B_ERROR)
1322 		return this;
1323 
1324 	return BWindow::ResolveSpecifier(message, index, specifier, what, property);
1325 }
1326 
1327 
1328 status_t
1329 MainWin::GetSupportedSuites(BMessage* data)
1330 {
1331 	if (data == NULL)
1332 		return B_BAD_VALUE;
1333 
1334 	status_t status = data->AddString("suites", "suite/vnd.Haiku-MediaPlayer");
1335 	if (status != B_OK)
1336 		return status;
1337 
1338 	BPropertyInfo propertyInfo(sPropertyInfo);
1339 	status = data->AddFlat("messages", &propertyInfo);
1340 	if (status != B_OK)
1341 		return status;
1342 
1343 	return BWindow::GetSupportedSuites(data);
1344 }
1345 
1346 
1347 // #pragma mark -
1348 
1349 
1350 void
1351 MainWin::_RefsReceived(BMessage* message)
1352 {
1353 	// the playlist is replaced by dropped files
1354 	// or the dropped files are appended to the end
1355 	// of the existing playlist if <shift> is pressed
1356 	bool append = false;
1357 	if (message->FindBool("append to playlist", &append) != B_OK)
1358 		append = modifiers() & B_SHIFT_KEY;
1359 
1360 	BAutolock _(fPlaylist);
1361 	int32 appendIndex = append ? APPEND_INDEX_APPEND_LAST
1362 		: APPEND_INDEX_REPLACE_PLAYLIST;
1363 	message->AddInt32("append_index", appendIndex);
1364 
1365 	// forward the message to the playlist window,
1366 	// so that undo/redo is used for modifying the playlist
1367 	fPlaylistWindow->PostMessage(message);
1368 
1369 	if (message->FindRect("window frame", &fNoVideoFrame) != B_OK)
1370 		fNoVideoFrame = BRect();
1371 }
1372 
1373 
1374 void
1375 MainWin::_PlaylistItemOpened(const PlaylistItemRef& item, status_t result)
1376 {
1377 	if (result != B_OK) {
1378 		BAutolock _(fPlaylist);
1379 
1380 		item->SetPlaybackFailed();
1381 		bool allItemsFailed = true;
1382 		int32 count = fPlaylist->CountItems();
1383 		for (int32 i = 0; i < count; i++) {
1384 			if (!fPlaylist->ItemAtFast(i)->PlaybackFailed()) {
1385 				allItemsFailed = false;
1386 				break;
1387 			}
1388 		}
1389 
1390 		if (allItemsFailed) {
1391 			// Display error if all files failed to play.
1392 			BString message(B_TRANSLATE(
1393 				"The file '%filename' could not be opened.\n\n"));;
1394 			message.ReplaceAll("%filename", item->Name());
1395 
1396 			if (result == B_MEDIA_NO_HANDLER) {
1397 				// give a more detailed message for the most likely of all
1398 				// errors
1399 				message << B_TRANSLATE(
1400 					"There is no decoder installed to handle the "
1401 					"file format, or the decoder has trouble with the "
1402 					"specific version of the format.");
1403 			} else {
1404 				message << B_TRANSLATE("Error: ") << strerror(result);
1405 			}
1406 			BAlert* alert = new BAlert("error", message.String(),
1407 				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1408 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1409 			alert->Go();
1410 			fControls->SetDisabledString(kDisabledSeekMessage);
1411 		} else {
1412 			// Just go to the next file and don't bother user (yet)
1413 			fPlaylist->SetCurrentItemIndex(fPlaylist->CurrentItemIndex() + 1);
1414 		}
1415 
1416 		fHasFile = false;
1417 		fHasVideo = false;
1418 		fHasAudio = false;
1419 		SetTitle(kApplicationName);
1420 	} else {
1421 		fHasFile = true;
1422 		fHasVideo = fController->VideoTrackCount() != 0;
1423 		fHasAudio = fController->AudioTrackCount() != 0;
1424 		SetTitle(item->Name().String());
1425 
1426 		if (fInitialSeekPosition < 0) {
1427 			fInitialSeekPosition
1428 				= fController->TimeDuration() + fInitialSeekPosition;
1429 		}
1430 		fController->SetTimePosition(fInitialSeekPosition);
1431 		fInitialSeekPosition = 0;
1432 	}
1433 	_SetupWindow();
1434 
1435 	if (result == B_OK)
1436 		_UpdatePlaylistItemFile();
1437 }
1438 
1439 
1440 void
1441 MainWin::_SetupWindow()
1442 {
1443 //	printf("MainWin::_SetupWindow\n");
1444 	// Populate the track menus
1445 	_SetupTrackMenus(fAudioTrackMenu, fVideoTrackMenu, fSubTitleTrackMenu);
1446 	_UpdateAudioChannelCount(fController->CurrentAudioTrack());
1447 
1448 	fVideoMenu->SetEnabled(fHasVideo);
1449 	fAudioMenu->SetEnabled(fHasAudio);
1450 	int previousSourceWidth = fSourceWidth;
1451 	int previousSourceHeight = fSourceHeight;
1452 	int previousWidthAspect = fWidthAspect;
1453 	int previousHeightAspect = fHeightAspect;
1454 	if (fHasVideo) {
1455 		fController->GetSize(&fSourceWidth, &fSourceHeight,
1456 			&fWidthAspect, &fHeightAspect);
1457 	} else {
1458 		fSourceWidth = 0;
1459 		fSourceHeight = 0;
1460 		fWidthAspect = 1;
1461 		fHeightAspect = 1;
1462 	}
1463 	_UpdateControlsEnabledStatus();
1464 
1465 	// Adopt the size and window layout if necessary
1466 	if (previousSourceWidth != fSourceWidth
1467 		|| previousSourceHeight != fSourceHeight
1468 		|| previousWidthAspect != fWidthAspect
1469 		|| previousHeightAspect != fHeightAspect) {
1470 
1471 		_SetWindowSizeLimits();
1472 
1473 		if (!fIsFullscreen) {
1474 			// Resize to 100% but stay on screen
1475 			_ResizeWindow(100, !fHasVideo, true);
1476 		} else {
1477 			// Make sure we relayout the video view when in full screen mode
1478 			FrameResized(Frame().Width(), Frame().Height());
1479 		}
1480 	}
1481 
1482 	_ShowIfNeeded();
1483 
1484 	fVideoView->MakeFocus();
1485 }
1486 
1487 
1488 void
1489 MainWin::_CreateMenu()
1490 {
1491 	fFileMenu = new BMenu(kApplicationName);
1492 	fPlaylistMenu = new BMenu(B_TRANSLATE("Playlist" B_UTF8_ELLIPSIS));
1493 	fAudioMenu = new BMenu(B_TRANSLATE("Audio"));
1494 	fVideoMenu = new BMenu(B_TRANSLATE("Video"));
1495 	fVideoAspectMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
1496 	fAudioTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track",
1497 		"Audio Track Menu"));
1498 	fVideoTrackMenu = new BMenu(B_TRANSLATE_CONTEXT("Track",
1499 		"Video Track Menu"));
1500 	fSubTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
1501 	fAttributesMenu = new BMenu(B_TRANSLATE("Attributes"));
1502 
1503 	fMenuBar->AddItem(fFileMenu);
1504 	fMenuBar->AddItem(fAudioMenu);
1505 	fMenuBar->AddItem(fVideoMenu);
1506 	fMenuBar->AddItem(fAttributesMenu);
1507 
1508 	BMenuItem* item = new BMenuItem(B_TRANSLATE("New player" B_UTF8_ELLIPSIS),
1509 		new BMessage(M_NEW_PLAYER), 'N');
1510 	fFileMenu->AddItem(item);
1511 	item->SetTarget(be_app);
1512 
1513 	// Add recent files to "Open File" entry as sub-menu.
1514 	BRecentFilesList recentFiles(10, false, NULL, kAppSig);
1515 	item = new BMenuItem(recentFiles.NewFileListMenu(
1516 		B_TRANSLATE("Open file" B_UTF8_ELLIPSIS), NULL, NULL, this, 10, true,
1517 		NULL, kAppSig), new BMessage(M_FILE_OPEN));
1518 	item->SetShortcut('O', 0);
1519 	fFileMenu->AddItem(item);
1520 
1521 	item = new BMenuItem(B_TRANSLATE("Open network stream"),
1522 		new BMessage(M_NETWORK_STREAM_OPEN));
1523 	fFileMenu->AddItem(item);
1524 
1525 	fFileMenu->AddSeparatorItem();
1526 
1527 	fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("File info" B_UTF8_ELLIPSIS),
1528 		new BMessage(M_FILE_INFO), 'I'));
1529 	fFileMenu->AddItem(fPlaylistMenu);
1530 	fPlaylistMenu->Superitem()->SetShortcut('P', B_COMMAND_KEY);
1531 	fPlaylistMenu->Superitem()->SetMessage(new BMessage(M_FILE_PLAYLIST));
1532 
1533 	fFileMenu->AddSeparatorItem();
1534 
1535 	fNoInterfaceMenuItem = new BMenuItem(B_TRANSLATE("Hide interface"),
1536 		new BMessage(M_TOGGLE_NO_INTERFACE), 'H');
1537 	fFileMenu->AddItem(fNoInterfaceMenuItem);
1538 	fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Always on top"),
1539 		new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
1540 
1541 	item = new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
1542 		new BMessage(M_SETTINGS), 'S');
1543 	fFileMenu->AddItem(item);
1544 	item->SetTarget(be_app);
1545 
1546 	fFileMenu->AddSeparatorItem();
1547 
1548 	fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
1549 		new BMessage(M_FILE_CLOSE), 'W'));
1550 	fFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
1551 		new BMessage(M_FILE_QUIT), 'Q'));
1552 
1553 	fPlaylistMenu->SetRadioMode(true);
1554 
1555 	fAudioMenu->AddItem(fAudioTrackMenu);
1556 
1557 	fVideoMenu->AddItem(fVideoTrackMenu);
1558 	fVideoMenu->AddItem(fSubTitleTrackMenu);
1559 	fVideoMenu->AddSeparatorItem();
1560 	BMessage* resizeMessage = new BMessage(M_VIEW_SIZE);
1561 	resizeMessage->AddInt32("size", 50);
1562 	fVideoMenu->AddItem(new BMenuItem(
1563 		B_TRANSLATE("50% scale"), resizeMessage, '0'));
1564 
1565 	resizeMessage = new BMessage(M_VIEW_SIZE);
1566 	resizeMessage->AddInt32("size", 100);
1567 	fVideoMenu->AddItem(new BMenuItem(
1568 		B_TRANSLATE("100% scale"), resizeMessage, '1'));
1569 
1570 	resizeMessage = new BMessage(M_VIEW_SIZE);
1571 	resizeMessage->AddInt32("size", 200);
1572 	fVideoMenu->AddItem(new BMenuItem(
1573 		B_TRANSLATE("200% scale"), resizeMessage, '2'));
1574 
1575 	resizeMessage = new BMessage(M_VIEW_SIZE);
1576 	resizeMessage->AddInt32("size", 300);
1577 	fVideoMenu->AddItem(new BMenuItem(
1578 		B_TRANSLATE("300% scale"), resizeMessage, '3'));
1579 
1580 	resizeMessage = new BMessage(M_VIEW_SIZE);
1581 	resizeMessage->AddInt32("size", 400);
1582 	fVideoMenu->AddItem(new BMenuItem(
1583 		B_TRANSLATE("400% scale"), resizeMessage, '4'));
1584 
1585 	fVideoMenu->AddSeparatorItem();
1586 
1587 	fVideoMenu->AddItem(new BMenuItem(B_TRANSLATE("Full screen"),
1588 		new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
1589 
1590 	fVideoMenu->AddSeparatorItem();
1591 
1592 	_SetupVideoAspectItems(fVideoAspectMenu);
1593 	fVideoMenu->AddItem(fVideoAspectMenu);
1594 
1595 	fRatingMenu = new BMenu(B_TRANSLATE("Rating"));
1596 	fAttributesMenu->AddItem(fRatingMenu);
1597 	for (int32 i = 1; i <= 10; i++) {
1598 		char label[16];
1599 		snprintf(label, sizeof(label), "%" B_PRId32, i);
1600 		BMessage* setRatingMsg = new BMessage(M_SET_RATING);
1601 		setRatingMsg->AddInt32("rating", i);
1602 		fRatingMenu->AddItem(new BMenuItem(label, setRatingMsg));
1603 	}
1604 }
1605 
1606 
1607 void
1608 MainWin::_SetupVideoAspectItems(BMenu* menu)
1609 {
1610 	BMenuItem* item;
1611 	while ((item = menu->RemoveItem((int32)0)) != NULL)
1612 		delete item;
1613 
1614 	int width;
1615 	int height;
1616 	int widthAspect;
1617 	int heightAspect;
1618 	fController->GetSize(&width, &height, &widthAspect, &heightAspect);
1619 		// We don't care if there is a video track at all. In that
1620 		// case we should end up not marking any item.
1621 
1622 	// NOTE: The item marking may end up marking for example both
1623 	// "Stream Settings" and "16 : 9" if the stream settings happen to
1624 	// be "16 : 9".
1625 
1626 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Stream settings"),
1627 		new BMessage(M_ASPECT_SAME_AS_SOURCE), '1', B_SHIFT_KEY));
1628 	item->SetMarked(widthAspect == fWidthAspect
1629 		&& heightAspect == fHeightAspect);
1630 
1631 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("No aspect correction"),
1632 		new BMessage(M_ASPECT_NO_DISTORTION), '0', B_SHIFT_KEY));
1633 	item->SetMarked(width == fWidthAspect && height == fHeightAspect);
1634 
1635 	menu->AddSeparatorItem();
1636 
1637 	menu->AddItem(item = new BMenuItem("4 : 3",
1638 		new BMessage(M_ASPECT_4_3), 2, B_SHIFT_KEY));
1639 	item->SetMarked(fWidthAspect == 4 && fHeightAspect == 3);
1640 	menu->AddItem(item = new BMenuItem("16 : 9",
1641 		new BMessage(M_ASPECT_16_9), 3, B_SHIFT_KEY));
1642 	item->SetMarked(fWidthAspect == 16 && fHeightAspect == 9);
1643 
1644 	menu->AddSeparatorItem();
1645 
1646 	menu->AddItem(item = new BMenuItem("1.66 : 1",
1647 		new BMessage(M_ASPECT_83_50)));
1648 	item->SetMarked(fWidthAspect == 83 && fHeightAspect == 50);
1649 	menu->AddItem(item = new BMenuItem("1.75 : 1",
1650 		new BMessage(M_ASPECT_7_4)));
1651 	item->SetMarked(fWidthAspect == 7 && fHeightAspect == 4);
1652 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("1.85 : 1 (American)"),
1653 		new BMessage(M_ASPECT_37_20)));
1654 	item->SetMarked(fWidthAspect == 37 && fHeightAspect == 20);
1655 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("2.35 : 1 (Cinemascope)"),
1656 		new BMessage(M_ASPECT_47_20)));
1657 	item->SetMarked(fWidthAspect == 47 && fHeightAspect == 20);
1658 }
1659 
1660 
1661 void
1662 MainWin::_SetupTrackMenus(BMenu* audioTrackMenu, BMenu* videoTrackMenu,
1663 	BMenu* subTitleTrackMenu)
1664 {
1665 	audioTrackMenu->RemoveItems(0, audioTrackMenu->CountItems(), true);
1666 	videoTrackMenu->RemoveItems(0, videoTrackMenu->CountItems(), true);
1667 	subTitleTrackMenu->RemoveItems(0, subTitleTrackMenu->CountItems(), true);
1668 
1669 	char s[100];
1670 
1671 	int count = fController->AudioTrackCount();
1672 	int current = fController->CurrentAudioTrack();
1673 	for (int i = 0; i < count; i++) {
1674 		BMessage metaData;
1675 		const char* languageString = NULL;
1676 		if (fController->GetAudioMetaData(i, &metaData) == B_OK)
1677 			metaData.FindString("language", &languageString);
1678 		if (languageString != NULL) {
1679 			BLanguage language(languageString);
1680 			BString languageName;
1681 			if (language.GetName(languageName) == B_OK)
1682 				languageString = languageName.String();
1683 			snprintf(s, sizeof(s), "%s", languageString);
1684 		} else
1685 			snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1686 		BMenuItem* item = new BMenuItem(s,
1687 			new BMessage(M_SELECT_AUDIO_TRACK + i));
1688 		item->SetMarked(i == current);
1689 		audioTrackMenu->AddItem(item);
1690 	}
1691 	if (count == 0) {
1692 		audioTrackMenu->AddItem(new BMenuItem(B_TRANSLATE_CONTEXT("none",
1693 			"Audio track menu"), new BMessage(M_DUMMY)));
1694 		audioTrackMenu->ItemAt(0)->SetMarked(true);
1695 	}
1696 	audioTrackMenu->SetEnabled(count > 1);
1697 
1698 	count = fController->VideoTrackCount();
1699 	current = fController->CurrentVideoTrack();
1700 	for (int i = 0; i < count; i++) {
1701 		snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1702 		BMenuItem* item = new BMenuItem(s,
1703 			new BMessage(M_SELECT_VIDEO_TRACK + i));
1704 		item->SetMarked(i == current);
1705 		videoTrackMenu->AddItem(item);
1706 	}
1707 	if (count == 0) {
1708 		videoTrackMenu->AddItem(new BMenuItem(B_TRANSLATE("none"),
1709 			new BMessage(M_DUMMY)));
1710 		videoTrackMenu->ItemAt(0)->SetMarked(true);
1711 	}
1712 	videoTrackMenu->SetEnabled(count > 1);
1713 
1714 	count = fController->SubTitleTrackCount();
1715 	if (count > 0) {
1716 		current = fController->CurrentSubTitleTrack();
1717 		BMenuItem* item = new BMenuItem(
1718 			B_TRANSLATE_CONTEXT("Off", "Subtitles menu"),
1719 			new BMessage(M_SELECT_SUB_TITLE_TRACK - 1));
1720 		subTitleTrackMenu->AddItem(item);
1721 		item->SetMarked(current == -1);
1722 
1723 		subTitleTrackMenu->AddSeparatorItem();
1724 
1725 		for (int i = 0; i < count; i++) {
1726 			const char* name = fController->SubTitleTrackName(i);
1727 			if (name != NULL)
1728 				snprintf(s, sizeof(s), "%s", name);
1729 			else
1730 				snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1731 			item = new BMenuItem(s,
1732 				new BMessage(M_SELECT_SUB_TITLE_TRACK + i));
1733 			item->SetMarked(i == current);
1734 			subTitleTrackMenu->AddItem(item);
1735 		}
1736 	} else {
1737 		subTitleTrackMenu->AddItem(new BMenuItem(
1738 			B_TRANSLATE_CONTEXT("none", "Subtitles menu"),
1739 			new BMessage(M_DUMMY)));
1740 		subTitleTrackMenu->ItemAt(0)->SetMarked(true);
1741 	}
1742 	subTitleTrackMenu->SetEnabled(count > 0);
1743 }
1744 
1745 
1746 void
1747 MainWin::_UpdateAudioChannelCount(int32 audioTrackIndex)
1748 {
1749 	fControls->SetAudioChannelCount(fController->AudioTrackChannelCount());
1750 }
1751 
1752 
1753 void
1754 MainWin::_GetMinimumWindowSize(int& width, int& height) const
1755 {
1756 	width = MIN_WIDTH;
1757 	height = 0;
1758 	if (!fNoInterface) {
1759 		width = max_c(width, fMenuBarWidth);
1760 		width = max_c(width, fControlsWidth);
1761 		height = fMenuBarHeight + fControlsHeight;
1762 	}
1763 }
1764 
1765 
1766 void
1767 MainWin::_GetUnscaledVideoSize(int& videoWidth, int& videoHeight) const
1768 {
1769 	if (fWidthAspect != 0 && fHeightAspect != 0) {
1770 		videoWidth = fSourceHeight * fWidthAspect / fHeightAspect;
1771 		videoHeight = fSourceWidth * fHeightAspect / fWidthAspect;
1772 		// Use the scaling which produces an enlarged view.
1773 		if (videoWidth > fSourceWidth) {
1774 			// Enlarge width
1775 			videoHeight = fSourceHeight;
1776 		} else {
1777 			// Enlarge height
1778 			videoWidth = fSourceWidth;
1779 		}
1780 	} else {
1781 		videoWidth = fSourceWidth;
1782 		videoHeight = fSourceHeight;
1783 	}
1784 }
1785 
1786 
1787 void
1788 MainWin::_SetWindowSizeLimits()
1789 {
1790 	int minWidth;
1791 	int minHeight;
1792 	_GetMinimumWindowSize(minWidth, minHeight);
1793 	SetSizeLimits(minWidth - 1, 32000, minHeight - 1,
1794 		fHasVideo ? 32000 : minHeight - 1);
1795 }
1796 
1797 
1798 int
1799 MainWin::_CurrentVideoSizeInPercent() const
1800 {
1801 	if (!fHasVideo)
1802 		return 0;
1803 
1804 	int videoWidth;
1805 	int videoHeight;
1806 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1807 
1808 	int viewWidth = fVideoView->Bounds().IntegerWidth() + 1;
1809 	int viewHeight = fVideoView->Bounds().IntegerHeight() + 1;
1810 
1811 	int widthPercent = viewWidth * 100 / videoWidth;
1812 	int heightPercent = viewHeight * 100 / videoHeight;
1813 
1814 	if (widthPercent > heightPercent)
1815 		return widthPercent;
1816 	return heightPercent;
1817 }
1818 
1819 
1820 void
1821 MainWin::_ZoomVideoView(int percentDiff)
1822 {
1823 	if (!fHasVideo)
1824 		return;
1825 
1826 	int percent = _CurrentVideoSizeInPercent();
1827 	int newSize = percent * (100 + percentDiff) / 100;
1828 
1829 	if (newSize < 25)
1830 		newSize = 25;
1831 	if (newSize > 400)
1832 		newSize = 400;
1833 	if (newSize != percent) {
1834 		BMessage message(M_VIEW_SIZE);
1835 		message.AddInt32("size", newSize);
1836 		PostMessage(&message);
1837 	}
1838 }
1839 
1840 
1841 void
1842 MainWin::_ResizeWindow(int percent, bool useNoVideoWidth, bool stayOnScreen)
1843 {
1844 	// Get required window size
1845 	int videoWidth;
1846 	int videoHeight;
1847 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1848 
1849 	videoWidth = (videoWidth * percent) / 100;
1850 	videoHeight = (videoHeight * percent) / 100;
1851 
1852 	// Calculate and set the minimum window size
1853 	int width;
1854 	int height;
1855 	_GetMinimumWindowSize(width, height);
1856 
1857 	width = max_c(width, videoWidth) - 1;
1858 	if (useNoVideoWidth)
1859 		width = max_c(width, fNoVideoWidth);
1860 	height = height + videoHeight - 1;
1861 
1862 	if (stayOnScreen) {
1863 		BRect screenFrame(BScreen(this).Frame());
1864 		BRect frame(Frame());
1865 		BRect decoratorFrame(DecoratorFrame());
1866 
1867 		// Shrink the screen frame by the window border size
1868 		screenFrame.top += frame.top - decoratorFrame.top;
1869 		screenFrame.left += frame.left - decoratorFrame.left;
1870 		screenFrame.right += frame.right - decoratorFrame.right;
1871 		screenFrame.bottom += frame.bottom - decoratorFrame.bottom;
1872 
1873 		// Update frame to what the new size would be
1874 		frame.right = frame.left + width;
1875 		frame.bottom = frame.top + height;
1876 
1877 		if (!screenFrame.Contains(frame)) {
1878 			// Resize the window so it doesn't extend outside the current
1879 			// screen frame.
1880 			// We don't use BWindow::MoveOnScreen() in order to resize the
1881 			// window while keeping the same aspect ratio.
1882 			if (frame.Width() > screenFrame.Width()
1883 				|| frame.Height() > screenFrame.Height()) {
1884 				// too large
1885 				int widthDiff
1886 					= frame.IntegerWidth() - screenFrame.IntegerWidth();
1887 				int heightDiff
1888 					= frame.IntegerHeight() - screenFrame.IntegerHeight();
1889 
1890 				float shrinkScale;
1891 				if (widthDiff > heightDiff)
1892 					shrinkScale = (float)(width - widthDiff) / width;
1893 				else
1894 					shrinkScale = (float)(height - heightDiff) / height;
1895 
1896 				// Resize width/height and center window
1897 				width = lround(width * shrinkScale);
1898 				height = lround(height * shrinkScale);
1899 				MoveTo((screenFrame.left + screenFrame.right - width) / 2,
1900 					(screenFrame.top + screenFrame.bottom - height) / 2);
1901 			} else {
1902 				// just off-screen on one or more sides
1903 				int offsetX = 0;
1904 				int offsetY = 0;
1905 				if (frame.left < screenFrame.left)
1906 					offsetX = (int)(screenFrame.left - frame.left);
1907 				else if (frame.right > screenFrame.right)
1908 					offsetX = (int)(screenFrame.right - frame.right);
1909 				if (frame.top < screenFrame.top)
1910 					offsetY = (int)(screenFrame.top - frame.top);
1911 				else if (frame.bottom > screenFrame.bottom)
1912 					offsetY = (int)(screenFrame.bottom - frame.bottom);
1913 				MoveBy(offsetX, offsetY);
1914 			}
1915 		}
1916 	}
1917 
1918 	ResizeTo(width, height);
1919 }
1920 
1921 
1922 void
1923 MainWin::_ResizeVideoView(int x, int y, int width, int height)
1924 {
1925 	// Keep aspect ratio, place video view inside
1926 	// the background area (may create black bars).
1927 	int videoWidth;
1928 	int videoHeight;
1929 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1930 	float scaledWidth  = videoWidth;
1931 	float scaledHeight = videoHeight;
1932 	float factor = min_c(width / scaledWidth, height / scaledHeight);
1933 	int renderWidth = lround(scaledWidth * factor);
1934 	int renderHeight = lround(scaledHeight * factor);
1935 	if (renderWidth > width)
1936 		renderWidth = width;
1937 	if (renderHeight > height)
1938 		renderHeight = height;
1939 
1940 	int xOffset = (width - renderWidth) / 2;
1941 	int yOffset = (height - renderHeight) / 2;
1942 
1943 	fVideoView->MoveTo(x, y);
1944 	fVideoView->ResizeTo(width - 1, height - 1);
1945 
1946 	BRect videoFrame(xOffset, yOffset,
1947 		xOffset + renderWidth - 1, yOffset + renderHeight - 1);
1948 
1949 	fVideoView->SetVideoFrame(videoFrame);
1950 	fVideoView->SetSubTitleMaxBottom(height - 1);
1951 }
1952 
1953 
1954 // #pragma mark -
1955 
1956 
1957 void
1958 MainWin::_MouseDown(BMessage* msg, BView* originalHandler)
1959 {
1960 	uint32 buttons = msg->FindInt32("buttons");
1961 
1962 	// On Zeta, only "screen_where" is reliable, "where" and "be:view_where"
1963 	// seem to be broken
1964 	BPoint screenWhere;
1965 	if (msg->FindPoint("screen_where", &screenWhere) != B_OK) {
1966 		// TODO: remove
1967 		// Workaround for BeOS R5, it has no "screen_where"
1968 		if (!originalHandler || msg->FindPoint("where", &screenWhere) < B_OK)
1969 			return;
1970 		originalHandler->ConvertToScreen(&screenWhere);
1971 	}
1972 
1973 	// double click handling
1974 
1975 	if (msg->FindInt32("clicks") % 2 == 0) {
1976 		BRect rect(screenWhere.x - 1, screenWhere.y - 1, screenWhere.x + 1,
1977 			screenWhere.y + 1);
1978 		if (rect.Contains(fMouseDownMousePos)) {
1979 			if (buttons == B_PRIMARY_MOUSE_BUTTON)
1980 				PostMessage(M_TOGGLE_FULLSCREEN);
1981 			else if (buttons == B_SECONDARY_MOUSE_BUTTON)
1982 				PostMessage(M_TOGGLE_NO_INTERFACE);
1983 
1984 			return;
1985 		}
1986 	}
1987 
1988 	fMouseDownMousePos = screenWhere;
1989 	fMouseDownWindowPos = Frame().LeftTop();
1990 
1991 	if (buttons == B_PRIMARY_MOUSE_BUTTON && !fIsFullscreen) {
1992 		// start mouse tracking
1993 		fVideoView->SetMouseEventMask(B_POINTER_EVENTS | B_NO_POINTER_HISTORY
1994 			/* | B_LOCK_WINDOW_FOCUS */);
1995 		fMouseDownTracking = true;
1996 	}
1997 
1998 	// pop up a context menu if right mouse button is down
1999 
2000 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
2001 		_ShowContextMenu(screenWhere);
2002 }
2003 
2004 
2005 void
2006 MainWin::_MouseMoved(BMessage* msg, BView* originalHandler)
2007 {
2008 //	msg->PrintToStream();
2009 
2010 	BPoint mousePos;
2011 	uint32 buttons = msg->FindInt32("buttons");
2012 	// On Zeta, only "screen_where" is reliable, "where"
2013 	// and "be:view_where" seem to be broken
2014 	if (msg->FindPoint("screen_where", &mousePos) != B_OK) {
2015 		// TODO: remove
2016 		// Workaround for BeOS R5, it has no "screen_where"
2017 		if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK)
2018 			return;
2019 		originalHandler->ConvertToScreen(&mousePos);
2020 	}
2021 
2022 	if (buttons == B_PRIMARY_MOUSE_BUTTON && fMouseDownTracking
2023 		&& !fIsFullscreen) {
2024 //		printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y);
2025 		float delta_x = mousePos.x - fMouseDownMousePos.x;
2026 		float delta_y = mousePos.y - fMouseDownMousePos.y;
2027 		float x = fMouseDownWindowPos.x + delta_x;
2028 		float y = fMouseDownWindowPos.y + delta_y;
2029 //		printf("move window to %.0f, %.0f\n", x, y);
2030 		MoveTo(x, y);
2031 	}
2032 
2033 	bigtime_t eventTime;
2034 	if (msg->FindInt64("when", &eventTime) != B_OK)
2035 		eventTime = system_time();
2036 
2037 	if (buttons == 0 && fIsFullscreen) {
2038 		BPoint moveDelta = mousePos - fLastMousePos;
2039 		float moveDeltaDist
2040 			= sqrtf(moveDelta.x * moveDelta.x + moveDelta.y * moveDelta.y);
2041 		if (eventTime - fLastMouseMovedTime < 200000)
2042 			fMouseMoveDist += moveDeltaDist;
2043 		else
2044 			fMouseMoveDist = moveDeltaDist;
2045 		if (fMouseMoveDist > 5)
2046 			_ShowFullscreenControls(true);
2047 	}
2048 
2049 	fLastMousePos = mousePos;
2050 	fLastMouseMovedTime =eventTime;
2051 }
2052 
2053 
2054 void
2055 MainWin::_MouseUp(BMessage* msg)
2056 {
2057 	fMouseDownTracking = false;
2058 }
2059 
2060 
2061 void
2062 MainWin::_ShowContextMenu(const BPoint& screenPoint)
2063 {
2064 	printf("Show context menu\n");
2065 	BPopUpMenu* menu = new BPopUpMenu("context menu", false, false);
2066 	BMenuItem* item;
2067 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Full screen"),
2068 		new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
2069 	item->SetMarked(fIsFullscreen);
2070 	item->SetEnabled(fHasVideo);
2071 
2072 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Hide interface"),
2073 		new BMessage(M_TOGGLE_NO_INTERFACE), 'H'));
2074 	item->SetMarked(fNoInterface);
2075 	item->SetEnabled(fHasVideo && !fIsFullscreen);
2076 
2077 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"),
2078 		new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
2079 	item->SetMarked(fAlwaysOnTop);
2080 	item->SetEnabled(fHasVideo);
2081 
2082 	BMenu* aspectSubMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
2083 	_SetupVideoAspectItems(aspectSubMenu);
2084 	aspectSubMenu->SetTargetForItems(this);
2085 	menu->AddItem(item = new BMenuItem(aspectSubMenu));
2086 	item->SetEnabled(fHasVideo);
2087 
2088 	menu->AddSeparatorItem();
2089 
2090 	// Add track selector menus
2091 	BMenu* audioTrackMenu = new BMenu(B_TRANSLATE("Audio track"));
2092 	BMenu* videoTrackMenu = new BMenu(B_TRANSLATE("Video track"));
2093 	BMenu* subTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
2094 	_SetupTrackMenus(audioTrackMenu, videoTrackMenu, subTitleTrackMenu);
2095 
2096 	audioTrackMenu->SetTargetForItems(this);
2097 	videoTrackMenu->SetTargetForItems(this);
2098 	subTitleTrackMenu->SetTargetForItems(this);
2099 
2100 	menu->AddItem(item = new BMenuItem(audioTrackMenu));
2101 	item->SetEnabled(fHasAudio);
2102 
2103 	menu->AddItem(item = new BMenuItem(videoTrackMenu));
2104 	item->SetEnabled(fHasVideo);
2105 
2106 	menu->AddItem(item = new BMenuItem(subTitleTrackMenu));
2107 	item->SetEnabled(fHasVideo);
2108 
2109 	menu->AddSeparatorItem();
2110 	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), new BMessage(M_FILE_QUIT), 'Q'));
2111 
2112 	menu->SetTargetForItems(this);
2113 	BRect rect(screenPoint.x - 5, screenPoint.y - 5, screenPoint.x + 5,
2114 		screenPoint.y + 5);
2115 	menu->Go(screenPoint, true, true, rect, true);
2116 }
2117 
2118 
2119 /*!	Trap keys that are about to be send to background or renderer view.
2120 	Return true if it shouldn't be passed to the view.
2121 */
2122 bool
2123 MainWin::_KeyDown(BMessage* msg)
2124 {
2125 	uint32 key = msg->FindInt32("key");
2126 	uint32 rawChar = msg->FindInt32("raw_char");
2127 	uint32 modifier = msg->FindInt32("modifiers");
2128 
2129 //	printf("key 0x%lx, rawChar 0x%lx, modifiers 0x%lx\n", key, rawChar,
2130 //		modifier);
2131 
2132 	// ignore the system modifier namespace
2133 	if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
2134 			== (B_CONTROL_KEY | B_COMMAND_KEY))
2135 		return false;
2136 
2137 	switch (rawChar) {
2138 		case B_SPACE:
2139 			fController->TogglePlaying();
2140 			return true;
2141 
2142 		case 'm':
2143 			fController->ToggleMute();
2144 			return true;
2145 
2146 		case B_ESCAPE:
2147 			if (!fIsFullscreen)
2148 				break;
2149 
2150 			PostMessage(M_TOGGLE_FULLSCREEN);
2151 			return true;
2152 
2153 		case B_ENTER:		// Enter / Return
2154 			if ((modifier & B_COMMAND_KEY) != 0) {
2155 				PostMessage(M_TOGGLE_FULLSCREEN);
2156 				return true;
2157 			}
2158 			break;
2159 
2160 		case B_TAB:
2161 		case 'f':
2162 			if ((modifier & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
2163 					| B_MENU_KEY)) == 0) {
2164 				PostMessage(M_TOGGLE_FULLSCREEN);
2165 				return true;
2166 			}
2167 			break;
2168 
2169 		case B_UP_ARROW:
2170 			if ((modifier & B_COMMAND_KEY) != 0)
2171 				PostMessage(M_SKIP_NEXT);
2172 			else
2173 				PostMessage(M_VOLUME_UP);
2174 			return true;
2175 
2176 		case B_DOWN_ARROW:
2177 			if ((modifier & B_COMMAND_KEY) != 0)
2178 				PostMessage(M_SKIP_PREV);
2179 			else
2180 				PostMessage(M_VOLUME_DOWN);
2181 			return true;
2182 
2183 		case B_RIGHT_ARROW:
2184 			if ((modifier & B_COMMAND_KEY) != 0)
2185 				PostMessage(M_SKIP_NEXT);
2186 			else if (fAllowWinding) {
2187 				BMessage windMessage(M_WIND);
2188 				if ((modifier & B_SHIFT_KEY) != 0) {
2189 					windMessage.AddInt64("how much", 30000000LL);
2190 					windMessage.AddInt64("frames", 5);
2191 				} else {
2192 					windMessage.AddInt64("how much", 5000000LL);
2193 					windMessage.AddInt64("frames", 1);
2194 				}
2195 				PostMessage(&windMessage);
2196 			}
2197 			return true;
2198 
2199 		case B_LEFT_ARROW:
2200 			if ((modifier & B_COMMAND_KEY) != 0)
2201 				PostMessage(M_SKIP_PREV);
2202 			else if (fAllowWinding) {
2203 				BMessage windMessage(M_WIND);
2204 				if ((modifier & B_SHIFT_KEY) != 0) {
2205 					windMessage.AddInt64("how much", -30000000LL);
2206 					windMessage.AddInt64("frames", -5);
2207 				} else {
2208 					windMessage.AddInt64("how much", -5000000LL);
2209 					windMessage.AddInt64("frames", -1);
2210 				}
2211 				PostMessage(&windMessage);
2212 			}
2213 			return true;
2214 
2215 		case B_PAGE_UP:
2216 			PostMessage(M_SKIP_NEXT);
2217 			return true;
2218 
2219 		case B_PAGE_DOWN:
2220 			PostMessage(M_SKIP_PREV);
2221 			return true;
2222 
2223 		case '+':
2224 			if ((modifier & B_COMMAND_KEY) == 0) {
2225 				_ZoomVideoView(10);
2226 				return true;
2227 			}
2228 			break;
2229 
2230 		case '-':
2231 			if ((modifier & B_COMMAND_KEY) == 0) {
2232 				_ZoomVideoView(-10);
2233 				return true;
2234 			}
2235 			break;
2236 
2237 		case B_DELETE:
2238 		case 'd': 			// d for delete
2239 		case 't':			// t for Trash
2240 			if ((modifiers() & B_COMMAND_KEY) != 0) {
2241 				BAutolock _(fPlaylist);
2242 				BMessage removeMessage(M_PLAYLIST_MOVE_TO_TRASH);
2243 				removeMessage.AddInt32("playlist index",
2244 					fPlaylist->CurrentItemIndex());
2245 				fPlaylistWindow->PostMessage(&removeMessage);
2246 				return true;
2247 			}
2248 			break;
2249 	}
2250 
2251 	switch (key) {
2252 		case 0x3a:  		// numeric keypad +
2253 			if ((modifier & B_COMMAND_KEY) == 0) {
2254 				_ZoomVideoView(10);
2255 				return true;
2256 			}
2257 			break;
2258 
2259 		case 0x25:  		// numeric keypad -
2260 			if ((modifier & B_COMMAND_KEY) == 0) {
2261 				_ZoomVideoView(-10);
2262 				return true;
2263 			}
2264 			break;
2265 
2266 		case 0x38:			// numeric keypad up arrow
2267 			PostMessage(M_VOLUME_UP);
2268 			return true;
2269 
2270 		case 0x59:			// numeric keypad down arrow
2271 			PostMessage(M_VOLUME_DOWN);
2272 			return true;
2273 
2274 		case 0x39:			// numeric keypad page up
2275 		case 0x4a:			// numeric keypad right arrow
2276 			PostMessage(M_SKIP_NEXT);
2277 			return true;
2278 
2279 		case 0x5a:			// numeric keypad page down
2280 		case 0x48:			// numeric keypad left arrow
2281 			PostMessage(M_SKIP_PREV);
2282 			return true;
2283 
2284 		// Playback controls along the bottom of the keyboard:
2285 		// Z X C (V) B  for US International
2286 		case 0x4c:
2287 			PostMessage(M_SKIP_PREV);
2288 			return true;
2289 		case 0x4d:
2290 			fController->TogglePlaying();
2291 			return true;
2292 		case 0x4e:
2293 			fController->Pause();
2294 			return true;
2295 		case 0x4f:
2296 			fController->Stop();
2297 			return true;
2298 		case 0x50:
2299 			PostMessage(M_SKIP_NEXT);
2300 			return true;
2301 	}
2302 
2303 	return false;
2304 }
2305 
2306 
2307 // #pragma mark -
2308 
2309 
2310 void
2311 MainWin::_ToggleFullscreen()
2312 {
2313 	printf("_ToggleFullscreen enter\n");
2314 
2315 	if (!fHasVideo) {
2316 		printf("_ToggleFullscreen - ignoring, as we don't have a video\n");
2317 		return;
2318 	}
2319 
2320 	fIsFullscreen = !fIsFullscreen;
2321 
2322 	if (fIsFullscreen) {
2323 		// switch to fullscreen
2324 
2325 		fSavedFrame = Frame();
2326 		printf("saving current frame: %d %d %d %d\n", int(fSavedFrame.left),
2327 			int(fSavedFrame.top), int(fSavedFrame.right),
2328 			int(fSavedFrame.bottom));
2329 		BScreen screen(this);
2330 		BRect rect(screen.Frame());
2331 
2332 		Hide();
2333 		MoveTo(rect.left, rect.top);
2334 		ResizeTo(rect.Width(), rect.Height());
2335 		Show();
2336 
2337 	} else {
2338 		// switch back from full screen mode
2339 		_ShowFullscreenControls(false, false);
2340 
2341 		Hide();
2342 		MoveTo(fSavedFrame.left, fSavedFrame.top);
2343 		ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
2344 		Show();
2345 	}
2346 
2347 	fVideoView->SetFullscreen(fIsFullscreen);
2348 
2349 	_MarkItem(fFileMenu, M_TOGGLE_FULLSCREEN, fIsFullscreen);
2350 
2351 	printf("_ToggleFullscreen leave\n");
2352 }
2353 
2354 void
2355 MainWin::_ToggleAlwaysOnTop()
2356 {
2357 	fAlwaysOnTop = !fAlwaysOnTop;
2358 	SetFeel(fAlwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL);
2359 
2360 	_MarkItem(fFileMenu, M_TOGGLE_ALWAYS_ON_TOP, fAlwaysOnTop);
2361 }
2362 
2363 
2364 void
2365 MainWin::_ToggleNoInterface()
2366 {
2367 	printf("_ToggleNoInterface enter\n");
2368 
2369 	if (fIsFullscreen || !fHasVideo) {
2370 		// Fullscreen playback is always without interface and
2371 		// audio playback is always with interface. So we ignore these
2372 		// two states here.
2373 		printf("_ToggleNoControls leave, doing nothing, we are fullscreen\n");
2374 		return;
2375 	}
2376 
2377 	fNoInterface = !fNoInterface;
2378 	_SetWindowSizeLimits();
2379 
2380 	if (fNoInterface) {
2381 		MoveBy(0, fMenuBarHeight);
2382 		ResizeBy(0, -(fControlsHeight + fMenuBarHeight));
2383 		SetLook(B_BORDERED_WINDOW_LOOK);
2384 	} else {
2385 		MoveBy(0, -fMenuBarHeight);
2386 		ResizeBy(0, fControlsHeight + fMenuBarHeight);
2387 		SetLook(B_TITLED_WINDOW_LOOK);
2388 	}
2389 
2390 	_MarkItem(fFileMenu, M_TOGGLE_NO_INTERFACE, fNoInterface);
2391 
2392 	printf("_ToggleNoInterface leave\n");
2393 }
2394 
2395 
2396 void
2397 MainWin::_ShowIfNeeded()
2398 {
2399 	// Only proceed if the window is already running
2400 	if (find_thread(NULL) != Thread())
2401 		return;
2402 
2403 	if (!fHasVideo && fNoVideoFrame.IsValid()) {
2404 		MoveTo(fNoVideoFrame.LeftTop());
2405 		ResizeTo(fNoVideoFrame.Width(), fNoVideoFrame.Height());
2406 		MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN);
2407 	} else if (fHasVideo && IsHidden())
2408 		CenterOnScreen();
2409 
2410 	fNoVideoFrame = BRect();
2411 
2412 	if (IsHidden()) {
2413 		Show();
2414 		UpdateIfNeeded();
2415 	}
2416 }
2417 
2418 
2419 void
2420 MainWin::_ShowFullscreenControls(bool show, bool animate)
2421 {
2422 	if (fShowsFullscreenControls == show)
2423 		return;
2424 
2425 	fShowsFullscreenControls = show;
2426 	fVideoView->SetFullscreenControlsVisible(show);
2427 
2428 	if (show) {
2429 		fControls->RemoveSelf();
2430 		fControls->MoveTo(fVideoView->Bounds().left,
2431 			fVideoView->Bounds().bottom + 1);
2432 		fVideoView->AddChild(fControls);
2433 		if (fScaleFullscreenControls)
2434 			fControls->SetSymbolScale(1.5f);
2435 
2436 		while (fControls->IsHidden())
2437 			fControls->Show();
2438 	}
2439 
2440 	if (animate) {
2441 		// Slide the controls into view. We need to do this with
2442 		// messages, otherwise we block the video playback for the
2443 		// time of the animation.
2444 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
2445 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
2446 		float height = fControls->Bounds().Height();
2447 		float moveDist = show ? -height : height;
2448 		float originalY = fControls->Frame().top;
2449 		for (int32 i = 0; i < steps; i++) {
2450 			BMessage message(M_SLIDE_CONTROLS);
2451 			message.AddFloat("offset",
2452 				floorf(moveDist * kAnimationOffsets[i]));
2453 			PostMessage(&message, this);
2454 		}
2455 		BMessage finalMessage(M_FINISH_SLIDING_CONTROLS);
2456 		finalMessage.AddFloat("offset", originalY + moveDist);
2457 		finalMessage.AddBool("show", show);
2458 		PostMessage(&finalMessage, this);
2459 	} else if (!show) {
2460 		fControls->RemoveSelf();
2461 		fControls->MoveTo(fVideoView->Frame().left,
2462 			fVideoView->Frame().bottom + 1);
2463 		fBackground->AddChild(fControls);
2464 		fControls->SetSymbolScale(1.0f);
2465 
2466 		while (!fControls->IsHidden())
2467 			fControls->Hide();
2468 	}
2469 }
2470 
2471 
2472 // #pragma mark -
2473 
2474 
2475 void
2476 MainWin::_Wind(bigtime_t howMuch, int64 frames)
2477 {
2478 	if (!fAllowWinding || !fController->Lock())
2479 		return;
2480 
2481 	if (frames != 0 && fHasVideo && !fController->IsPlaying()) {
2482 		int64 newFrame = fController->CurrentFrame() + frames;
2483 		fController->SetFramePosition(newFrame);
2484 	} else {
2485 		bigtime_t seekTime = fController->TimePosition() + howMuch;
2486 		if (seekTime < 0) {
2487 			fInitialSeekPosition = seekTime;
2488 			PostMessage(M_SKIP_PREV);
2489 		} else if (seekTime > fController->TimeDuration()) {
2490 			fInitialSeekPosition = 0;
2491 			PostMessage(M_SKIP_NEXT);
2492 		} else
2493 			fController->SetTimePosition(seekTime);
2494 	}
2495 
2496 	fController->Unlock();
2497 	fAllowWinding = false;
2498 }
2499 
2500 
2501 // #pragma mark -
2502 
2503 
2504 void
2505 MainWin::_UpdatePlaylistItemFile()
2506 {
2507 	BAutolock locker(fPlaylist);
2508 	const FilePlaylistItem* item
2509 		= dynamic_cast<const FilePlaylistItem*>(fController->Item());
2510 	if (item == NULL)
2511 		return;
2512 
2513 	if (!fHasVideo && !fHasAudio)
2514 		return;
2515 
2516 	BNode node(&item->Ref());
2517 	if (node.InitCheck())
2518 		return;
2519 
2520 	locker.Unlock();
2521 
2522 	// Set some standard attributes of the currently played file.
2523 	// This should only be a temporary solution.
2524 
2525 	// Write duration
2526 	const char* kDurationAttrName = "Media:Length";
2527 	attr_info info;
2528 	status_t status = node.GetAttrInfo(kDurationAttrName, &info);
2529 	if (status != B_OK || info.size == 0) {
2530 		bigtime_t duration = fController->TimeDuration();
2531 		// TODO: Tracker does not seem to care about endian for scalar types
2532 		node.WriteAttr(kDurationAttrName, B_INT64_TYPE, 0, &duration,
2533 			sizeof(int64));
2534 	}
2535 
2536 	// Write audio bitrate
2537 	if (fHasAudio) {
2538 		status = node.GetAttrInfo("Audio:Bitrate", &info);
2539 		if (status != B_OK || info.size == 0) {
2540 			media_format format;
2541 			if (fController->GetEncodedAudioFormat(&format) == B_OK
2542 				&& format.type == B_MEDIA_ENCODED_AUDIO) {
2543 				int32 bitrate = (int32)(format.u.encoded_audio.bit_rate
2544 					/ 1000);
2545 				char text[256];
2546 				snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2547 				node.WriteAttr("Audio:Bitrate", B_STRING_TYPE, 0, text,
2548 					strlen(text) + 1);
2549 			}
2550 		}
2551 	}
2552 
2553 	// Write video bitrate
2554 	if (fHasVideo) {
2555 		status = node.GetAttrInfo("Video:Bitrate", &info);
2556 		if (status != B_OK || info.size == 0) {
2557 			media_format format;
2558 			if (fController->GetEncodedVideoFormat(&format) == B_OK
2559 				&& format.type == B_MEDIA_ENCODED_VIDEO) {
2560 				int32 bitrate = (int32)(format.u.encoded_video.avg_bit_rate
2561 					/ 1000);
2562 				char text[256];
2563 				snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2564 				node.WriteAttr("Video:Bitrate", B_STRING_TYPE, 0, text,
2565 					strlen(text) + 1);
2566 			}
2567 		}
2568 	}
2569 
2570 	_UpdateAttributesMenu(node);
2571 }
2572 
2573 
2574 void
2575 MainWin::_UpdateAttributesMenu(const BNode& node)
2576 {
2577 	int32 rating = -1;
2578 
2579 	attr_info info;
2580 	status_t status = node.GetAttrInfo(kRatingAttrName, &info);
2581 	if (status == B_OK && info.type == B_INT32_TYPE) {
2582 		// Node has the Rating attribute.
2583 		node.ReadAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating,
2584 			sizeof(rating));
2585 	}
2586 
2587 	for (int32 i = 0; BMenuItem* item = fRatingMenu->ItemAt(i); i++)
2588 		item->SetMarked(i + 1 == rating);
2589 }
2590 
2591 
2592 void
2593 MainWin::_SetRating(int32 rating)
2594 {
2595 	BAutolock locker(fPlaylist);
2596 	const FilePlaylistItem* item
2597 		= dynamic_cast<const FilePlaylistItem*>(fController->Item());
2598 	if (item == NULL)
2599 		return;
2600 
2601 	BNode node(&item->Ref());
2602 	if (node.InitCheck())
2603 		return;
2604 
2605 	locker.Unlock();
2606 
2607 	node.WriteAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating, sizeof(rating));
2608 
2609 	// TODO: The whole mechnism should work like this:
2610 	// * There is already an attribute API for PlaylistItem, flesh it out!
2611 	// * FilePlaylistItem node-monitors it's file somehow.
2612 	// * FilePlaylistItem keeps attributes in sync and sends notications.
2613 	// * MainWin updates the menu according to FilePlaylistItem notifications.
2614 	// * PlaylistWin shows columns with attribute and other info.
2615 	// * PlaylistWin updates also upon FilePlaylistItem notifications.
2616 	// * This keeps attributes in sync when another app changes them.
2617 
2618 	_UpdateAttributesMenu(node);
2619 }
2620 
2621 
2622 void
2623 MainWin::_UpdateControlsEnabledStatus()
2624 {
2625 	uint32 enabledButtons = 0;
2626 	if (fHasVideo || fHasAudio) {
2627 		enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED
2628 			| SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED;
2629 	}
2630 	if (fHasAudio)
2631 		enabledButtons |= VOLUME_ENABLED;
2632 
2633 	BAutolock _(fPlaylist);
2634 	bool canSkipPrevious, canSkipNext;
2635 	fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext);
2636 	if (canSkipPrevious)
2637 		enabledButtons |= SKIP_BACK_ENABLED;
2638 	if (canSkipNext)
2639 		enabledButtons |= SKIP_FORWARD_ENABLED;
2640 
2641 	fControls->SetEnabled(enabledButtons);
2642 
2643 	fNoInterfaceMenuItem->SetEnabled(fHasVideo);
2644 	fAttributesMenu->SetEnabled(fHasAudio || fHasVideo);
2645 }
2646 
2647 
2648 void
2649 MainWin::_UpdatePlaylistMenu()
2650 {
2651 	if (!fPlaylist->Lock())
2652 		return;
2653 
2654 	fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true);
2655 
2656 	int32 count = fPlaylist->CountItems();
2657 	for (int32 i = 0; i < count; i++) {
2658 		PlaylistItem* item = fPlaylist->ItemAtFast(i);
2659 		_AddPlaylistItem(item, i);
2660 	}
2661 	fPlaylistMenu->SetTargetForItems(this);
2662 
2663 	_MarkPlaylistItem(fPlaylist->CurrentItemIndex());
2664 
2665 	fPlaylist->Unlock();
2666 }
2667 
2668 
2669 void
2670 MainWin::_AddPlaylistItem(PlaylistItem* item, int32 index)
2671 {
2672 	BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION);
2673 	message->AddInt32("index", index);
2674 	BMenuItem* menuItem = new BMenuItem(item->Name().String(), message);
2675 	fPlaylistMenu->AddItem(menuItem, index);
2676 }
2677 
2678 
2679 void
2680 MainWin::_RemovePlaylistItem(int32 index)
2681 {
2682 	delete fPlaylistMenu->RemoveItem(index);
2683 }
2684 
2685 
2686 void
2687 MainWin::_MarkPlaylistItem(int32 index)
2688 {
2689 	if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) {
2690 		item->SetMarked(true);
2691 		// ... and in case the menu is currently on screen:
2692 		if (fPlaylistMenu->LockLooper()) {
2693 			fPlaylistMenu->Invalidate();
2694 			fPlaylistMenu->UnlockLooper();
2695 		}
2696 	}
2697 }
2698 
2699 
2700 void
2701 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark)
2702 {
2703 	if (BMenuItem* item = menu->FindItem(command))
2704 		item->SetMarked(mark);
2705 }
2706 
2707 
2708 void
2709 MainWin::_AdoptGlobalSettings()
2710 {
2711 	mpSettings settings;
2712 	Settings::Default()->Get(settings);
2713 
2714 	fCloseWhenDonePlayingMovie = settings.closeWhenDonePlayingMovie;
2715 	fCloseWhenDonePlayingSound = settings.closeWhenDonePlayingSound;
2716 	fLoopMovies = settings.loopMovie;
2717 	fLoopSounds = settings.loopSound;
2718 	fScaleFullscreenControls = settings.scaleFullscreenControls;
2719 }
2720 
2721 
2722