xref: /haiku/src/apps/mediaplayer/MainWin.cpp (revision 4d8811742fa447ec05b4993a16a0931bc29aafab)
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 			// We don't use BWindow::MoveOnScreen() in order to resize the
1876 			// window while keeping the same aspect ratio.
1877 			if (frame.Width() > screenFrame.Width()
1878 				|| frame.Height() > screenFrame.Height()) {
1879 				// too large
1880 				int widthDiff
1881 					= frame.IntegerWidth() - screenFrame.IntegerWidth();
1882 				int heightDiff
1883 					= frame.IntegerHeight() - screenFrame.IntegerHeight();
1884 
1885 				float shrinkScale;
1886 				if (widthDiff > heightDiff)
1887 					shrinkScale = (float)(width - widthDiff) / width;
1888 				else
1889 					shrinkScale = (float)(height - heightDiff) / height;
1890 
1891 				// Resize width/height and center window
1892 				width = lround(width * shrinkScale);
1893 				height = lround(height * shrinkScale);
1894 				MoveTo((screenFrame.left + screenFrame.right - width) / 2,
1895 					(screenFrame.top + screenFrame.bottom - height) / 2);
1896 			} else {
1897 				// just off-screen on one or more sides
1898 				int offsetX = 0;
1899 				int offsetY = 0;
1900 				if (frame.left < screenFrame.left)
1901 					offsetX = (int)(screenFrame.left - frame.left);
1902 				else if (frame.right > screenFrame.right)
1903 					offsetX = (int)(screenFrame.right - frame.right);
1904 				if (frame.top < screenFrame.top)
1905 					offsetY = (int)(screenFrame.top - frame.top);
1906 				else if (frame.bottom > screenFrame.bottom)
1907 					offsetY = (int)(screenFrame.bottom - frame.bottom);
1908 				MoveBy(offsetX, offsetY);
1909 			}
1910 		}
1911 	}
1912 
1913 	ResizeTo(width, height);
1914 }
1915 
1916 
1917 void
1918 MainWin::_ResizeVideoView(int x, int y, int width, int height)
1919 {
1920 	// Keep aspect ratio, place video view inside
1921 	// the background area (may create black bars).
1922 	int videoWidth;
1923 	int videoHeight;
1924 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1925 	float scaledWidth  = videoWidth;
1926 	float scaledHeight = videoHeight;
1927 	float factor = min_c(width / scaledWidth, height / scaledHeight);
1928 	int renderWidth = lround(scaledWidth * factor);
1929 	int renderHeight = lround(scaledHeight * factor);
1930 	if (renderWidth > width)
1931 		renderWidth = width;
1932 	if (renderHeight > height)
1933 		renderHeight = height;
1934 
1935 	int xOffset = (width - renderWidth) / 2;
1936 	int yOffset = (height - renderHeight) / 2;
1937 
1938 	fVideoView->MoveTo(x, y);
1939 	fVideoView->ResizeTo(width - 1, height - 1);
1940 
1941 	BRect videoFrame(xOffset, yOffset,
1942 		xOffset + renderWidth - 1, yOffset + renderHeight - 1);
1943 
1944 	fVideoView->SetVideoFrame(videoFrame);
1945 	fVideoView->SetSubTitleMaxBottom(height - 1);
1946 }
1947 
1948 
1949 // #pragma mark -
1950 
1951 
1952 void
1953 MainWin::_MouseDown(BMessage* msg, BView* originalHandler)
1954 {
1955 	uint32 buttons = msg->FindInt32("buttons");
1956 
1957 	// On Zeta, only "screen_where" is reliable, "where" and "be:view_where"
1958 	// seem to be broken
1959 	BPoint screenWhere;
1960 	if (msg->FindPoint("screen_where", &screenWhere) != B_OK) {
1961 		// TODO: remove
1962 		// Workaround for BeOS R5, it has no "screen_where"
1963 		if (!originalHandler || msg->FindPoint("where", &screenWhere) < B_OK)
1964 			return;
1965 		originalHandler->ConvertToScreen(&screenWhere);
1966 	}
1967 
1968 	// double click handling
1969 
1970 	if (msg->FindInt32("clicks") % 2 == 0) {
1971 		BRect rect(screenWhere.x - 1, screenWhere.y - 1, screenWhere.x + 1,
1972 			screenWhere.y + 1);
1973 		if (rect.Contains(fMouseDownMousePos)) {
1974 			if (buttons == B_PRIMARY_MOUSE_BUTTON)
1975 				PostMessage(M_TOGGLE_FULLSCREEN);
1976 			else if (buttons == B_SECONDARY_MOUSE_BUTTON)
1977 				PostMessage(M_TOGGLE_NO_INTERFACE);
1978 
1979 			return;
1980 		}
1981 	}
1982 
1983 	fMouseDownMousePos = screenWhere;
1984 	fMouseDownWindowPos = Frame().LeftTop();
1985 
1986 	if (buttons == B_PRIMARY_MOUSE_BUTTON && !fIsFullscreen) {
1987 		// start mouse tracking
1988 		fVideoView->SetMouseEventMask(B_POINTER_EVENTS | B_NO_POINTER_HISTORY
1989 			/* | B_LOCK_WINDOW_FOCUS */);
1990 		fMouseDownTracking = true;
1991 	}
1992 
1993 	// pop up a context menu if right mouse button is down
1994 
1995 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
1996 		_ShowContextMenu(screenWhere);
1997 }
1998 
1999 
2000 void
2001 MainWin::_MouseMoved(BMessage* msg, BView* originalHandler)
2002 {
2003 //	msg->PrintToStream();
2004 
2005 	BPoint mousePos;
2006 	uint32 buttons = msg->FindInt32("buttons");
2007 	// On Zeta, only "screen_where" is reliable, "where"
2008 	// and "be:view_where" seem to be broken
2009 	if (msg->FindPoint("screen_where", &mousePos) != B_OK) {
2010 		// TODO: remove
2011 		// Workaround for BeOS R5, it has no "screen_where"
2012 		if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK)
2013 			return;
2014 		originalHandler->ConvertToScreen(&mousePos);
2015 	}
2016 
2017 	if (buttons == B_PRIMARY_MOUSE_BUTTON && fMouseDownTracking
2018 		&& !fIsFullscreen) {
2019 //		printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y);
2020 		float delta_x = mousePos.x - fMouseDownMousePos.x;
2021 		float delta_y = mousePos.y - fMouseDownMousePos.y;
2022 		float x = fMouseDownWindowPos.x + delta_x;
2023 		float y = fMouseDownWindowPos.y + delta_y;
2024 //		printf("move window to %.0f, %.0f\n", x, y);
2025 		MoveTo(x, y);
2026 	}
2027 
2028 	bigtime_t eventTime;
2029 	if (msg->FindInt64("when", &eventTime) != B_OK)
2030 		eventTime = system_time();
2031 
2032 	if (buttons == 0 && fIsFullscreen) {
2033 		BPoint moveDelta = mousePos - fLastMousePos;
2034 		float moveDeltaDist
2035 			= sqrtf(moveDelta.x * moveDelta.x + moveDelta.y * moveDelta.y);
2036 		if (eventTime - fLastMouseMovedTime < 200000)
2037 			fMouseMoveDist += moveDeltaDist;
2038 		else
2039 			fMouseMoveDist = moveDeltaDist;
2040 		if (fMouseMoveDist > 5)
2041 			_ShowFullscreenControls(true);
2042 	}
2043 
2044 	fLastMousePos = mousePos;
2045 	fLastMouseMovedTime =eventTime;
2046 }
2047 
2048 
2049 void
2050 MainWin::_MouseUp(BMessage* msg)
2051 {
2052 	fMouseDownTracking = false;
2053 }
2054 
2055 
2056 void
2057 MainWin::_ShowContextMenu(const BPoint& screenPoint)
2058 {
2059 	printf("Show context menu\n");
2060 	BPopUpMenu* menu = new BPopUpMenu("context menu", false, false);
2061 	BMenuItem* item;
2062 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Full screen"),
2063 		new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
2064 	item->SetMarked(fIsFullscreen);
2065 	item->SetEnabled(fHasVideo);
2066 
2067 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Hide interface"),
2068 		new BMessage(M_TOGGLE_NO_INTERFACE), 'H'));
2069 	item->SetMarked(fNoInterface);
2070 	item->SetEnabled(fHasVideo && !fIsFullscreen);
2071 
2072 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"),
2073 		new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
2074 	item->SetMarked(fAlwaysOnTop);
2075 	item->SetEnabled(fHasVideo);
2076 
2077 	BMenu* aspectSubMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
2078 	_SetupVideoAspectItems(aspectSubMenu);
2079 	aspectSubMenu->SetTargetForItems(this);
2080 	menu->AddItem(item = new BMenuItem(aspectSubMenu));
2081 	item->SetEnabled(fHasVideo);
2082 
2083 	menu->AddSeparatorItem();
2084 
2085 	// Add track selector menus
2086 	BMenu* audioTrackMenu = new BMenu(B_TRANSLATE("Audio track"));
2087 	BMenu* videoTrackMenu = new BMenu(B_TRANSLATE("Video track"));
2088 	BMenu* subTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
2089 	_SetupTrackMenus(audioTrackMenu, videoTrackMenu, subTitleTrackMenu);
2090 
2091 	audioTrackMenu->SetTargetForItems(this);
2092 	videoTrackMenu->SetTargetForItems(this);
2093 	subTitleTrackMenu->SetTargetForItems(this);
2094 
2095 	menu->AddItem(item = new BMenuItem(audioTrackMenu));
2096 	item->SetEnabled(fHasAudio);
2097 
2098 	menu->AddItem(item = new BMenuItem(videoTrackMenu));
2099 	item->SetEnabled(fHasVideo);
2100 
2101 	menu->AddItem(item = new BMenuItem(subTitleTrackMenu));
2102 	item->SetEnabled(fHasVideo);
2103 
2104 	menu->AddSeparatorItem();
2105 	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), new BMessage(M_FILE_QUIT), 'Q'));
2106 
2107 	menu->SetTargetForItems(this);
2108 	BRect rect(screenPoint.x - 5, screenPoint.y - 5, screenPoint.x + 5,
2109 		screenPoint.y + 5);
2110 	menu->Go(screenPoint, true, true, rect, true);
2111 }
2112 
2113 
2114 /*!	Trap keys that are about to be send to background or renderer view.
2115 	Return true if it shouldn't be passed to the view.
2116 */
2117 bool
2118 MainWin::_KeyDown(BMessage* msg)
2119 {
2120 	uint32 key = msg->FindInt32("key");
2121 	uint32 rawChar = msg->FindInt32("raw_char");
2122 	uint32 modifier = msg->FindInt32("modifiers");
2123 
2124 //	printf("key 0x%lx, rawChar 0x%lx, modifiers 0x%lx\n", key, rawChar,
2125 //		modifier);
2126 
2127 	// ignore the system modifier namespace
2128 	if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
2129 			== (B_CONTROL_KEY | B_COMMAND_KEY))
2130 		return false;
2131 
2132 	switch (rawChar) {
2133 		case B_SPACE:
2134 			fController->TogglePlaying();
2135 			return true;
2136 
2137 		case 'm':
2138 			fController->ToggleMute();
2139 			return true;
2140 
2141 		case B_ESCAPE:
2142 			if (!fIsFullscreen)
2143 				break;
2144 
2145 			PostMessage(M_TOGGLE_FULLSCREEN);
2146 			return true;
2147 
2148 		case B_ENTER:		// Enter / Return
2149 			if ((modifier & B_COMMAND_KEY) != 0) {
2150 				PostMessage(M_TOGGLE_FULLSCREEN);
2151 				return true;
2152 			}
2153 			break;
2154 
2155 		case B_TAB:
2156 		case 'f':
2157 			if ((modifier & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
2158 					| B_MENU_KEY)) == 0) {
2159 				PostMessage(M_TOGGLE_FULLSCREEN);
2160 				return true;
2161 			}
2162 			break;
2163 
2164 		case B_UP_ARROW:
2165 			if ((modifier & B_COMMAND_KEY) != 0)
2166 				PostMessage(M_SKIP_NEXT);
2167 			else
2168 				PostMessage(M_VOLUME_UP);
2169 			return true;
2170 
2171 		case B_DOWN_ARROW:
2172 			if ((modifier & B_COMMAND_KEY) != 0)
2173 				PostMessage(M_SKIP_PREV);
2174 			else
2175 				PostMessage(M_VOLUME_DOWN);
2176 			return true;
2177 
2178 		case B_RIGHT_ARROW:
2179 			if ((modifier & B_COMMAND_KEY) != 0)
2180 				PostMessage(M_SKIP_NEXT);
2181 			else if (fAllowWinding) {
2182 				BMessage windMessage(M_WIND);
2183 				if ((modifier & B_SHIFT_KEY) != 0) {
2184 					windMessage.AddInt64("how much", 30000000LL);
2185 					windMessage.AddInt64("frames", 5);
2186 				} else {
2187 					windMessage.AddInt64("how much", 5000000LL);
2188 					windMessage.AddInt64("frames", 1);
2189 				}
2190 				PostMessage(&windMessage);
2191 			}
2192 			return true;
2193 
2194 		case B_LEFT_ARROW:
2195 			if ((modifier & B_COMMAND_KEY) != 0)
2196 				PostMessage(M_SKIP_PREV);
2197 			else if (fAllowWinding) {
2198 				BMessage windMessage(M_WIND);
2199 				if ((modifier & B_SHIFT_KEY) != 0) {
2200 					windMessage.AddInt64("how much", -30000000LL);
2201 					windMessage.AddInt64("frames", -5);
2202 				} else {
2203 					windMessage.AddInt64("how much", -5000000LL);
2204 					windMessage.AddInt64("frames", -1);
2205 				}
2206 				PostMessage(&windMessage);
2207 			}
2208 			return true;
2209 
2210 		case B_PAGE_UP:
2211 			PostMessage(M_SKIP_NEXT);
2212 			return true;
2213 
2214 		case B_PAGE_DOWN:
2215 			PostMessage(M_SKIP_PREV);
2216 			return true;
2217 
2218 		case '+':
2219 			if ((modifier & B_COMMAND_KEY) == 0) {
2220 				_ZoomVideoView(10);
2221 				return true;
2222 			}
2223 			break;
2224 
2225 		case '-':
2226 			if ((modifier & B_COMMAND_KEY) == 0) {
2227 				_ZoomVideoView(-10);
2228 				return true;
2229 			}
2230 			break;
2231 
2232 		case B_DELETE:
2233 		case 'd': 			// d for delete
2234 		case 't':			// t for Trash
2235 			if ((modifiers() & B_COMMAND_KEY) != 0) {
2236 				BAutolock _(fPlaylist);
2237 				BMessage removeMessage(M_PLAYLIST_MOVE_TO_TRASH);
2238 				removeMessage.AddInt32("playlist index",
2239 					fPlaylist->CurrentItemIndex());
2240 				fPlaylistWindow->PostMessage(&removeMessage);
2241 				return true;
2242 			}
2243 			break;
2244 	}
2245 
2246 	switch (key) {
2247 		case 0x3a:  		// numeric keypad +
2248 			if ((modifier & B_COMMAND_KEY) == 0) {
2249 				_ZoomVideoView(10);
2250 				return true;
2251 			}
2252 			break;
2253 
2254 		case 0x25:  		// numeric keypad -
2255 			if ((modifier & B_COMMAND_KEY) == 0) {
2256 				_ZoomVideoView(-10);
2257 				return true;
2258 			}
2259 			break;
2260 
2261 		case 0x38:			// numeric keypad up arrow
2262 			PostMessage(M_VOLUME_UP);
2263 			return true;
2264 
2265 		case 0x59:			// numeric keypad down arrow
2266 			PostMessage(M_VOLUME_DOWN);
2267 			return true;
2268 
2269 		case 0x39:			// numeric keypad page up
2270 		case 0x4a:			// numeric keypad right arrow
2271 			PostMessage(M_SKIP_NEXT);
2272 			return true;
2273 
2274 		case 0x5a:			// numeric keypad page down
2275 		case 0x48:			// numeric keypad left arrow
2276 			PostMessage(M_SKIP_PREV);
2277 			return true;
2278 
2279 		// Playback controls along the bottom of the keyboard:
2280 		// Z X C (V) B  for US International
2281 		case 0x4c:
2282 			PostMessage(M_SKIP_PREV);
2283 			return true;
2284 		case 0x4d:
2285 			fController->TogglePlaying();
2286 			return true;
2287 		case 0x4e:
2288 			fController->Pause();
2289 			return true;
2290 		case 0x4f:
2291 			fController->Stop();
2292 			return true;
2293 		case 0x50:
2294 			PostMessage(M_SKIP_NEXT);
2295 			return true;
2296 	}
2297 
2298 	return false;
2299 }
2300 
2301 
2302 // #pragma mark -
2303 
2304 
2305 void
2306 MainWin::_ToggleFullscreen()
2307 {
2308 	printf("_ToggleFullscreen enter\n");
2309 
2310 	if (!fHasVideo) {
2311 		printf("_ToggleFullscreen - ignoring, as we don't have a video\n");
2312 		return;
2313 	}
2314 
2315 	fIsFullscreen = !fIsFullscreen;
2316 
2317 	if (fIsFullscreen) {
2318 		// switch to fullscreen
2319 
2320 		fSavedFrame = Frame();
2321 		printf("saving current frame: %d %d %d %d\n", int(fSavedFrame.left),
2322 			int(fSavedFrame.top), int(fSavedFrame.right),
2323 			int(fSavedFrame.bottom));
2324 		BScreen screen(this);
2325 		BRect rect(screen.Frame());
2326 
2327 		Hide();
2328 		MoveTo(rect.left, rect.top);
2329 		ResizeTo(rect.Width(), rect.Height());
2330 		Show();
2331 
2332 	} else {
2333 		// switch back from full screen mode
2334 		_ShowFullscreenControls(false, false);
2335 
2336 		Hide();
2337 		MoveTo(fSavedFrame.left, fSavedFrame.top);
2338 		ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
2339 		Show();
2340 	}
2341 
2342 	fVideoView->SetFullscreen(fIsFullscreen);
2343 
2344 	_MarkItem(fFileMenu, M_TOGGLE_FULLSCREEN, fIsFullscreen);
2345 
2346 	printf("_ToggleFullscreen leave\n");
2347 }
2348 
2349 void
2350 MainWin::_ToggleAlwaysOnTop()
2351 {
2352 	fAlwaysOnTop = !fAlwaysOnTop;
2353 	SetFeel(fAlwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL);
2354 
2355 	_MarkItem(fFileMenu, M_TOGGLE_ALWAYS_ON_TOP, fAlwaysOnTop);
2356 }
2357 
2358 
2359 void
2360 MainWin::_ToggleNoInterface()
2361 {
2362 	printf("_ToggleNoInterface enter\n");
2363 
2364 	if (fIsFullscreen || !fHasVideo) {
2365 		// Fullscreen playback is always without interface and
2366 		// audio playback is always with interface. So we ignore these
2367 		// two states here.
2368 		printf("_ToggleNoControls leave, doing nothing, we are fullscreen\n");
2369 		return;
2370 	}
2371 
2372 	fNoInterface = !fNoInterface;
2373 	_SetWindowSizeLimits();
2374 
2375 	if (fNoInterface) {
2376 		MoveBy(0, fMenuBarHeight);
2377 		ResizeBy(0, -(fControlsHeight + fMenuBarHeight));
2378 		SetLook(B_BORDERED_WINDOW_LOOK);
2379 	} else {
2380 		MoveBy(0, -fMenuBarHeight);
2381 		ResizeBy(0, fControlsHeight + fMenuBarHeight);
2382 		SetLook(B_TITLED_WINDOW_LOOK);
2383 	}
2384 
2385 	_MarkItem(fFileMenu, M_TOGGLE_NO_INTERFACE, fNoInterface);
2386 
2387 	printf("_ToggleNoInterface leave\n");
2388 }
2389 
2390 
2391 void
2392 MainWin::_ShowIfNeeded()
2393 {
2394 	// Only proceed if the window is already running
2395 	if (find_thread(NULL) != Thread())
2396 		return;
2397 
2398 	if (!fHasVideo && fNoVideoFrame.IsValid()) {
2399 		MoveTo(fNoVideoFrame.LeftTop());
2400 		ResizeTo(fNoVideoFrame.Width(), fNoVideoFrame.Height());
2401 		MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN);
2402 	} else if (fHasVideo && IsHidden())
2403 		CenterOnScreen();
2404 
2405 	fNoVideoFrame = BRect();
2406 
2407 	if (IsHidden()) {
2408 		Show();
2409 		UpdateIfNeeded();
2410 	}
2411 }
2412 
2413 
2414 void
2415 MainWin::_ShowFullscreenControls(bool show, bool animate)
2416 {
2417 	if (fShowsFullscreenControls == show)
2418 		return;
2419 
2420 	fShowsFullscreenControls = show;
2421 	fVideoView->SetFullscreenControlsVisible(show);
2422 
2423 	if (show) {
2424 		fControls->RemoveSelf();
2425 		fControls->MoveTo(fVideoView->Bounds().left,
2426 			fVideoView->Bounds().bottom + 1);
2427 		fVideoView->AddChild(fControls);
2428 		if (fScaleFullscreenControls)
2429 			fControls->SetSymbolScale(1.5f);
2430 
2431 		while (fControls->IsHidden())
2432 			fControls->Show();
2433 	}
2434 
2435 	if (animate) {
2436 		// Slide the controls into view. We need to do this with
2437 		// messages, otherwise we block the video playback for the
2438 		// time of the animation.
2439 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
2440 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
2441 		float height = fControls->Bounds().Height();
2442 		float moveDist = show ? -height : height;
2443 		float originalY = fControls->Frame().top;
2444 		for (int32 i = 0; i < steps; i++) {
2445 			BMessage message(M_SLIDE_CONTROLS);
2446 			message.AddFloat("offset",
2447 				floorf(moveDist * kAnimationOffsets[i]));
2448 			PostMessage(&message, this);
2449 		}
2450 		BMessage finalMessage(M_FINISH_SLIDING_CONTROLS);
2451 		finalMessage.AddFloat("offset", originalY + moveDist);
2452 		finalMessage.AddBool("show", show);
2453 		PostMessage(&finalMessage, this);
2454 	} else if (!show) {
2455 		fControls->RemoveSelf();
2456 		fControls->MoveTo(fVideoView->Frame().left,
2457 			fVideoView->Frame().bottom + 1);
2458 		fBackground->AddChild(fControls);
2459 		fControls->SetSymbolScale(1.0f);
2460 
2461 		while (!fControls->IsHidden())
2462 			fControls->Hide();
2463 	}
2464 }
2465 
2466 
2467 // #pragma mark -
2468 
2469 
2470 void
2471 MainWin::_Wind(bigtime_t howMuch, int64 frames)
2472 {
2473 	if (!fAllowWinding || !fController->Lock())
2474 		return;
2475 
2476 	if (frames != 0 && fHasVideo && !fController->IsPlaying()) {
2477 		int64 newFrame = fController->CurrentFrame() + frames;
2478 		fController->SetFramePosition(newFrame);
2479 	} else {
2480 		bigtime_t seekTime = fController->TimePosition() + howMuch;
2481 		if (seekTime < 0) {
2482 			fInitialSeekPosition = seekTime;
2483 			PostMessage(M_SKIP_PREV);
2484 		} else if (seekTime > fController->TimeDuration()) {
2485 			fInitialSeekPosition = 0;
2486 			PostMessage(M_SKIP_NEXT);
2487 		} else
2488 			fController->SetTimePosition(seekTime);
2489 	}
2490 
2491 	fController->Unlock();
2492 	fAllowWinding = false;
2493 }
2494 
2495 
2496 // #pragma mark -
2497 
2498 
2499 void
2500 MainWin::_UpdatePlaylistItemFile()
2501 {
2502 	BAutolock locker(fPlaylist);
2503 	const FilePlaylistItem* item
2504 		= dynamic_cast<const FilePlaylistItem*>(fController->Item());
2505 	if (item == NULL)
2506 		return;
2507 
2508 	if (!fHasVideo && !fHasAudio)
2509 		return;
2510 
2511 	BNode node(&item->Ref());
2512 	if (node.InitCheck())
2513 		return;
2514 
2515 	locker.Unlock();
2516 
2517 	// Set some standard attributes of the currently played file.
2518 	// This should only be a temporary solution.
2519 
2520 	// Write duration
2521 	const char* kDurationAttrName = "Media:Length";
2522 	attr_info info;
2523 	status_t status = node.GetAttrInfo(kDurationAttrName, &info);
2524 	if (status != B_OK || info.size == 0) {
2525 		bigtime_t duration = fController->TimeDuration();
2526 		// TODO: Tracker does not seem to care about endian for scalar types
2527 		node.WriteAttr(kDurationAttrName, B_INT64_TYPE, 0, &duration,
2528 			sizeof(int64));
2529 	}
2530 
2531 	// Write audio bitrate
2532 	if (fHasAudio) {
2533 		status = node.GetAttrInfo("Audio:Bitrate", &info);
2534 		if (status != B_OK || info.size == 0) {
2535 			media_format format;
2536 			if (fController->GetEncodedAudioFormat(&format) == B_OK
2537 				&& format.type == B_MEDIA_ENCODED_AUDIO) {
2538 				int32 bitrate = (int32)(format.u.encoded_audio.bit_rate
2539 					/ 1000);
2540 				char text[256];
2541 				snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2542 				node.WriteAttr("Audio:Bitrate", B_STRING_TYPE, 0, text,
2543 					strlen(text) + 1);
2544 			}
2545 		}
2546 	}
2547 
2548 	// Write video bitrate
2549 	if (fHasVideo) {
2550 		status = node.GetAttrInfo("Video:Bitrate", &info);
2551 		if (status != B_OK || info.size == 0) {
2552 			media_format format;
2553 			if (fController->GetEncodedVideoFormat(&format) == B_OK
2554 				&& format.type == B_MEDIA_ENCODED_VIDEO) {
2555 				int32 bitrate = (int32)(format.u.encoded_video.avg_bit_rate
2556 					/ 1000);
2557 				char text[256];
2558 				snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2559 				node.WriteAttr("Video:Bitrate", B_STRING_TYPE, 0, text,
2560 					strlen(text) + 1);
2561 			}
2562 		}
2563 	}
2564 
2565 	_UpdateAttributesMenu(node);
2566 }
2567 
2568 
2569 void
2570 MainWin::_UpdateAttributesMenu(const BNode& node)
2571 {
2572 	int32 rating = -1;
2573 
2574 	attr_info info;
2575 	status_t status = node.GetAttrInfo(kRatingAttrName, &info);
2576 	if (status == B_OK && info.type == B_INT32_TYPE) {
2577 		// Node has the Rating attribute.
2578 		node.ReadAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating,
2579 			sizeof(rating));
2580 	}
2581 
2582 	for (int32 i = 0; BMenuItem* item = fRatingMenu->ItemAt(i); i++)
2583 		item->SetMarked(i + 1 == rating);
2584 }
2585 
2586 
2587 void
2588 MainWin::_SetRating(int32 rating)
2589 {
2590 	BAutolock locker(fPlaylist);
2591 	const FilePlaylistItem* item
2592 		= dynamic_cast<const FilePlaylistItem*>(fController->Item());
2593 	if (item == NULL)
2594 		return;
2595 
2596 	BNode node(&item->Ref());
2597 	if (node.InitCheck())
2598 		return;
2599 
2600 	locker.Unlock();
2601 
2602 	node.WriteAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating, sizeof(rating));
2603 
2604 	// TODO: The whole mechnism should work like this:
2605 	// * There is already an attribute API for PlaylistItem, flesh it out!
2606 	// * FilePlaylistItem node-monitors it's file somehow.
2607 	// * FilePlaylistItem keeps attributes in sync and sends notications.
2608 	// * MainWin updates the menu according to FilePlaylistItem notifications.
2609 	// * PlaylistWin shows columns with attribute and other info.
2610 	// * PlaylistWin updates also upon FilePlaylistItem notifications.
2611 	// * This keeps attributes in sync when another app changes them.
2612 
2613 	_UpdateAttributesMenu(node);
2614 }
2615 
2616 
2617 void
2618 MainWin::_UpdateControlsEnabledStatus()
2619 {
2620 	uint32 enabledButtons = 0;
2621 	if (fHasVideo || fHasAudio) {
2622 		enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED
2623 			| SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED;
2624 	}
2625 	if (fHasAudio)
2626 		enabledButtons |= VOLUME_ENABLED;
2627 
2628 	BAutolock _(fPlaylist);
2629 	bool canSkipPrevious, canSkipNext;
2630 	fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext);
2631 	if (canSkipPrevious)
2632 		enabledButtons |= SKIP_BACK_ENABLED;
2633 	if (canSkipNext)
2634 		enabledButtons |= SKIP_FORWARD_ENABLED;
2635 
2636 	fControls->SetEnabled(enabledButtons);
2637 
2638 	fNoInterfaceMenuItem->SetEnabled(fHasVideo);
2639 	fAttributesMenu->SetEnabled(fHasAudio || fHasVideo);
2640 }
2641 
2642 
2643 void
2644 MainWin::_UpdatePlaylistMenu()
2645 {
2646 	if (!fPlaylist->Lock())
2647 		return;
2648 
2649 	fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true);
2650 
2651 	int32 count = fPlaylist->CountItems();
2652 	for (int32 i = 0; i < count; i++) {
2653 		PlaylistItem* item = fPlaylist->ItemAtFast(i);
2654 		_AddPlaylistItem(item, i);
2655 	}
2656 	fPlaylistMenu->SetTargetForItems(this);
2657 
2658 	_MarkPlaylistItem(fPlaylist->CurrentItemIndex());
2659 
2660 	fPlaylist->Unlock();
2661 }
2662 
2663 
2664 void
2665 MainWin::_AddPlaylistItem(PlaylistItem* item, int32 index)
2666 {
2667 	BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION);
2668 	message->AddInt32("index", index);
2669 	BMenuItem* menuItem = new BMenuItem(item->Name().String(), message);
2670 	fPlaylistMenu->AddItem(menuItem, index);
2671 }
2672 
2673 
2674 void
2675 MainWin::_RemovePlaylistItem(int32 index)
2676 {
2677 	delete fPlaylistMenu->RemoveItem(index);
2678 }
2679 
2680 
2681 void
2682 MainWin::_MarkPlaylistItem(int32 index)
2683 {
2684 	if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) {
2685 		item->SetMarked(true);
2686 		// ... and in case the menu is currently on screen:
2687 		if (fPlaylistMenu->LockLooper()) {
2688 			fPlaylistMenu->Invalidate();
2689 			fPlaylistMenu->UnlockLooper();
2690 		}
2691 	}
2692 }
2693 
2694 
2695 void
2696 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark)
2697 {
2698 	if (BMenuItem* item = menu->FindItem(command))
2699 		item->SetMarked(mark);
2700 }
2701 
2702 
2703 void
2704 MainWin::_AdoptGlobalSettings()
2705 {
2706 	mpSettings settings;
2707 	Settings::Default()->Get(settings);
2708 
2709 	fCloseWhenDonePlayingMovie = settings.closeWhenDonePlayingMovie;
2710 	fCloseWhenDonePlayingSound = settings.closeWhenDonePlayingSound;
2711 	fLoopMovies = settings.loopMovie;
2712 	fLoopSounds = settings.loopSound;
2713 	fScaleFullscreenControls = settings.scaleFullscreenControls;
2714 }
2715 
2716 
2717