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