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