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