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