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