xref: /haiku/src/apps/mediaplayer/MainWin.cpp (revision ebd866299d45ed59d8576e1a9634f924cc07aa6e)
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 			_RefsReceived(msg);
537 			break;
538 		case B_SIMPLE_DATA:
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 		case MSG_PLAYLIST_IMPORT_FAILED:
606 		{
607 			BAlert* alert = new BAlert("Nothing to Play", "None of the files "
608 				"you wanted to play appear to be media files.", "OK");
609 			alert->Go();
610 
611 			_ShowIfNeeded();
612 			break;
613 		}
614 
615 		// ControllerObserver messages
616 		case MSG_CONTROLLER_FILE_FINISHED:
617 		{
618 			BAutolock _(fPlaylist);
619 
620 			bool hadNext = fPlaylist->SetCurrentItemIndex(
621 				fPlaylist->CurrentItemIndex() + 1);
622 			if (!hadNext) {
623 				// Reached end of playlist
624 				// Handle "quit when done" settings
625 				if ((fHasVideo && fCloseWhenDonePlayingMovie)
626 					|| (!fHasVideo && fCloseWhenDonePlayingSound))
627 					PostMessage(B_QUIT_REQUESTED);
628 				// Handle "loop by default" settings
629 				if ((fHasVideo && fLoopMovies)
630 					|| (!fHasVideo && fLoopSounds)) {
631 					if (fPlaylist->CountItems() > 1)
632 						fPlaylist->SetCurrentItemIndex(0);
633 					else
634 						fController->Play();
635 				}
636 			}
637 			break;
638 		}
639 		case MSG_CONTROLLER_FILE_CHANGED:
640 		{
641 			status_t result = B_ERROR;
642 			msg->FindInt32("result", &result);
643 			PlaylistItemRef itemRef;
644 			PlaylistItem* item;
645 			if (msg->FindPointer("item", (void**)&item) == B_OK) {
646 				itemRef.SetTo(item, true);
647 					// The reference was passed along with the message.
648 			} else {
649 				BAutolock _(fPlaylist);
650 				itemRef.SetTo(fPlaylist->ItemAt(
651 					fPlaylist->CurrentItemIndex()));
652 			}
653 			_PlaylistItemOpened(itemRef, result);
654 			break;
655 		}
656 		case MSG_CONTROLLER_VIDEO_TRACK_CHANGED:
657 		{
658 			int32 index;
659 			if (msg->FindInt32("index", &index) == B_OK) {
660 				int32 i = 0;
661 				while (BMenuItem* item = fVideoTrackMenu->ItemAt(i)) {
662 					item->SetMarked(i == index);
663 					i++;
664 				}
665 			}
666 			break;
667 		}
668 		case MSG_CONTROLLER_AUDIO_TRACK_CHANGED:
669 		{
670 			int32 index;
671 			if (msg->FindInt32("index", &index) == B_OK) {
672 				int32 i = 0;
673 				while (BMenuItem* item = fAudioTrackMenu->ItemAt(i)) {
674 					item->SetMarked(i == index);
675 					i++;
676 				}
677 				_UpdateAudioChannelCount(index);
678 			}
679 			break;
680 		}
681 		case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED:
682 		{
683 			uint32 state;
684 			if (msg->FindInt32("state", (int32*)&state) == B_OK)
685 				fControls->SetPlaybackState(state);
686 			break;
687 		}
688 		case MSG_CONTROLLER_POSITION_CHANGED:
689 		{
690 			float position;
691 			if (msg->FindFloat("position", &position) == B_OK) {
692 				fControls->SetPosition(position, fController->TimePosition(),
693 					fController->TimeDuration());
694 				fAllowWinding = true;
695 			}
696 			break;
697 		}
698 		case MSG_CONTROLLER_SEEK_HANDLED:
699 			break;
700 
701 		case MSG_CONTROLLER_VOLUME_CHANGED:
702 		{
703 			float volume;
704 			if (msg->FindFloat("volume", &volume) == B_OK)
705 				fControls->SetVolume(volume);
706 			break;
707 		}
708 		case MSG_CONTROLLER_MUTED_CHANGED:
709 		{
710 			bool muted;
711 			if (msg->FindBool("muted", &muted) == B_OK)
712 				fControls->SetMuted(muted);
713 			break;
714 		}
715 
716 		// menu item messages
717 		case M_FILE_OPEN:
718 		{
719 			BMessenger target(this);
720 			BMessage result(B_REFS_RECEIVED);
721 			BMessage appMessage(M_SHOW_OPEN_PANEL);
722 			appMessage.AddMessenger("target", target);
723 			appMessage.AddMessage("message", &result);
724 			appMessage.AddString("title", "Open Clips");
725 			appMessage.AddString("label", "Open");
726 			be_app->PostMessage(&appMessage);
727 			break;
728 		}
729 		case M_FILE_INFO:
730 			ShowFileInfo();
731 			break;
732 		case M_FILE_PLAYLIST:
733 			ShowPlaylistWindow();
734 			break;
735 		case M_FILE_CLOSE:
736 			PostMessage(B_QUIT_REQUESTED);
737 			break;
738 		case M_FILE_QUIT:
739 			be_app->PostMessage(B_QUIT_REQUESTED);
740 			break;
741 
742 		case M_TOGGLE_FULLSCREEN:
743 			_ToggleFullscreen();
744 			break;
745 
746 		case M_TOGGLE_ALWAYS_ON_TOP:
747 			_ToggleAlwaysOnTop();
748 			break;
749 
750 		case M_TOGGLE_NO_INTERFACE:
751 			_ToggleNoInterface();
752 			break;
753 
754 		case M_VIEW_SIZE:
755 		{
756 			int32 size;
757 			if (msg->FindInt32("size", &size) == B_OK) {
758 				if (!fHasVideo)
759 					break;
760 				if (fIsFullscreen)
761 					_ToggleFullscreen();
762 				_ResizeWindow(size);
763 			}
764 			break;
765 		}
766 
767 /*
768 		case B_ACQUIRE_OVERLAY_LOCK:
769 			printf("B_ACQUIRE_OVERLAY_LOCK\n");
770 			fVideoView->OverlayLockAcquire();
771 			break;
772 
773 		case B_RELEASE_OVERLAY_LOCK:
774 			printf("B_RELEASE_OVERLAY_LOCK\n");
775 			fVideoView->OverlayLockRelease();
776 			break;
777 */
778 		case B_MOUSE_WHEEL_CHANGED:
779 		{
780 			float dx = msg->FindFloat("be:wheel_delta_x");
781 			float dy = msg->FindFloat("be:wheel_delta_y");
782 			bool inv = modifiers() & B_COMMAND_KEY;
783 			if (dx > 0.1)
784 				PostMessage(inv ? M_VOLUME_DOWN : M_SKIP_PREV);
785 			if (dx < -0.1)
786 				PostMessage(inv ? M_VOLUME_UP : M_SKIP_NEXT);
787 			if (dy > 0.1)
788 				PostMessage(inv ? M_SKIP_PREV : M_VOLUME_DOWN);
789 			if (dy < -0.1)
790 				PostMessage(inv ? M_SKIP_NEXT : M_VOLUME_UP);
791 			break;
792 		}
793 
794 		case M_SKIP_NEXT:
795 			fControls->SkipForward();
796 			break;
797 
798 		case M_SKIP_PREV:
799 			fControls->SkipBackward();
800 			break;
801 
802 		case M_WIND:
803 		{
804 			if (!fAllowWinding)
805 				break;
806 
807 			bigtime_t howMuch;
808 			int64 frames;
809 			if (msg->FindInt64("how much", &howMuch) != B_OK
810 				|| msg->FindInt64("frames", &frames) != B_OK) {
811 				break;
812 			}
813 
814 			if (fController->Lock()) {
815 				if (fHasVideo && !fController->IsPlaying()) {
816 					int64 newFrame = fController->CurrentFrame() + frames;
817 					fController->SetFramePosition(newFrame);
818 				} else {
819 					bigtime_t seekTime = fController->TimePosition() + howMuch;
820 					if (seekTime < 0) {
821 						fInitialSeekPosition = seekTime;
822 						PostMessage(M_SKIP_PREV);
823 					} else if (seekTime > fController->TimeDuration()) {
824 						fInitialSeekPosition = 0;
825 						PostMessage(M_SKIP_NEXT);
826 					} else
827 						fController->SetTimePosition(seekTime);
828 				}
829 				fController->Unlock();
830 
831 				fAllowWinding = false;
832 			}
833 			break;
834 		}
835 
836 		case M_VOLUME_UP:
837 			fController->VolumeUp();
838 			break;
839 
840 		case M_VOLUME_DOWN:
841 			fController->VolumeDown();
842 			break;
843 
844 		case M_ASPECT_SAME_AS_SOURCE:
845 			if (fHasVideo) {
846 				int width;
847 				int height;
848 				int widthAspect;
849 				int heightAspect;
850 				fController->GetSize(&width, &height,
851 					&widthAspect, &heightAspect);
852 				VideoFormatChange(width, height, widthAspect, heightAspect);
853 			}
854 			break;
855 
856 		case M_ASPECT_NO_DISTORTION:
857 			if (fHasVideo) {
858 				int width;
859 				int height;
860 				fController->GetSize(&width, &height);
861 				VideoFormatChange(width, height, width, height);
862 			}
863 			break;
864 
865 		case M_ASPECT_4_3:
866 			VideoAspectChange(4, 3);
867 			break;
868 
869 		case M_ASPECT_16_9: // 1.77 : 1
870 			VideoAspectChange(16, 9);
871 			break;
872 
873 		case M_ASPECT_83_50: // 1.66 : 1
874 			VideoAspectChange(83, 50);
875 			break;
876 
877 		case M_ASPECT_7_4: // 1.75 : 1
878 			VideoAspectChange(7, 4);
879 			break;
880 
881 		case M_ASPECT_37_20: // 1.85 : 1
882 			VideoAspectChange(37, 20);
883 			break;
884 
885 		case M_ASPECT_47_20: // 2.35 : 1
886 			VideoAspectChange(47, 20);
887 			break;
888 
889 		case M_SET_PLAYLIST_POSITION:
890 		{
891 			BAutolock _(fPlaylist);
892 
893 			int32 index;
894 			if (msg->FindInt32("index", &index) == B_OK)
895 				fPlaylist->SetCurrentItemIndex(index);
896 			break;
897 		}
898 
899 		case MSG_OBJECT_CHANGED:
900 			// received from fGlobalSettingsListener
901 			// TODO: find out which object, if we ever watch more than
902 			// the global settings instance...
903 			_AdoptGlobalSettings();
904 			break;
905 
906 		case M_SHOW_IF_NEEDED:
907 			_ShowIfNeeded();
908 			break;
909 
910 		case M_SLIDE_CONTROLS:
911 		{
912 			float offset;
913 			if (msg->FindFloat("offset", &offset) == B_OK) {
914 				fControls->MoveBy(0, offset);
915 				UpdateIfNeeded();
916 				snooze(15000);
917 			}
918 			break;
919 		}
920 		case M_FINISH_SLIDING_CONTROLS:
921 		{
922 			float offset;
923 			bool show;
924 			if (msg->FindFloat("offset", &offset) == B_OK
925 				&& msg->FindBool("show", &show) == B_OK) {
926 				if (show)
927 					fControls->MoveTo(fControls->Frame().left, offset);
928 				else {
929 					fControls->RemoveSelf();
930 					fControls->MoveTo(fVideoView->Frame().left,
931 						fVideoView->Frame().bottom + 1);
932 					fBackground->AddChild(fControls);
933 					fControls->SetSymbolScale(1.0f);
934 					while (!fControls->IsHidden())
935 						fControls->Hide();
936 				}
937 			}
938 			break;
939 		}
940 		case M_HIDE_FULL_SCREEN_CONTROLS:
941 			if (fIsFullscreen) {
942 				BPoint videoViewWhere;
943 				if (msg->FindPoint("where", &videoViewWhere) == B_OK) {
944 					if (!fControls->Frame().Contains(videoViewWhere)) {
945 						_ShowFullscreenControls(false);
946 						// hide the mouse cursor until the user moves it
947 						be_app->ObscureCursor();
948 					}
949 				}
950 			}
951 			break;
952 
953 		case M_SET_RATING:
954 		{
955 			int32 rating;
956 			if (msg->FindInt32("rating", &rating) == B_OK)
957 				_SetRating(rating);
958 			break;
959 		}
960 
961 		default:
962 			if (msg->what >= M_SELECT_AUDIO_TRACK
963 				&& msg->what <= M_SELECT_AUDIO_TRACK_END) {
964 				fController->SelectAudioTrack(msg->what - M_SELECT_AUDIO_TRACK);
965 				break;
966 			}
967 			if (msg->what >= M_SELECT_VIDEO_TRACK
968 				&& msg->what <= M_SELECT_VIDEO_TRACK_END) {
969 				fController->SelectVideoTrack(msg->what - M_SELECT_VIDEO_TRACK);
970 				break;
971 			}
972 			// let BWindow handle the rest
973 			BWindow::MessageReceived(msg);
974 	}
975 }
976 
977 
978 void
979 MainWin::WindowActivated(bool active)
980 {
981 	fController->PlayerActivated(active);
982 }
983 
984 
985 bool
986 MainWin::QuitRequested()
987 {
988 	BMessage message(M_PLAYER_QUIT);
989 	GetQuitMessage(&message);
990 	be_app->PostMessage(&message);
991 	return true;
992 }
993 
994 
995 void
996 MainWin::MenusBeginning()
997 {
998 	_SetupVideoAspectItems(fVideoAspectMenu);
999 }
1000 
1001 
1002 // #pragma mark -
1003 
1004 
1005 void
1006 MainWin::OpenPlaylist(const BMessage* playlistArchive)
1007 {
1008 	if (playlistArchive == NULL)
1009 		return;
1010 
1011 	BAutolock _(this);
1012 	BAutolock playlistLocker(fPlaylist);
1013 
1014 	if (fPlaylist->Unarchive(playlistArchive) != B_OK)
1015 		return;
1016 
1017 	int32 currentIndex;
1018 	if (playlistArchive->FindInt32("index", &currentIndex) != B_OK)
1019 		currentIndex = 0;
1020 	fPlaylist->SetCurrentItemIndex(currentIndex);
1021 
1022 	playlistLocker.Unlock();
1023 
1024 	if (currentIndex != -1) {
1025 		// Restore the current play position only if we have something to play
1026 		playlistArchive->FindInt64("position", (int64*)&fInitialSeekPosition);
1027 	}
1028 
1029 	if (IsHidden())
1030 		Show();
1031 }
1032 
1033 
1034 void
1035 MainWin::OpenPlaylistItem(const PlaylistItemRef& item)
1036 {
1037 	status_t ret = fController->SetToAsync(item);
1038 	if (ret != B_OK) {
1039 		fprintf(stderr, "MainWin::OpenPlaylistItem() - Failed to send message "
1040 			"to Controller.\n");
1041 		(new BAlert("error", NAME" encountered an internal error. "
1042 			"The file could not be opened.", "OK"))->Go();
1043 		_PlaylistItemOpened(item, ret);
1044 	} else {
1045 		BString string;
1046 		string << "Opening '" << item->Name() << "'.";
1047 		fControls->SetDisabledString(string.String());
1048 
1049 		if (IsHidden()) {
1050 			BMessage showMessage(M_SHOW_IF_NEEDED);
1051 			BMessageRunner::StartSending(BMessenger(this), &showMessage,
1052 				150000, 1);
1053 		}
1054 	}
1055 }
1056 
1057 
1058 void
1059 MainWin::ShowFileInfo()
1060 {
1061 	if (!fInfoWin)
1062 		fInfoWin = new InfoWin(Frame().LeftTop(), fController);
1063 
1064 	if (fInfoWin->Lock()) {
1065 		if (fInfoWin->IsHidden())
1066 			fInfoWin->Show();
1067 		else
1068 			fInfoWin->Activate();
1069 		fInfoWin->Unlock();
1070 	}
1071 }
1072 
1073 
1074 void
1075 MainWin::ShowPlaylistWindow()
1076 {
1077 	if (fPlaylistWindow->Lock()) {
1078 		// make sure the window shows on the same workspace as ourself
1079 		uint32 workspaces = Workspaces();
1080 		if (fPlaylistWindow->Workspaces() != workspaces)
1081 			fPlaylistWindow->SetWorkspaces(workspaces);
1082 
1083 		// show or activate
1084 		if (fPlaylistWindow->IsHidden())
1085 			fPlaylistWindow->Show();
1086 		else
1087 			fPlaylistWindow->Activate();
1088 
1089 		fPlaylistWindow->Unlock();
1090 	}
1091 }
1092 
1093 
1094 void
1095 MainWin::VideoAspectChange(int forcedWidth, int forcedHeight, float widthScale)
1096 {
1097 	// Force specific source size and pixel width scale.
1098 	if (fHasVideo) {
1099 		int width;
1100 		int height;
1101 		fController->GetSize(&width, &height);
1102 		VideoFormatChange(forcedWidth, forcedHeight,
1103 			lround(width * widthScale), height);
1104 	}
1105 }
1106 
1107 
1108 void
1109 MainWin::VideoAspectChange(float widthScale)
1110 {
1111 	// Called when video aspect ratio changes and the original
1112 	// width/height should be restored too, display aspect is not known,
1113 	// only pixel width scale.
1114 	if (fHasVideo) {
1115 		int width;
1116 		int height;
1117 		fController->GetSize(&width, &height);
1118 		VideoFormatChange(width, height, lround(width * widthScale), height);
1119 	}
1120 }
1121 
1122 
1123 void
1124 MainWin::VideoAspectChange(int widthAspect, int heightAspect)
1125 {
1126 	// Called when video aspect ratio changes and the original
1127 	// width/height should be restored too.
1128 	if (fHasVideo) {
1129 		int width;
1130 		int height;
1131 		fController->GetSize(&width, &height);
1132 		VideoFormatChange(width, height, widthAspect, heightAspect);
1133 	}
1134 }
1135 
1136 
1137 void
1138 MainWin::VideoFormatChange(int width, int height, int widthAspect,
1139 	int heightAspect)
1140 {
1141 	// Called when video format or aspect ratio changes.
1142 
1143 	printf("VideoFormatChange enter: width %d, height %d, "
1144 		"aspect ratio: %d:%d\n", width, height, widthAspect, heightAspect);
1145 
1146 	// remember current view scale
1147 	int percent = _CurrentVideoSizeInPercent();
1148 
1149  	fSourceWidth = width;
1150  	fSourceHeight = height;
1151  	fWidthAspect = widthAspect;
1152  	fHeightAspect = heightAspect;
1153 
1154 	if (percent == 100)
1155 		_ResizeWindow(100);
1156 	else
1157 	 	FrameResized(Bounds().Width(), Bounds().Height());
1158 
1159 	printf("VideoFormatChange leave\n");
1160 }
1161 
1162 
1163 void
1164 MainWin::GetQuitMessage(BMessage* message)
1165 {
1166 	message->AddPointer("instance", this);
1167 	message->AddRect("window frame", Frame());
1168 	message->AddBool("audio only", !fHasVideo);
1169 	message->AddInt64("creation time", fCreationTime);
1170 
1171 	if (!fHasVideo && fHasAudio) {
1172 		// store playlist, current index and position if this is audio
1173 		BMessage playlistArchive;
1174 
1175 		BAutolock controllerLocker(fController);
1176 		playlistArchive.AddInt64("position", fController->TimePosition());
1177 		controllerLocker.Unlock();
1178 
1179 		if (!fPlaylist)
1180 			return;
1181 
1182 		BAutolock playlistLocker(fPlaylist);
1183 		if (fPlaylist->Archive(&playlistArchive) != B_OK
1184 			|| playlistArchive.AddInt32("index",
1185 				fPlaylist->CurrentItemIndex()) != B_OK
1186 			|| message->AddMessage("playlist", &playlistArchive) != B_OK) {
1187 			fprintf(stderr, "Failed to store current playlist.\n");
1188 		}
1189 	}
1190 }
1191 
1192 
1193 BHandler*
1194 MainWin::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
1195 	int32 what, const char* property)
1196 {
1197 	BPropertyInfo propertyInfo(sPropertyInfo);
1198 	switch (propertyInfo.FindMatch(message, index, specifier, what, property)) {
1199 		case 0:
1200 		case 1:
1201 		case 2:
1202 		case 3:
1203 		case 4:
1204 		case 5:
1205 		case 6:
1206 		case 7:
1207 		case 8:
1208 			return this;
1209 	}
1210 
1211 	return BWindow::ResolveSpecifier(message, index, specifier, what, property);
1212 }
1213 
1214 
1215 status_t
1216 MainWin::GetSupportedSuites(BMessage* data)
1217 {
1218 	if (data == NULL)
1219 		return B_BAD_VALUE;
1220 
1221 	status_t status = data->AddString("suites", "suite/vnd.Haiku-MediaPlayer");
1222 	if (status != B_OK)
1223 		return status;
1224 
1225 	BPropertyInfo propertyInfo(sPropertyInfo);
1226 	status = data->AddFlat("messages", &propertyInfo);
1227 	if (status != B_OK)
1228 		return status;
1229 
1230 	return BWindow::GetSupportedSuites(data);
1231 }
1232 
1233 
1234 // #pragma mark -
1235 
1236 
1237 void
1238 MainWin::_RefsReceived(BMessage* message)
1239 {
1240 	// the playlist is replaced by dropped files
1241 	// or the dropped files are appended to the end
1242 	// of the existing playlist if <shift> is pressed
1243 	bool append = false;
1244 	if (message->FindBool("append to playlist", &append) != B_OK)
1245 		append = modifiers() & B_SHIFT_KEY;
1246 
1247 	BAutolock _(fPlaylist);
1248 	int32 appendIndex = append ? APPEND_INDEX_APPEND_LAST
1249 		: APPEND_INDEX_REPLACE_PLAYLIST;
1250 	message->AddInt32("append_index", appendIndex);
1251 
1252 	// forward the message to the playlist window,
1253 	// so that undo/redo is used for modifying the playlist
1254 	fPlaylistWindow->PostMessage(message);
1255 
1256 	if (message->FindRect("window frame", &fNoVideoFrame) != B_OK) {
1257 		fNoVideoFrame = BRect();
1258 		_ShowIfNeeded();
1259 	}
1260 }
1261 
1262 
1263 void
1264 MainWin::_PlaylistItemOpened(const PlaylistItemRef& item, status_t result)
1265 {
1266 	if (result != B_OK) {
1267 		BAutolock _(fPlaylist);
1268 
1269 		item->SetPlaybackFailed();
1270 		bool allItemsFailed = true;
1271 		int32 count = fPlaylist->CountItems();
1272 		for (int32 i = 0; i < count; i++) {
1273 			if (!fPlaylist->ItemAtFast(i)->PlaybackFailed()) {
1274 				allItemsFailed = false;
1275 				break;
1276 			}
1277 		}
1278 
1279 		if (allItemsFailed) {
1280 			// Display error if all files failed to play.
1281 			BString message;
1282 			message << "The file '";
1283 			message << item->Name();
1284 			message << "' could not be opened.\n\n";
1285 
1286 			if (result == B_MEDIA_NO_HANDLER) {
1287 				// give a more detailed message for the most likely of all
1288 				// errors
1289 				message << "There is no decoder installed to handle the "
1290 					"file format, or the decoder has trouble with the "
1291 					"specific version of the format.";
1292 			} else {
1293 				message << "Error: " << strerror(result);
1294 			}
1295 			(new BAlert("error", message.String(), "OK"))->Go();
1296 		} else {
1297 			// Just go to the next file and don't bother user (yet)
1298 			fPlaylist->SetCurrentItemIndex(fPlaylist->CurrentItemIndex() + 1);
1299 		}
1300 
1301 		fHasFile = false;
1302 		fHasVideo = false;
1303 		fHasAudio = false;
1304 		SetTitle(NAME);
1305 	} else {
1306 		fHasFile = true;
1307 		fHasVideo = fController->VideoTrackCount() != 0;
1308 		fHasAudio = fController->AudioTrackCount() != 0;
1309 		SetTitle(item->Name().String());
1310 
1311 		if (fInitialSeekPosition < 0) {
1312 			fInitialSeekPosition
1313 				= fController->TimeDuration() + fInitialSeekPosition;
1314 		}
1315 		fController->SetTimePosition(fInitialSeekPosition);
1316 		fInitialSeekPosition = 0;
1317 	}
1318 	_SetupWindow();
1319 
1320 	if (result == B_OK)
1321 		_UpdatePlaylistItemFile();
1322 }
1323 
1324 
1325 void
1326 MainWin::_SetupWindow()
1327 {
1328 //	printf("MainWin::_SetupWindow\n");
1329 	// Populate the track menus
1330 	_SetupTrackMenus(fAudioTrackMenu, fVideoTrackMenu);
1331 	_UpdateAudioChannelCount(fController->CurrentAudioTrack());
1332 
1333 	fVideoMenu->SetEnabled(fHasVideo);
1334 	fAudioMenu->SetEnabled(fHasAudio);
1335 	int previousSourceWidth = fSourceWidth;
1336 	int previousSourceHeight = fSourceHeight;
1337 	int previousWidthAspect = fWidthAspect;
1338 	int previousHeightAspect = fHeightAspect;
1339 	if (fHasVideo) {
1340 		fController->GetSize(&fSourceWidth, &fSourceHeight,
1341 			&fWidthAspect, &fHeightAspect);
1342 	} else {
1343 		fSourceWidth = 0;
1344 		fSourceHeight = 0;
1345 		fWidthAspect = 1;
1346 		fHeightAspect = 1;
1347 	}
1348 	_UpdateControlsEnabledStatus();
1349 
1350 	if (!fHasVideo && fNoVideoFrame.IsValid()) {
1351 		MoveTo(fNoVideoFrame.LeftTop());
1352 		ResizeTo(fNoVideoFrame.Width(), fNoVideoFrame.Height());
1353 	}
1354 	fNoVideoFrame = BRect();
1355 	_ShowIfNeeded();
1356 
1357 	// Adopt the size and window layout if necessary
1358 	if (previousSourceWidth != fSourceWidth
1359 		|| previousSourceHeight != fSourceHeight
1360 		|| previousWidthAspect != fWidthAspect
1361 		|| previousHeightAspect != fHeightAspect) {
1362 
1363 		_SetWindowSizeLimits();
1364 
1365 		if (!fIsFullscreen) {
1366 			// Resize to 100% but stay on screen
1367 			_ResizeWindow(100, !fHasVideo, true);
1368 		} else {
1369 			// Make sure we relayout the video view when in full screen mode
1370 			FrameResized(Frame().Width(), Frame().Height());
1371 		}
1372 	}
1373 
1374 	fVideoView->MakeFocus();
1375 }
1376 
1377 
1378 void
1379 MainWin::_CreateMenu()
1380 {
1381 	fFileMenu = new BMenu(NAME);
1382 	fPlaylistMenu = new BMenu("Playlist"B_UTF8_ELLIPSIS);
1383 	fAudioMenu = new BMenu("Audio");
1384 	fVideoMenu = new BMenu("Video");
1385 	fVideoAspectMenu = new BMenu("Aspect ratio");
1386 	fAudioTrackMenu = new BMenu("Track");
1387 	fVideoTrackMenu = new BMenu("Track");
1388 	fAttributesMenu = new BMenu("Attributes");
1389 
1390 	fMenuBar->AddItem(fFileMenu);
1391 	fMenuBar->AddItem(fAudioMenu);
1392 	fMenuBar->AddItem(fVideoMenu);
1393 	fMenuBar->AddItem(fAttributesMenu);
1394 
1395 	BMenuItem* item = new BMenuItem("New player"B_UTF8_ELLIPSIS,
1396 		new BMessage(M_NEW_PLAYER), 'N');
1397 	fFileMenu->AddItem(item);
1398 	item->SetTarget(be_app);
1399 
1400 #if 0
1401 	// Plain "Open File" entry
1402 	fFileMenu->AddItem(new BMenuItem("Open File"B_UTF8_ELLIPSIS,
1403 		new BMessage(M_FILE_OPEN), 'O'));
1404 #else
1405 	// Add recent files to "Open File" entry as sub-menu.
1406 	BRecentFilesList recentFiles(10, false, NULL, kAppSig);
1407 	item = new BMenuItem(recentFiles.NewFileListMenu(
1408 		"Open file"B_UTF8_ELLIPSIS, new BMessage(B_REFS_RECEIVED),
1409 		NULL, this, 10, false, NULL, 0, kAppSig), new BMessage(M_FILE_OPEN));
1410 	item->SetShortcut('O', 0);
1411 	fFileMenu->AddItem(item);
1412 #endif
1413 
1414 	fFileMenu->AddSeparatorItem();
1415 
1416 	fFileMenu->AddItem(new BMenuItem("File info"B_UTF8_ELLIPSIS,
1417 		new BMessage(M_FILE_INFO), 'I'));
1418 	fFileMenu->AddItem(fPlaylistMenu);
1419 	fPlaylistMenu->Superitem()->SetShortcut('P', B_COMMAND_KEY);
1420 	fPlaylistMenu->Superitem()->SetMessage(new BMessage(M_FILE_PLAYLIST));
1421 
1422 	fFileMenu->AddSeparatorItem();
1423 
1424 	fNoInterfaceMenuItem = new BMenuItem("Hide interface",
1425 		new BMessage(M_TOGGLE_NO_INTERFACE), 'H');
1426 	fFileMenu->AddItem(fNoInterfaceMenuItem);
1427 	fFileMenu->AddItem(new BMenuItem("Always on top",
1428 		new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
1429 
1430 	item = new BMenuItem("Settings"B_UTF8_ELLIPSIS,
1431 		new BMessage(M_SETTINGS), 'S');
1432 	fFileMenu->AddItem(item);
1433 	item->SetTarget(be_app);
1434 
1435 	fFileMenu->AddSeparatorItem();
1436 
1437 	item = new BMenuItem("About " NAME B_UTF8_ELLIPSIS,
1438 		new BMessage(B_ABOUT_REQUESTED));
1439 	fFileMenu->AddItem(item);
1440 	item->SetTarget(be_app);
1441 
1442 	fFileMenu->AddSeparatorItem();
1443 
1444 	fFileMenu->AddItem(new BMenuItem("Close", new BMessage(M_FILE_CLOSE), 'W'));
1445 	fFileMenu->AddItem(new BMenuItem("Quit", new BMessage(M_FILE_QUIT), 'Q'));
1446 
1447 	fPlaylistMenu->SetRadioMode(true);
1448 
1449 	fAudioMenu->AddItem(fAudioTrackMenu);
1450 
1451 	fVideoMenu->AddItem(fVideoTrackMenu);
1452 	fVideoMenu->AddSeparatorItem();
1453 	BMessage* resizeMessage = new BMessage(M_VIEW_SIZE);
1454 	resizeMessage->AddInt32("size", 50);
1455 	fVideoMenu->AddItem(new BMenuItem("50% scale", resizeMessage, '0'));
1456 
1457 	resizeMessage = new BMessage(M_VIEW_SIZE);
1458 	resizeMessage->AddInt32("size", 100);
1459 	fVideoMenu->AddItem(new BMenuItem("100% scale", resizeMessage, '1'));
1460 
1461 	resizeMessage = new BMessage(M_VIEW_SIZE);
1462 	resizeMessage->AddInt32("size", 200);
1463 	fVideoMenu->AddItem(new BMenuItem("200% scale", resizeMessage, '2'));
1464 
1465 	resizeMessage = new BMessage(M_VIEW_SIZE);
1466 	resizeMessage->AddInt32("size", 300);
1467 	fVideoMenu->AddItem(new BMenuItem("300% scale", resizeMessage, '3'));
1468 
1469 	resizeMessage = new BMessage(M_VIEW_SIZE);
1470 	resizeMessage->AddInt32("size", 400);
1471 	fVideoMenu->AddItem(new BMenuItem("400% scale", resizeMessage, '4'));
1472 
1473 	fVideoMenu->AddSeparatorItem();
1474 
1475 	fVideoMenu->AddItem(new BMenuItem("Full screen",
1476 		new BMessage(M_TOGGLE_FULLSCREEN), 'F'));
1477 
1478 	fVideoMenu->AddSeparatorItem();
1479 
1480 	_SetupVideoAspectItems(fVideoAspectMenu);
1481 	fVideoMenu->AddItem(fVideoAspectMenu);
1482 
1483 	fRatingMenu = new BMenu("Rating");
1484 	fAttributesMenu->AddItem(fRatingMenu);
1485 	for (int32 i = 1; i <= 10; i++) {
1486 		char label[16];
1487 		snprintf(label, sizeof(label), "%ld", i);
1488 		BMessage* setRatingMsg = new BMessage(M_SET_RATING);
1489 		setRatingMsg->AddInt32("rating", i);
1490 		fRatingMenu->AddItem(new BMenuItem(label, setRatingMsg));
1491 	}
1492 }
1493 
1494 
1495 void
1496 MainWin::_SetupVideoAspectItems(BMenu* menu)
1497 {
1498 	BMenuItem* item;
1499 	while ((item = menu->RemoveItem(0L)) != NULL)
1500 		delete item;
1501 
1502 	int width;
1503 	int height;
1504 	int widthAspect;
1505 	int heightAspect;
1506 	fController->GetSize(&width, &height, &widthAspect, &heightAspect);
1507 		// We don't care if there is a video track at all. In that
1508 		// case we should end up not marking any item.
1509 
1510 	// NOTE: The item marking may end up marking for example both
1511 	// "Stream Settings" and "16 : 9" if the stream settings happen to
1512 	// be "16 : 9".
1513 
1514 	menu->AddItem(item = new BMenuItem("Stream settings",
1515 		new BMessage(M_ASPECT_SAME_AS_SOURCE)));
1516 	item->SetMarked(widthAspect == fWidthAspect
1517 		&& heightAspect == fHeightAspect);
1518 
1519 	menu->AddItem(item = new BMenuItem("No aspect correction",
1520 		new BMessage(M_ASPECT_NO_DISTORTION)));
1521 	item->SetMarked(width == fWidthAspect && height == fHeightAspect);
1522 
1523 	menu->AddSeparatorItem();
1524 
1525 	menu->AddItem(item = new BMenuItem("4 : 3",
1526 		new BMessage(M_ASPECT_4_3)));
1527 	item->SetMarked(fWidthAspect == 4 && fHeightAspect == 3);
1528 	menu->AddItem(item = new BMenuItem("16 : 9",
1529 		new BMessage(M_ASPECT_16_9)));
1530 	item->SetMarked(fWidthAspect == 16 && fHeightAspect == 9);
1531 
1532 	menu->AddSeparatorItem();
1533 
1534 	menu->AddItem(item = new BMenuItem("1.66 : 1",
1535 		new BMessage(M_ASPECT_83_50)));
1536 	item->SetMarked(fWidthAspect == 83 && fHeightAspect == 50);
1537 	menu->AddItem(item = new BMenuItem("1.75 : 1",
1538 		new BMessage(M_ASPECT_7_4)));
1539 	item->SetMarked(fWidthAspect == 7 && fHeightAspect == 4);
1540 	menu->AddItem(item = new BMenuItem("1.85 : 1 (American)",
1541 		new BMessage(M_ASPECT_37_20)));
1542 	item->SetMarked(fWidthAspect == 37 && fHeightAspect == 20);
1543 	menu->AddItem(item = new BMenuItem("2.35 : 1 (Cinemascope)",
1544 		new BMessage(M_ASPECT_47_20)));
1545 	item->SetMarked(fWidthAspect == 47 && fHeightAspect == 20);
1546 }
1547 
1548 
1549 void
1550 MainWin::_SetupTrackMenus(BMenu* audioTrackMenu, BMenu* videoTrackMenu)
1551 {
1552 	audioTrackMenu->RemoveItems(0, audioTrackMenu->CountItems(), true);
1553 	videoTrackMenu->RemoveItems(0, videoTrackMenu->CountItems(), true);
1554 
1555 	char s[100];
1556 
1557 	int count = fController->AudioTrackCount();
1558 	int current = fController->CurrentAudioTrack();
1559 	for (int i = 0; i < count; i++) {
1560 		BMessage metaData;
1561 		const char* languageString = NULL;
1562 		if (fController->GetAudioMetaData(i, &metaData) == B_OK)
1563 			metaData.FindString("language", &languageString);
1564 		if (languageString != NULL) {
1565 			BLanguage language(languageString);
1566 			BString languageName;
1567 			if (language.GetTranslatedName(languageName) == B_OK)
1568 				languageString = languageName.String();
1569 			snprintf(s, sizeof(s), "%s", languageString);
1570 		} else
1571 			snprintf(s, sizeof(s), "Track %d", i + 1);
1572 		BMenuItem* item = new BMenuItem(s,
1573 			new BMessage(M_SELECT_AUDIO_TRACK + i));
1574 		item->SetMarked(i == current);
1575 		audioTrackMenu->AddItem(item);
1576 	}
1577 	if (count == 0) {
1578 		audioTrackMenu->AddItem(new BMenuItem("none", new BMessage(M_DUMMY)));
1579 		audioTrackMenu->ItemAt(0)->SetMarked(true);
1580 	}
1581 
1582 
1583 	count = fController->VideoTrackCount();
1584 	current = fController->CurrentVideoTrack();
1585 	for (int i = 0; i < count; i++) {
1586 		snprintf(s, sizeof(s), "Track %d", i + 1);
1587 		BMenuItem* item = new BMenuItem(s,
1588 			new BMessage(M_SELECT_VIDEO_TRACK + i));
1589 		item->SetMarked(i == current);
1590 		videoTrackMenu->AddItem(item);
1591 	}
1592 	if (count == 0) {
1593 		videoTrackMenu->AddItem(new BMenuItem("none", new BMessage(M_DUMMY)));
1594 		videoTrackMenu->ItemAt(0)->SetMarked(true);
1595 	}
1596 }
1597 
1598 
1599 void
1600 MainWin::_UpdateAudioChannelCount(int32 audioTrackIndex)
1601 {
1602 	fControls->SetAudioChannelCount(fController->AudioTrackChannelCount());
1603 }
1604 
1605 
1606 void
1607 MainWin::_GetMinimumWindowSize(int& width, int& height) const
1608 {
1609 	width = MIN_WIDTH;
1610 	height = 0;
1611 	if (!fNoInterface) {
1612 		width = max_c(width, fMenuBarWidth);
1613 		width = max_c(width, fControlsWidth);
1614 		height = fMenuBarHeight + fControlsHeight;
1615 	}
1616 }
1617 
1618 
1619 void
1620 MainWin::_GetUnscaledVideoSize(int& videoWidth, int& videoHeight) const
1621 {
1622 	if (fWidthAspect != 0 && fHeightAspect != 0) {
1623 		videoWidth = fSourceHeight * fWidthAspect / fHeightAspect;
1624 		videoHeight = fSourceWidth * fHeightAspect / fWidthAspect;
1625 		// Use the scaling which produces an enlarged view.
1626 		if (videoWidth > fSourceWidth) {
1627 			// Enlarge width
1628 			videoHeight = fSourceHeight;
1629 		} else {
1630 			// Enlarge height
1631 			videoWidth = fSourceWidth;
1632 		}
1633 	} else {
1634 		videoWidth = fSourceWidth;
1635 		videoHeight = fSourceHeight;
1636 	}
1637 }
1638 
1639 
1640 void
1641 MainWin::_SetWindowSizeLimits()
1642 {
1643 	int minWidth;
1644 	int minHeight;
1645 	_GetMinimumWindowSize(minWidth, minHeight);
1646 	SetSizeLimits(minWidth - 1, 32000, minHeight - 1,
1647 		fHasVideo ? 32000 : minHeight - 1);
1648 }
1649 
1650 
1651 int
1652 MainWin::_CurrentVideoSizeInPercent() const
1653 {
1654 	if (!fHasVideo)
1655 		return 0;
1656 
1657 	int videoWidth;
1658 	int videoHeight;
1659 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1660 
1661 	int viewWidth = fVideoView->Bounds().IntegerWidth() + 1;
1662 	int viewHeight = fVideoView->Bounds().IntegerHeight() + 1;
1663 
1664 	int widthPercent = viewWidth * 100 / videoWidth;
1665 	int heightPercent = viewHeight * 100 / videoHeight;
1666 
1667 	if (widthPercent > heightPercent)
1668 		return widthPercent;
1669 	return heightPercent;
1670 }
1671 
1672 
1673 void
1674 MainWin::_ZoomVideoView(int percentDiff)
1675 {
1676 	if (!fHasVideo)
1677 		return;
1678 
1679 	int percent = _CurrentVideoSizeInPercent();
1680 	int newSize = percent * (100 + percentDiff) / 100;
1681 
1682 	if (newSize < 25)
1683 		newSize = 25;
1684 	if (newSize > 400)
1685 		newSize = 400;
1686 	if (newSize != percent) {
1687 		BMessage message(M_VIEW_SIZE);
1688 		message.AddInt32("size", newSize);
1689 		PostMessage(&message);
1690 	}
1691 }
1692 
1693 
1694 void
1695 MainWin::_ResizeWindow(int percent, bool useNoVideoWidth, bool stayOnScreen)
1696 {
1697 	// Get required window size
1698 	int videoWidth;
1699 	int videoHeight;
1700 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1701 
1702 	videoWidth = (videoWidth * percent) / 100;
1703 	videoHeight = (videoHeight * percent) / 100;
1704 
1705 	// Calculate and set the minimum window size
1706 	int width;
1707 	int height;
1708 	_GetMinimumWindowSize(width, height);
1709 
1710 	width = max_c(width, videoWidth) - 1;
1711 	if (useNoVideoWidth)
1712 		width = max_c(width, fNoVideoWidth);
1713 	height = height + videoHeight - 1;
1714 
1715 	if (stayOnScreen) {
1716 		BRect screenFrame(BScreen(this).Frame());
1717 		BRect frame(Frame());
1718 		BRect decoratorFrame(DecoratorFrame());
1719 
1720 		// Shrink the screen frame by the window border size
1721 		screenFrame.top += frame.top - decoratorFrame.top;
1722 		screenFrame.left += frame.left - decoratorFrame.left;
1723 		screenFrame.right += frame.right - decoratorFrame.right;
1724 		screenFrame.bottom += frame.bottom - decoratorFrame.bottom;
1725 
1726 		// Update frame to what the new size would be
1727 		frame.right = frame.left + width;
1728 		frame.bottom = frame.top + height;
1729 
1730 		if (!screenFrame.Contains(frame)) {
1731 			// Resize the window so it doesn't extend outside the current
1732 			// screen frame.
1733 			if (frame.Width() > screenFrame.Width()
1734 				|| frame.Height() > screenFrame.Height()) {
1735 				// too large
1736 				int widthDiff
1737 					= frame.IntegerWidth() - screenFrame.IntegerWidth();
1738 				int heightDiff
1739 					= frame.IntegerHeight() - screenFrame.IntegerHeight();
1740 
1741 				float shrinkScale;
1742 				if (widthDiff > heightDiff)
1743 					shrinkScale = (float)(width - widthDiff) / width;
1744 				else
1745 					shrinkScale = (float)(height - heightDiff) / height;
1746 
1747 				// Resize width/height and center window
1748 				width = lround(width * shrinkScale);
1749 				height = lround(height * shrinkScale);
1750 				MoveTo((screenFrame.left + screenFrame.right - width) / 2,
1751 					(screenFrame.top + screenFrame.bottom - height) / 2);
1752 			} else {
1753 				// just off-screen on one or more sides
1754 				int offsetX = 0;
1755 				int offsetY = 0;
1756 				if (frame.left < screenFrame.left)
1757 					offsetX = (int)(screenFrame.left - frame.left);
1758 				else if (frame.right > screenFrame.right)
1759 					offsetX = (int)(screenFrame.right - frame.right);
1760 				if (frame.top < screenFrame.top)
1761 					offsetY = (int)(screenFrame.top - frame.top);
1762 				else if (frame.bottom > screenFrame.bottom)
1763 					offsetY = (int)(screenFrame.bottom - frame.bottom);
1764 				MoveBy(offsetX, offsetY);
1765 			}
1766 		}
1767 	}
1768 
1769 	ResizeTo(width, height);
1770 }
1771 
1772 
1773 void
1774 MainWin::_ResizeVideoView(int x, int y, int width, int height)
1775 {
1776 	// Keep aspect ratio, place video view inside
1777 	// the background area (may create black bars).
1778 	int videoWidth;
1779 	int videoHeight;
1780 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1781 	float scaledWidth  = videoWidth;
1782 	float scaledHeight = videoHeight;
1783 	float factor = min_c(width / scaledWidth, height / scaledHeight);
1784 	int renderWidth = lround(scaledWidth * factor);
1785 	int renderHeight = lround(scaledHeight * factor);
1786 	if (renderWidth > width)
1787 		renderWidth = width;
1788 	if (renderHeight > height)
1789 		renderHeight = height;
1790 
1791 	int xOffset = (width - renderWidth) / 2;
1792 	int yOffset = (height - renderHeight) / 2;
1793 
1794 	fVideoView->MoveTo(x, y);
1795 	fVideoView->ResizeTo(width - 1, height - 1);
1796 
1797 	BRect videoFrame(xOffset, yOffset,
1798 		xOffset + renderWidth - 1, yOffset + renderHeight - 1);
1799 
1800 	fVideoView->SetVideoFrame(videoFrame);
1801 }
1802 
1803 
1804 // #pragma mark -
1805 
1806 
1807 void
1808 MainWin::_MouseDown(BMessage* msg, BView* originalHandler)
1809 {
1810 	uint32 buttons = msg->FindInt32("buttons");
1811 
1812 	// On Zeta, only "screen_where" is reliable, "where" and "be:view_where"
1813 	// seem to be broken
1814 	BPoint screenWhere;
1815 	if (msg->FindPoint("screen_where", &screenWhere) != B_OK) {
1816 		// TODO: remove
1817 		// Workaround for BeOS R5, it has no "screen_where"
1818 		if (!originalHandler || msg->FindPoint("where", &screenWhere) < B_OK)
1819 			return;
1820 		originalHandler->ConvertToScreen(&screenWhere);
1821 	}
1822 
1823 	// double click handling
1824 
1825 	if (msg->FindInt32("clicks") % 2 == 0) {
1826 		BRect rect(screenWhere.x - 1, screenWhere.y - 1, screenWhere.x + 1,
1827 			screenWhere.y + 1);
1828 		if (rect.Contains(fMouseDownMousePos)) {
1829 			if (buttons == B_PRIMARY_MOUSE_BUTTON)
1830 				PostMessage(M_TOGGLE_FULLSCREEN);
1831 			else if (buttons == B_SECONDARY_MOUSE_BUTTON)
1832 				PostMessage(M_TOGGLE_NO_INTERFACE);
1833 
1834 			return;
1835 		}
1836 	}
1837 
1838 	fMouseDownMousePos = screenWhere;
1839 	fMouseDownWindowPos = Frame().LeftTop();
1840 
1841 	if (buttons == B_PRIMARY_MOUSE_BUTTON && !fIsFullscreen) {
1842 		// start mouse tracking
1843 		fVideoView->SetMouseEventMask(B_POINTER_EVENTS | B_NO_POINTER_HISTORY
1844 			/* | B_LOCK_WINDOW_FOCUS */);
1845 		fMouseDownTracking = true;
1846 	}
1847 
1848 	// pop up a context menu if right mouse button is down for 200 ms
1849 
1850 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0)
1851 		return;
1852 
1853 	bigtime_t start = system_time();
1854 	bigtime_t delay = 200000;
1855 	BPoint location;
1856 	do {
1857 		fVideoView->GetMouse(&location, &buttons);
1858 		if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0)
1859 			break;
1860 		snooze(1000);
1861 	} while (system_time() - start < delay);
1862 
1863 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
1864 		_ShowContextMenu(screenWhere);
1865 }
1866 
1867 
1868 void
1869 MainWin::_MouseMoved(BMessage* msg, BView* originalHandler)
1870 {
1871 //	msg->PrintToStream();
1872 
1873 	BPoint mousePos;
1874 	uint32 buttons = msg->FindInt32("buttons");
1875 	// On Zeta, only "screen_where" is reliable, "where"
1876 	// and "be:view_where" seem to be broken
1877 	if (msg->FindPoint("screen_where", &mousePos) != B_OK) {
1878 		// TODO: remove
1879 		// Workaround for BeOS R5, it has no "screen_where"
1880 		if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK)
1881 			return;
1882 		originalHandler->ConvertToScreen(&mousePos);
1883 	}
1884 
1885 	if (buttons == B_PRIMARY_MOUSE_BUTTON && fMouseDownTracking
1886 		&& !fIsFullscreen) {
1887 //		printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y);
1888 		float delta_x = mousePos.x - fMouseDownMousePos.x;
1889 		float delta_y = mousePos.y - fMouseDownMousePos.y;
1890 		float x = fMouseDownWindowPos.x + delta_x;
1891 		float y = fMouseDownWindowPos.y + delta_y;
1892 //		printf("move window to %.0f, %.0f\n", x, y);
1893 		MoveTo(x, y);
1894 	}
1895 
1896 	bigtime_t eventTime;
1897 	if (msg->FindInt64("when", &eventTime) != B_OK)
1898 		eventTime = system_time();
1899 
1900 	if (buttons == 0 && fIsFullscreen) {
1901 		BPoint moveDelta = mousePos - fLastMousePos;
1902 		float moveDeltaDist
1903 			= sqrtf(moveDelta.x * moveDelta.x + moveDelta.y * moveDelta.y);
1904 		if (eventTime - fLastMouseMovedTime < 200000)
1905 			fMouseMoveDist += moveDeltaDist;
1906 		else
1907 			fMouseMoveDist = moveDeltaDist;
1908 		if (fMouseMoveDist > 5)
1909 			_ShowFullscreenControls(true);
1910 	}
1911 
1912 	fLastMousePos = mousePos;
1913 	fLastMouseMovedTime =eventTime;
1914 }
1915 
1916 
1917 void
1918 MainWin::_MouseUp(BMessage* msg)
1919 {
1920 	fMouseDownTracking = false;
1921 }
1922 
1923 
1924 void
1925 MainWin::_ShowContextMenu(const BPoint& screenPoint)
1926 {
1927 	printf("Show context menu\n");
1928 	BPopUpMenu* menu = new BPopUpMenu("context menu", false, false);
1929 	BMenuItem* item;
1930 	menu->AddItem(item = new BMenuItem("Full screen",
1931 		new BMessage(M_TOGGLE_FULLSCREEN), 'F'));
1932 	item->SetMarked(fIsFullscreen);
1933 	item->SetEnabled(fHasVideo);
1934 
1935 	BMenu* aspectSubMenu = new BMenu("Aspect ratio");
1936 	_SetupVideoAspectItems(aspectSubMenu);
1937 	aspectSubMenu->SetTargetForItems(this);
1938 	menu->AddItem(item = new BMenuItem(aspectSubMenu));
1939 	item->SetEnabled(fHasVideo);
1940 
1941 	menu->AddItem(item = new BMenuItem("No interface",
1942 		new BMessage(M_TOGGLE_NO_INTERFACE), 'B'));
1943 	item->SetMarked(fNoInterface);
1944 	item->SetEnabled(fHasVideo);
1945 
1946 	menu->AddSeparatorItem();
1947 
1948 	// Add track selector menus
1949 	BMenu* audioTrackMenu = new BMenu("Audio track");
1950 	BMenu* videoTrackMenu = new BMenu("Video track");
1951 	_SetupTrackMenus(audioTrackMenu, videoTrackMenu);
1952 
1953 	audioTrackMenu->SetTargetForItems(this);
1954 	videoTrackMenu->SetTargetForItems(this);
1955 
1956 	menu->AddItem(item = new BMenuItem(audioTrackMenu));
1957 	item->SetEnabled(fHasAudio);
1958 
1959 	menu->AddItem(item = new BMenuItem(videoTrackMenu));
1960 	item->SetEnabled(fHasVideo);
1961 
1962 	menu->AddSeparatorItem();
1963 	menu->AddItem(new BMenuItem("Quit", new BMessage(M_FILE_QUIT), 'Q'));
1964 
1965 	menu->SetTargetForItems(this);
1966 	BRect rect(screenPoint.x - 5, screenPoint.y - 5, screenPoint.x + 5,
1967 		screenPoint.y + 5);
1968 	menu->Go(screenPoint, true, true, rect, true);
1969 }
1970 
1971 
1972 /*!	Trap keys that are about to be send to background or renderer view.
1973 	Return true if it shouldn't be passed to the view.
1974 */
1975 bool
1976 MainWin::_KeyDown(BMessage* msg)
1977 {
1978 	uint32 key = msg->FindInt32("key");
1979 	uint32 rawChar = msg->FindInt32("raw_char");
1980 	uint32 modifier = msg->FindInt32("modifiers");
1981 
1982 //	printf("key 0x%lx, rawChar 0x%lx, modifiers 0x%lx\n", key, rawChar,
1983 //		modifier);
1984 
1985 	// ignore the system modifier namespace
1986 	if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
1987 			== (B_CONTROL_KEY | B_COMMAND_KEY))
1988 		return false;
1989 
1990 	switch (rawChar) {
1991 		case B_SPACE:
1992 			fController->TogglePlaying();
1993 			return true;
1994 
1995 		case 'm':
1996 			fController->ToggleMute();
1997 			return true;
1998 
1999 		case B_ESCAPE:
2000 			if (!fIsFullscreen)
2001 				break;
2002 
2003 			PostMessage(M_TOGGLE_FULLSCREEN);
2004 			return true;
2005 
2006 		case B_ENTER:		// Enter / Return
2007 			if ((modifier & B_COMMAND_KEY) != 0) {
2008 				PostMessage(M_TOGGLE_FULLSCREEN);
2009 				return true;
2010 			}
2011 			break;
2012 
2013 		case B_TAB:
2014 			if ((modifier & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
2015 					| B_MENU_KEY)) == 0) {
2016 				PostMessage(M_TOGGLE_FULLSCREEN);
2017 				return true;
2018 			}
2019 			break;
2020 
2021 		case B_UP_ARROW:
2022 			if ((modifier & B_COMMAND_KEY) != 0)
2023 				PostMessage(M_SKIP_NEXT);
2024 			else
2025 				PostMessage(M_VOLUME_UP);
2026 			return true;
2027 
2028 		case B_DOWN_ARROW:
2029 			if ((modifier & B_COMMAND_KEY) != 0)
2030 				PostMessage(M_SKIP_PREV);
2031 			else
2032 				PostMessage(M_VOLUME_DOWN);
2033 			return true;
2034 
2035 		case B_RIGHT_ARROW:
2036 			if ((modifier & B_COMMAND_KEY) != 0)
2037 				PostMessage(M_SKIP_NEXT);
2038 			else if (fAllowWinding) {
2039 				BMessage windMessage(M_WIND);
2040 				if ((modifier & B_SHIFT_KEY) != 0) {
2041 					windMessage.AddInt64("how much", 30000000LL);
2042 					windMessage.AddInt64("frames", 5);
2043 				} else {
2044 					windMessage.AddInt64("how much", 5000000LL);
2045 					windMessage.AddInt64("frames", 1);
2046 				}
2047 				PostMessage(&windMessage);
2048 			}
2049 			return true;
2050 
2051 		case B_LEFT_ARROW:
2052 			if ((modifier & B_COMMAND_KEY) != 0)
2053 				PostMessage(M_SKIP_PREV);
2054 			else if (fAllowWinding) {
2055 				BMessage windMessage(M_WIND);
2056 				if ((modifier & B_SHIFT_KEY) != 0) {
2057 					windMessage.AddInt64("how much", -30000000LL);
2058 					windMessage.AddInt64("frames", -5);
2059 				} else {
2060 					windMessage.AddInt64("how much", -5000000LL);
2061 					windMessage.AddInt64("frames", -1);
2062 				}
2063 				PostMessage(&windMessage);
2064 			}
2065 			return true;
2066 
2067 		case B_PAGE_UP:
2068 			PostMessage(M_SKIP_NEXT);
2069 			return true;
2070 
2071 		case B_PAGE_DOWN:
2072 			PostMessage(M_SKIP_PREV);
2073 			return true;
2074 
2075 		case '+':
2076 			if ((modifier & B_COMMAND_KEY) == 0) {
2077 				_ZoomVideoView(10);
2078 				return true;
2079 			}
2080 			break;
2081 
2082 		case '-':
2083 			if ((modifier & B_COMMAND_KEY) == 0) {
2084 				_ZoomVideoView(-10);
2085 				return true;
2086 			}
2087 			break;
2088 
2089 		case B_DELETE:
2090 		case 'd': 			// d for delete
2091 		case 't':			// t for Trash
2092 			if ((modifiers() & B_COMMAND_KEY) != 0) {
2093 				BAutolock _(fPlaylist);
2094 				BMessage removeMessage(M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH);
2095 				removeMessage.AddInt32("playlist index",
2096 					fPlaylist->CurrentItemIndex());
2097 				fPlaylistWindow->PostMessage(&removeMessage);
2098 				return true;
2099 			}
2100 			break;
2101 	}
2102 
2103 	switch (key) {
2104 		case 0x3a:  		// numeric keypad +
2105 			if ((modifier & B_COMMAND_KEY) == 0) {
2106 				_ZoomVideoView(10);
2107 				return true;
2108 			}
2109 			break;
2110 
2111 		case 0x25:  		// numeric keypad -
2112 			if ((modifier & B_COMMAND_KEY) == 0) {
2113 				_ZoomVideoView(-10);
2114 				return true;
2115 			}
2116 			break;
2117 
2118 		case 0x38:			// numeric keypad up arrow
2119 			PostMessage(M_VOLUME_UP);
2120 			return true;
2121 
2122 		case 0x59:			// numeric keypad down arrow
2123 			PostMessage(M_VOLUME_DOWN);
2124 			return true;
2125 
2126 		case 0x39:			// numeric keypad page up
2127 		case 0x4a:			// numeric keypad right arrow
2128 			PostMessage(M_SKIP_NEXT);
2129 			return true;
2130 
2131 		case 0x5a:			// numeric keypad page down
2132 		case 0x48:			// numeric keypad left arrow
2133 			PostMessage(M_SKIP_PREV);
2134 			return true;
2135 
2136 		// Playback controls along the bottom of the keyboard:
2137 		// Z X C V B  for US International
2138 		case 0x4c:
2139 			PostMessage(M_SKIP_PREV);
2140 			return true;
2141 		case 0x4d:
2142 			fController->Play();
2143 			return true;
2144 		case 0x4e:
2145 			fController->Pause();
2146 			return true;
2147 		case 0x4f:
2148 			fController->Stop();
2149 			return true;
2150 		case 0x50:
2151 			PostMessage(M_SKIP_NEXT);
2152 			return true;
2153 	}
2154 
2155 	return false;
2156 }
2157 
2158 
2159 // #pragma mark -
2160 
2161 
2162 void
2163 MainWin::_ToggleFullscreen()
2164 {
2165 	printf("_ToggleFullscreen enter\n");
2166 
2167 	if (!fHasVideo) {
2168 		printf("_ToggleFullscreen - ignoring, as we don't have a video\n");
2169 		return;
2170 	}
2171 
2172 	fIsFullscreen = !fIsFullscreen;
2173 
2174 	if (fIsFullscreen) {
2175 		// switch to fullscreen
2176 
2177 		fSavedFrame = Frame();
2178 		printf("saving current frame: %d %d %d %d\n", int(fSavedFrame.left),
2179 			int(fSavedFrame.top), int(fSavedFrame.right),
2180 			int(fSavedFrame.bottom));
2181 		BScreen screen(this);
2182 		BRect rect(screen.Frame());
2183 
2184 		Hide();
2185 		MoveTo(rect.left, rect.top);
2186 		ResizeTo(rect.Width(), rect.Height());
2187 		Show();
2188 
2189 	} else {
2190 		// switch back from full screen mode
2191 		_ShowFullscreenControls(false, false);
2192 
2193 		Hide();
2194 		MoveTo(fSavedFrame.left, fSavedFrame.top);
2195 		ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
2196 		Show();
2197 	}
2198 
2199 	fVideoView->SetFullscreen(fIsFullscreen);
2200 
2201 	_MarkItem(fFileMenu, M_TOGGLE_FULLSCREEN, fIsFullscreen);
2202 
2203 	printf("_ToggleFullscreen leave\n");
2204 }
2205 
2206 void
2207 MainWin::_ToggleAlwaysOnTop()
2208 {
2209 	fAlwaysOnTop = !fAlwaysOnTop;
2210 	SetFeel(fAlwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL);
2211 
2212 	_MarkItem(fFileMenu, M_TOGGLE_ALWAYS_ON_TOP, fAlwaysOnTop);
2213 }
2214 
2215 
2216 void
2217 MainWin::_ToggleNoInterface()
2218 {
2219 	printf("_ToggleNoInterface enter\n");
2220 
2221 	if (fIsFullscreen || !fHasVideo) {
2222 		// Fullscreen playback is always without interface and
2223 		// audio playback is always with interface. So we ignore these
2224 		// two states here.
2225 		printf("_ToggleNoControls leave, doing nothing, we are fullscreen\n");
2226 		return;
2227 	}
2228 
2229 	fNoInterface = !fNoInterface;
2230 	_SetWindowSizeLimits();
2231 
2232 	if (fNoInterface) {
2233 		MoveBy(0, fMenuBarHeight);
2234 		ResizeBy(0, -(fControlsHeight + fMenuBarHeight));
2235 		SetLook(B_BORDERED_WINDOW_LOOK);
2236 	} else {
2237 		MoveBy(0, -fMenuBarHeight);
2238 		ResizeBy(0, fControlsHeight + fMenuBarHeight);
2239 		SetLook(B_TITLED_WINDOW_LOOK);
2240 	}
2241 
2242 	_MarkItem(fFileMenu, M_TOGGLE_NO_INTERFACE, fNoInterface);
2243 
2244 	printf("_ToggleNoInterface leave\n");
2245 }
2246 
2247 
2248 void
2249 MainWin::_ShowIfNeeded()
2250 {
2251 	if (find_thread(NULL) != Thread())
2252 		return;
2253 
2254 	if (IsHidden()) {
2255 		Show();
2256 		UpdateIfNeeded();
2257 	}
2258 }
2259 
2260 
2261 void
2262 MainWin::_ShowFullscreenControls(bool show, bool animate)
2263 {
2264 	if (fShowsFullscreenControls == show)
2265 		return;
2266 
2267 	fShowsFullscreenControls = show;
2268 
2269 	if (show) {
2270 		fControls->RemoveSelf();
2271 		fControls->MoveTo(fVideoView->Bounds().left,
2272 			fVideoView->Bounds().bottom + 1);
2273 		fVideoView->AddChild(fControls);
2274 		if (fScaleFullscreenControls)
2275 			fControls->SetSymbolScale(1.5f);
2276 		while (fControls->IsHidden())
2277 			fControls->Show();
2278 	}
2279 
2280 	if (animate) {
2281 		// Slide the controls into view. We need to do this with
2282 		// messages, otherwise we block the video playback for the
2283 		// time of the animation.
2284 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
2285 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
2286 		float height = fControls->Bounds().Height();
2287 		float moveDist = show ? -height : height;
2288 		float originalY = fControls->Frame().top;
2289 		for (int32 i = 0; i < steps; i++) {
2290 			BMessage message(M_SLIDE_CONTROLS);
2291 			message.AddFloat("offset",
2292 				floorf(moveDist * kAnimationOffsets[i]));
2293 			PostMessage(&message, this);
2294 		}
2295 		BMessage finalMessage(M_FINISH_SLIDING_CONTROLS);
2296 		finalMessage.AddFloat("offset", originalY + moveDist);
2297 		finalMessage.AddBool("show", show);
2298 		PostMessage(&finalMessage, this);
2299 	} else {
2300 		if (!show) {
2301 			fControls->RemoveSelf();
2302 			fControls->MoveTo(fVideoView->Frame().left,
2303 				fVideoView->Frame().bottom + 1);
2304 			fBackground->AddChild(fControls);
2305 			fControls->SetSymbolScale(1.0f);
2306 			while (!fControls->IsHidden())
2307 				fControls->Hide();
2308 		}
2309 	}
2310 }
2311 
2312 
2313 // #pragma mark -
2314 
2315 
2316 void
2317 MainWin::_UpdatePlaylistItemFile()
2318 {
2319 	BAutolock locker(fPlaylist);
2320 	const FilePlaylistItem* item
2321 		= dynamic_cast<const FilePlaylistItem*>(fController->Item());
2322 	if (item == NULL)
2323 		return;
2324 
2325 	if (!fHasVideo && !fHasAudio)
2326 		return;
2327 
2328 	BNode node(&item->Ref());
2329 	if (node.InitCheck())
2330 		return;
2331 
2332 	// Add to recent documents
2333 	be_roster->AddToRecentDocuments(&item->Ref(), kAppSig);
2334 
2335 	locker.Unlock();
2336 
2337 	// Set some standard attributes of the currently played file.
2338 	// This should only be a temporary solution.
2339 
2340 	// Write duration
2341 	const char* kDurationAttrName = "Media:Length";
2342 	attr_info info;
2343 	status_t status = node.GetAttrInfo(kDurationAttrName, &info);
2344 	if (status != B_OK || info.size == 0) {
2345 		time_t duration = fController->TimeDuration() / 1000000L;
2346 
2347 		char text[256];
2348 		duration_to_string(duration, text, sizeof(text));
2349 		node.WriteAttr(kDurationAttrName, B_STRING_TYPE, 0, text,
2350 			strlen(text) + 1);
2351 	}
2352 
2353 	// Write audio bitrate
2354 	if (fHasAudio) {
2355 		status = node.GetAttrInfo("Audio:Bitrate", &info);
2356 		if (status != B_OK || info.size == 0) {
2357 			media_format format;
2358 			if (fController->GetEncodedAudioFormat(&format) == B_OK
2359 				&& format.type == B_MEDIA_ENCODED_AUDIO) {
2360 				int32 bitrate = (int32)(format.u.encoded_audio.bit_rate
2361 					/ 1000);
2362 				char text[256];
2363 				snprintf(text, sizeof(text), "%ld kbit", bitrate);
2364 				node.WriteAttr("Audio:Bitrate", B_STRING_TYPE, 0, text,
2365 					strlen(text) + 1);
2366 			}
2367 		}
2368 	}
2369 
2370 	// Write video bitrate
2371 	if (fHasVideo) {
2372 		status = node.GetAttrInfo("Video:Bitrate", &info);
2373 		if (status != B_OK || info.size == 0) {
2374 			media_format format;
2375 			if (fController->GetEncodedVideoFormat(&format) == B_OK
2376 				&& format.type == B_MEDIA_ENCODED_VIDEO) {
2377 				int32 bitrate = (int32)(format.u.encoded_video.avg_bit_rate
2378 					/ 1000);
2379 				char text[256];
2380 				snprintf(text, sizeof(text), "%ld kbit", bitrate);
2381 				node.WriteAttr("Video:Bitrate", B_STRING_TYPE, 0, text,
2382 					strlen(text) + 1);
2383 			}
2384 		}
2385 	}
2386 
2387 	_UpdateAttributesMenu(node);
2388 }
2389 
2390 
2391 void
2392 MainWin::_UpdateAttributesMenu(const BNode& node)
2393 {
2394 	int32 rating = -1;
2395 
2396 	attr_info info;
2397 	status_t status = node.GetAttrInfo(kRatingAttrName, &info);
2398 	if (status == B_OK && info.type == B_INT32_TYPE) {
2399 		// Node has the Rating attribute.
2400 		node.ReadAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating,
2401 			sizeof(rating));
2402 	}
2403 
2404 	for (int32 i = 0; BMenuItem* item = fRatingMenu->ItemAt(i); i++)
2405 		item->SetMarked(i + 1 == rating);
2406 }
2407 
2408 
2409 void
2410 MainWin::_SetRating(int32 rating)
2411 {
2412 	BAutolock locker(fPlaylist);
2413 	const FilePlaylistItem* item
2414 		= dynamic_cast<const FilePlaylistItem*>(fController->Item());
2415 	if (item == NULL)
2416 		return;
2417 
2418 	BNode node(&item->Ref());
2419 	if (node.InitCheck())
2420 		return;
2421 
2422 	locker.Unlock();
2423 
2424 	node.WriteAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating, sizeof(rating));
2425 
2426 	// TODO: The whole mechnism should work like this:
2427 	// * There is already an attribute API for PlaylistItem, flesh it out!
2428 	// * FilePlaylistItem node-monitors it's file somehow.
2429 	// * FilePlaylistItem keeps attributes in sync and sends notications.
2430 	// * MainWin updates the menu according to FilePlaylistItem notifications.
2431 	// * PlaylistWin shows columns with attribute and other info.
2432 	// * PlaylistWin updates also upon FilePlaylistItem notifications.
2433 	// * This keeps attributes in sync when another app changes them.
2434 
2435 	_UpdateAttributesMenu(node);
2436 }
2437 
2438 
2439 void
2440 MainWin::_UpdateControlsEnabledStatus()
2441 {
2442 	uint32 enabledButtons = 0;
2443 	if (fHasVideo || fHasAudio) {
2444 		enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED
2445 			| SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED;
2446 	}
2447 	if (fHasAudio)
2448 		enabledButtons |= VOLUME_ENABLED;
2449 
2450 	BAutolock _(fPlaylist);
2451 	bool canSkipPrevious, canSkipNext;
2452 	fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext);
2453 	if (canSkipPrevious)
2454 		enabledButtons |= SKIP_BACK_ENABLED;
2455 	if (canSkipNext)
2456 		enabledButtons |= SKIP_FORWARD_ENABLED;
2457 
2458 	fControls->SetEnabled(enabledButtons);
2459 
2460 	fNoInterfaceMenuItem->SetEnabled(fHasVideo);
2461 	fAttributesMenu->SetEnabled(fHasAudio || fHasVideo);
2462 }
2463 
2464 
2465 void
2466 MainWin::_UpdatePlaylistMenu()
2467 {
2468 	if (!fPlaylist->Lock())
2469 		return;
2470 
2471 	fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true);
2472 
2473 	int32 count = fPlaylist->CountItems();
2474 	for (int32 i = 0; i < count; i++) {
2475 		PlaylistItem* item = fPlaylist->ItemAtFast(i);
2476 		_AddPlaylistItem(item, i);
2477 	}
2478 	fPlaylistMenu->SetTargetForItems(this);
2479 
2480 	_MarkPlaylistItem(fPlaylist->CurrentItemIndex());
2481 
2482 	fPlaylist->Unlock();
2483 }
2484 
2485 
2486 void
2487 MainWin::_AddPlaylistItem(PlaylistItem* item, int32 index)
2488 {
2489 	BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION);
2490 	message->AddInt32("index", index);
2491 	BMenuItem* menuItem = new BMenuItem(item->Name().String(), message);
2492 	fPlaylistMenu->AddItem(menuItem, index);
2493 }
2494 
2495 
2496 void
2497 MainWin::_RemovePlaylistItem(int32 index)
2498 {
2499 	delete fPlaylistMenu->RemoveItem(index);
2500 }
2501 
2502 
2503 void
2504 MainWin::_MarkPlaylistItem(int32 index)
2505 {
2506 	if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) {
2507 		item->SetMarked(true);
2508 		// ... and in case the menu is currently on screen:
2509 		if (fPlaylistMenu->LockLooper()) {
2510 			fPlaylistMenu->Invalidate();
2511 			fPlaylistMenu->UnlockLooper();
2512 		}
2513 	}
2514 }
2515 
2516 
2517 void
2518 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark)
2519 {
2520 	if (BMenuItem* item = menu->FindItem(command))
2521 		item->SetMarked(mark);
2522 }
2523 
2524 
2525 void
2526 MainWin::_AdoptGlobalSettings()
2527 {
2528 	mpSettings settings = Settings::CurrentSettings();
2529 		// thread safe
2530 
2531 	fCloseWhenDonePlayingMovie = settings.closeWhenDonePlayingMovie;
2532 	fCloseWhenDonePlayingSound = settings.closeWhenDonePlayingSound;
2533 	fLoopMovies = settings.loopMovie;
2534 	fLoopSounds = settings.loopSound;
2535 	fScaleFullscreenControls = settings.scaleFullscreenControls;
2536 }
2537 
2538 
2539