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