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