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