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