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