xref: /haiku/src/apps/mediaplayer/MainWin.cpp (revision cf02b29e4e0dd6d61c4bb25fcc8620e99d4908bf)
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-2009 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 <Menu.h>
35 #include <MenuBar.h>
36 #include <MenuItem.h>
37 #include <Messenger.h>
38 #include <PopUpMenu.h>
39 #include <RecentItems.h>
40 #include <Roster.h>
41 #include <Screen.h>
42 #include <String.h>
43 #include <View.h>
44 
45 #include "AudioProducer.h"
46 #include "ControllerObserver.h"
47 #include "MainApp.h"
48 #include "PeakView.h"
49 #include "PlaylistItem.h"
50 #include "PlaylistObserver.h"
51 #include "PlaylistWindow.h"
52 #include "Settings.h"
53 
54 #define NAME "MediaPlayer"
55 #define MIN_WIDTH 250
56 
57 
58 // XXX TODO: why is lround not defined?
59 #define lround(a) ((int)(0.99999 + (a)))
60 
61 enum {
62 	M_DUMMY = 0x100,
63 	M_FILE_OPEN = 0x1000,
64 	M_FILE_NEWPLAYER,
65 	M_FILE_INFO,
66 	M_FILE_PLAYLIST,
67 	M_FILE_CLOSE,
68 	M_FILE_QUIT,
69 	M_VIEW_SIZE,
70 	M_TOGGLE_FULLSCREEN,
71 	M_TOGGLE_ALWAYS_ON_TOP,
72 	M_TOGGLE_NO_INTERFACE,
73 	M_VOLUME_UP,
74 	M_VOLUME_DOWN,
75 	M_SKIP_NEXT,
76 	M_SKIP_PREV,
77 
78 	// The common display aspect ratios
79 	M_ASPECT_SAME_AS_SOURCE,
80 	M_ASPECT_NO_DISTORTION,
81 	M_ASPECT_4_3,
82 	M_ASPECT_16_9,
83 	M_ASPECT_83_50,
84 	M_ASPECT_7_4,
85 	M_ASPECT_37_20,
86 	M_ASPECT_47_20,
87 
88 	M_SELECT_AUDIO_TRACK		= 0x00000800,
89 	M_SELECT_AUDIO_TRACK_END	= 0x00000fff,
90 	M_SELECT_VIDEO_TRACK		= 0x00010000,
91 	M_SELECT_VIDEO_TRACK_END	= 0x000fffff,
92 
93 	M_SET_PLAYLIST_POSITION,
94 
95 	M_FILE_DELETE
96 };
97 
98 //#define printf(a...)
99 
100 
101 MainWin::MainWin()
102 	:
103 	BWindow(BRect(100,100,400,300), NAME, B_TITLED_WINDOW,
104  		B_ASYNCHRONOUS_CONTROLS /* | B_WILL_ACCEPT_FIRST_CLICK */),
105 	fInfoWin(NULL),
106 	fPlaylistWindow(NULL),
107 	fHasFile(false),
108 	fHasVideo(false),
109 	fHasAudio(false),
110 	fPlaylist(new Playlist),
111 	fPlaylistObserver(new PlaylistObserver(this)),
112 	fController(new Controller),
113 	fControllerObserver(new ControllerObserver(this,
114 		OBSERVE_FILE_CHANGES | OBSERVE_TRACK_CHANGES
115 			| OBSERVE_PLAYBACK_STATE_CHANGES | OBSERVE_POSITION_CHANGES
116 			| OBSERVE_VOLUME_CHANGES)),
117 	fIsFullscreen(false),
118 	fAlwaysOnTop(false),
119 	fNoInterface(false),
120 	fSourceWidth(-1),
121 	fSourceHeight(-1),
122 	fWidthAspect(0),
123 	fHeightAspect(0),
124 	fMouseDownTracking(false),
125 	fGlobalSettingsListener(this)
126 {
127 	static int pos = 0;
128 	MoveBy(pos * 25, pos * 25);
129 	pos = (pos + 1) % 15;
130 
131 	BRect rect = Bounds();
132 
133 	// background
134 	fBackground = new BView(rect, "background", B_FOLLOW_ALL,
135 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
136 	fBackground->SetViewColor(0,0,0);
137 	AddChild(fBackground);
138 
139 	// menu
140 	fMenuBar = new BMenuBar(fBackground->Bounds(), "menu");
141 	_CreateMenu();
142 	fBackground->AddChild(fMenuBar);
143 	fMenuBar->SetResizingMode(B_FOLLOW_NONE);
144 	fMenuBar->ResizeToPreferred();
145 	fMenuBarWidth = (int)fMenuBar->Frame().Width() + 1;
146 	fMenuBarHeight = (int)fMenuBar->Frame().Height() + 1;
147 
148 	// video view
149 	rect = BRect(0, fMenuBarHeight, fBackground->Bounds().right,
150 		fMenuBarHeight + 10);
151 	fVideoView = new VideoView(rect, "video display", B_FOLLOW_NONE);
152 	fBackground->AddChild(fVideoView);
153 
154 	// controls
155 	rect = BRect(0, fMenuBarHeight + 11, fBackground->Bounds().right,
156 		fBackground->Bounds().bottom);
157 	fControls = new ControllerView(rect, fController, fPlaylist);
158 	fBackground->AddChild(fControls);
159 	fControls->ResizeToPreferred();
160 	fControlsHeight = (int)fControls->Frame().Height() + 1;
161 	fControlsWidth = (int)fControls->Frame().Width() + 1;
162 	fControls->SetResizingMode(B_FOLLOW_BOTTOM | B_FOLLOW_LEFT_RIGHT);
163 //	fControls->MoveTo(0, fBackground->Bounds().bottom - fControlsHeight + 1);
164 
165 //	fVideoView->ResizeTo(fBackground->Bounds().Width(),
166 //		fBackground->Bounds().Height() - fMenuBarHeight - fControlsHeight);
167 
168 	fPlaylist->AddListener(fPlaylistObserver);
169 	fController->SetVideoView(fVideoView);
170 	fController->AddListener(fControllerObserver);
171 	PeakView* peakView = fControls->GetPeakView();
172 	peakView->SetPeakNotificationWhat(MSG_PEAK_NOTIFICATION);
173 	fController->SetPeakListener(peakView);
174 
175 //	printf("fMenuBarHeight %d\n", fMenuBarHeight);
176 //	printf("fControlsHeight %d\n", fControlsHeight);
177 //	printf("fControlsWidth %d\n", fControlsWidth);
178 
179 	_SetupWindow();
180 
181 	// setup the playlist window now, we need to have it
182 	// running for the undo/redo playlist editing
183 	fPlaylistWindow = new PlaylistWindow(BRect(150, 150, 500, 600), fPlaylist,
184 		fController);
185 	fPlaylistWindow->Hide();
186 	fPlaylistWindow->Show();
187 		// this makes sure the window thread is running without
188 		// showing the window just yet
189 
190 	Settings::Default()->AddListener(&fGlobalSettingsListener);
191 	_AdoptGlobalSettings();
192 
193 	AddShortcut('z', B_COMMAND_KEY, new BMessage(B_UNDO));
194 	AddShortcut('y', B_COMMAND_KEY, new BMessage(B_UNDO));
195 	AddShortcut('z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO));
196 	AddShortcut('y', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO));
197 
198 	Show();
199 }
200 
201 
202 MainWin::~MainWin()
203 {
204 //	printf("MainWin::~MainWin\n");
205 
206 	Settings::Default()->RemoveListener(&fGlobalSettingsListener);
207 	fPlaylist->RemoveListener(fPlaylistObserver);
208 	fController->RemoveListener(fControllerObserver);
209 	fController->SetPeakListener(NULL);
210 
211 	// give the views a chance to detach from any notifiers
212 	// before we delete them
213 	fBackground->RemoveSelf();
214 	delete fBackground;
215 
216 	if (fInfoWin && fInfoWin->Lock())
217 		fInfoWin->Quit();
218 
219 	if (fPlaylistWindow && fPlaylistWindow->Lock())
220 		fPlaylistWindow->Quit();
221 
222 	delete fPlaylist;
223 
224 	// quit the Controller looper thread
225 	thread_id controllerThread = fController->Thread();
226 	fController->PostMessage(B_QUIT_REQUESTED);
227 	status_t exitValue;
228 	wait_for_thread(controllerThread, &exitValue);
229 }
230 
231 
232 // #pragma mark -
233 
234 
235 void
236 MainWin::FrameResized(float newWidth, float newHeight)
237 {
238 	if (newWidth != Bounds().Width() || newHeight != Bounds().Height()) {
239 		debugger("size wrong\n");
240 	}
241 
242 	bool noMenu = fNoInterface || fIsFullscreen;
243 	bool noControls = fNoInterface || fIsFullscreen;
244 
245 //	printf("FrameResized enter: newWidth %.0f, newHeight %.0f\n",
246 //		newWidth, newHeight);
247 
248 	int maxVideoWidth  = int(newWidth) + 1;
249 	int maxVideoHeight = int(newHeight) + 1
250 		- (noMenu  ? 0 : fMenuBarHeight)
251 		- (noControls ? 0 : fControlsHeight);
252 
253 	ASSERT(maxVideoHeight >= 0);
254 
255 	int y = 0;
256 
257 	if (noMenu) {
258 		if (!fMenuBar->IsHidden())
259 			fMenuBar->Hide();
260 	} else {
261 		fMenuBar->MoveTo(0, y);
262 		fMenuBar->ResizeTo(newWidth, fMenuBarHeight - 1);
263 		if (fMenuBar->IsHidden())
264 			fMenuBar->Show();
265 		y += fMenuBarHeight;
266 	}
267 
268 	if (maxVideoHeight == 0) {
269 		if (!fVideoView->IsHidden())
270 			fVideoView->Hide();
271 	} else {
272 		_ResizeVideoView(0, y, maxVideoWidth, maxVideoHeight);
273 		if (fVideoView->IsHidden())
274 			fVideoView->Show();
275 		y += maxVideoHeight;
276 	}
277 
278 	if (noControls) {
279 		if (!fControls->IsHidden())
280 			fControls->Hide();
281 	} else {
282 		fControls->MoveTo(0, y);
283 		fControls->ResizeTo(newWidth, fControlsHeight - 1);
284 		if (fControls->IsHidden())
285 			fControls->Show();
286 //		y += fControlsHeight;
287 	}
288 
289 //	printf("FrameResized leave\n");
290 }
291 
292 
293 void
294 MainWin::Zoom(BPoint rec_position, float rec_width, float rec_height)
295 {
296 	PostMessage(M_TOGGLE_FULLSCREEN);
297 }
298 
299 
300 void
301 MainWin::DispatchMessage(BMessage *msg, BHandler *handler)
302 {
303 	if ((msg->what == B_MOUSE_DOWN)
304 		&& (handler == fBackground || handler == fVideoView
305 				|| handler == fControls))
306 		_MouseDown(msg, dynamic_cast<BView*>(handler));
307 
308 	if ((msg->what == B_MOUSE_MOVED)
309 		&& (handler == fBackground || handler == fVideoView
310 				|| handler == fControls))
311 		_MouseMoved(msg, dynamic_cast<BView*>(handler));
312 
313 	if ((msg->what == B_MOUSE_UP)
314 		&& (handler == fBackground || handler == fVideoView))
315 		_MouseUp(msg);
316 
317 	if ((msg->what == B_KEY_DOWN)
318 		&& (handler == fBackground || handler == fVideoView)) {
319 
320 		// special case for PrintScreen key
321 		if (msg->FindInt32("key") == B_PRINT_KEY) {
322 			fVideoView->OverlayScreenshotPrepare();
323 			BWindow::DispatchMessage(msg, handler);
324 			fVideoView->OverlayScreenshotCleanup();
325 			return;
326 		}
327 
328 		// every other key gets dispatched to our _KeyDown first
329 		if (_KeyDown(msg)) {
330 			// it got handled, don't pass it on
331 			return;
332 		}
333 	}
334 
335 	BWindow::DispatchMessage(msg, handler);
336 }
337 
338 
339 void
340 MainWin::MessageReceived(BMessage *msg)
341 {
342 //	msg->PrintToStream();
343 	switch (msg->what) {
344 		case B_REFS_RECEIVED:
345 			printf("MainWin::MessageReceived: B_REFS_RECEIVED\n");
346 			_RefsReceived(msg);
347 			break;
348 		case B_SIMPLE_DATA:
349 			printf("MainWin::MessageReceived: B_SIMPLE_DATA\n");
350 			if (msg->HasRef("refs")) {
351 				// add to recent documents as it's not done with drag-n-drop
352 				entry_ref ref;
353 				for (int32 i = 0; msg->FindRef("refs", i, &ref) == B_OK; i++) {
354 					be_roster->AddToRecentDocuments(&ref, kAppSig);
355 				}
356 				_RefsReceived(msg);
357 			}
358 			break;
359 
360 		case B_UNDO:
361 		case B_REDO:
362 			fPlaylistWindow->PostMessage(msg);
363 			break;
364 
365 		case M_MEDIA_SERVER_STARTED:
366 			printf("TODO: implement M_MEDIA_SERVER_STARTED\n");
367 			// fController->...
368 			break;
369 
370 		case M_MEDIA_SERVER_QUIT:
371 			printf("TODO: implement M_MEDIA_SERVER_QUIT\n");
372 			// fController->...
373 			break;
374 
375 		// PlaylistObserver messages
376 		case MSG_PLAYLIST_ITEM_ADDED:
377 		{
378 			PlaylistItem* item;
379 			int32 index;
380 			if (msg->FindPointer("item", (void**)&item) == B_OK
381 				&& msg->FindInt32("index", &index) == B_OK) {
382 				_AddPlaylistItem(item, index);
383 			}
384 			break;
385 		}
386 		case MSG_PLAYLIST_ITEM_REMOVED:
387 		{
388 			int32 index;
389 			if (msg->FindInt32("index", &index) == B_OK)
390 				_RemovePlaylistItem(index);
391 			break;
392 		}
393 		case MSG_PLAYLIST_CURRENT_ITEM_CHANGED:
394 		{
395 			BAutolock _(fPlaylist);
396 
397 			int32 index;
398 			if (msg->FindInt32("index", &index) < B_OK
399 				|| index != fPlaylist->CurrentItemIndex())
400 				break;
401 			PlaylistItemRef item(fPlaylist->ItemAt(index));
402 			if (item.Get() != NULL) {
403 				printf("open playlist item: %s\n", item->Name().String());
404 				OpenPlaylistItem(item);
405 				_MarkPlaylistItem(index);
406 			}
407 			break;
408 		}
409 
410 		// ControllerObserver messages
411 		case MSG_CONTROLLER_FILE_FINISHED:
412 		{
413 			BAutolock _(fPlaylist);
414 
415 			bool hadNext = fPlaylist->SetCurrentItemIndex(
416 				fPlaylist->CurrentItemIndex() + 1);
417 			if (!hadNext) {
418 				if (fHasVideo) {
419 					if (fCloseWhenDonePlayingMovie)
420 						PostMessage(B_QUIT_REQUESTED);
421 				} else {
422 					if (fCloseWhenDonePlayingSound)
423 						PostMessage(B_QUIT_REQUESTED);
424 				}
425 			}
426 			break;
427 		}
428 		case MSG_CONTROLLER_FILE_CHANGED:
429 			// TODO: move all other GUI changes as a reaction to this
430 			// notification
431 //			_UpdatePlaylistMenu();
432 			break;
433 		case MSG_CONTROLLER_VIDEO_TRACK_CHANGED:
434 		{
435 			int32 index;
436 			if (msg->FindInt32("index", &index) == B_OK) {
437 				int32 i = 0;
438 				while (BMenuItem* item = fVideoTrackMenu->ItemAt(i)) {
439 					item->SetMarked(i == index);
440 					i++;
441 				}
442 			}
443 			break;
444 		}
445 		case MSG_CONTROLLER_AUDIO_TRACK_CHANGED:
446 		{
447 			int32 index;
448 			if (msg->FindInt32("index", &index) == B_OK) {
449 				int32 i = 0;
450 				while (BMenuItem* item = fAudioTrackMenu->ItemAt(i)) {
451 					item->SetMarked(i == index);
452 					i++;
453 				}
454 			}
455 			break;
456 		}
457 		case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED:
458 		{
459 			uint32 state;
460 			if (msg->FindInt32("state", (int32*)&state) == B_OK)
461 				fControls->SetPlaybackState(state);
462 			break;
463 		}
464 		case MSG_CONTROLLER_POSITION_CHANGED:
465 		{
466 			float position;
467 			if (msg->FindFloat("position", &position) == B_OK)
468 				fControls->SetPosition(position);
469 			break;
470 		}
471 		case MSG_CONTROLLER_VOLUME_CHANGED:
472 		{
473 			float volume;
474 			if (msg->FindFloat("volume", &volume) == B_OK)
475 				fControls->SetVolume(volume);
476 			break;
477 		}
478 		case MSG_CONTROLLER_MUTED_CHANGED:
479 		{
480 			bool muted;
481 			if (msg->FindBool("muted", &muted) == B_OK)
482 				fControls->SetMuted(muted);
483 			break;
484 		}
485 
486 		// menu item messages
487 		case M_FILE_NEWPLAYER:
488 			gMainApp->NewWindow();
489 			break;
490 		case M_FILE_OPEN:
491 		{
492 			BMessenger target(this);
493 			BMessage result(B_REFS_RECEIVED);
494 			BMessage appMessage(M_SHOW_OPEN_PANEL);
495 			appMessage.AddMessenger("target", target);
496 			appMessage.AddMessage("message", &result);
497 			appMessage.AddString("title", "Open Clips");
498 			appMessage.AddString("label", "Open");
499 			be_app->PostMessage(&appMessage);
500 			break;
501 		}
502 		case M_FILE_INFO:
503 			ShowFileInfo();
504 			break;
505 		case M_FILE_PLAYLIST:
506 			ShowPlaylistWindow();
507 			break;
508 		case B_ABOUT_REQUESTED:
509 		{
510 			BAlert *alert;
511 			alert = new BAlert("about", NAME"\n\n Written by Marcus Overhagen "
512 				", Stephan Aßmus and Frederik Modéen", "Thanks");
513 			if (fAlwaysOnTop) {
514 				_ToggleAlwaysOnTop();
515 				alert->Go(NULL);  // Asynchronous mode
516 				_ToggleAlwaysOnTop();
517 			} else {
518 				alert->Go(NULL); // Asynchronous mode
519 			}
520 			break;
521 		}
522 		case M_FILE_CLOSE:
523 			PostMessage(B_QUIT_REQUESTED);
524 			break;
525 		case M_FILE_QUIT:
526 			be_app->PostMessage(B_QUIT_REQUESTED);
527 			break;
528 
529 		case M_TOGGLE_FULLSCREEN:
530 			_ToggleFullscreen();
531 			break;
532 
533 		case M_TOGGLE_ALWAYS_ON_TOP:
534 			_ToggleAlwaysOnTop();
535 			break;
536 
537 		case M_TOGGLE_NO_INTERFACE:
538 			_ToggleNoInterface();
539 			break;
540 
541 		case M_VIEW_SIZE:
542 		{
543 			int32 size;
544 			if (msg->FindInt32("size", &size) == B_OK) {
545 				if (!fHasVideo)
546 					break;
547 				if (fIsFullscreen)
548 					_ToggleFullscreen();
549 				_ResizeWindow(size);
550 			}
551 			break;
552 		}
553 
554 /*
555 		case B_ACQUIRE_OVERLAY_LOCK:
556 			printf("B_ACQUIRE_OVERLAY_LOCK\n");
557 			fVideoView->OverlayLockAcquire();
558 			break;
559 
560 		case B_RELEASE_OVERLAY_LOCK:
561 			printf("B_RELEASE_OVERLAY_LOCK\n");
562 			fVideoView->OverlayLockRelease();
563 			break;
564 */
565 		case B_MOUSE_WHEEL_CHANGED:
566 		{
567 			float dx = msg->FindFloat("be:wheel_delta_x");
568 			float dy = msg->FindFloat("be:wheel_delta_y");
569 			bool inv = modifiers() & B_COMMAND_KEY;
570 			if (dx > 0.1)	PostMessage(inv ? M_VOLUME_DOWN : M_SKIP_PREV);
571 			if (dx < -0.1)	PostMessage(inv ? M_VOLUME_UP : M_SKIP_NEXT);
572 			if (dy > 0.1)	PostMessage(inv ? M_SKIP_PREV : M_VOLUME_DOWN);
573 			if (dy < -0.1)	PostMessage(inv ? M_SKIP_NEXT : M_VOLUME_UP);
574 			break;
575 		}
576 
577 		case M_SKIP_NEXT:
578 			fControls->SkipForward();
579 			break;
580 
581 		case M_SKIP_PREV:
582 			fControls->SkipBackward();
583 			break;
584 
585 		case M_VOLUME_UP:
586 			fController->VolumeUp();
587 			break;
588 
589 		case M_VOLUME_DOWN:
590 			fController->VolumeDown();
591 			break;
592 
593 		case M_ASPECT_SAME_AS_SOURCE:
594 			if (fHasVideo) {
595 				int width;
596 				int height;
597 				int widthAspect;
598 				int heightAspect;
599 				fController->GetSize(&width, &height,
600 					&widthAspect, &heightAspect);
601 				VideoFormatChange(width, height, widthAspect, heightAspect);
602 			}
603 			break;
604 
605 		case M_ASPECT_NO_DISTORTION:
606 			if (fHasVideo) {
607 				int width;
608 				int height;
609 				fController->GetSize(&width, &height);
610 				VideoFormatChange(width, height, width, height);
611 			}
612 			break;
613 
614 		case M_ASPECT_4_3:
615 			VideoAspectChange(4, 3);
616 			break;
617 
618 		case M_ASPECT_16_9: // 1.77 : 1
619 			VideoAspectChange(16, 9);
620 			break;
621 
622 		case M_ASPECT_83_50: // 1.66 : 1
623 			VideoAspectChange(83, 50);
624 			break;
625 
626 		case M_ASPECT_7_4: // 1.75 : 1
627 			VideoAspectChange(7, 4);
628 			break;
629 
630 		case M_ASPECT_37_20: // 1.85 : 1
631 			VideoAspectChange(37, 20);
632 			break;
633 
634 		case M_ASPECT_47_20: // 2.35 : 1
635 			VideoAspectChange(47, 20);
636 			break;
637 
638 		case M_SET_PLAYLIST_POSITION:
639 		{
640 			BAutolock _(fPlaylist);
641 
642 			int32 index;
643 			if (msg->FindInt32("index", &index) == B_OK)
644 				fPlaylist->SetCurrentItemIndex(index);
645 			break;
646 		}
647 
648 		case MSG_OBJECT_CHANGED:
649 			// received from fGlobalSettingsListener
650 			// TODO: find out which object, if we ever watch more than
651 			// the global settings instance...
652 			_AdoptGlobalSettings();
653 			break;
654 
655 		default:
656 			if (msg->what >= M_SELECT_AUDIO_TRACK
657 				&& msg->what <= M_SELECT_AUDIO_TRACK_END) {
658 				fController->SelectAudioTrack(msg->what - M_SELECT_AUDIO_TRACK);
659 				break;
660 			}
661 			if (msg->what >= M_SELECT_VIDEO_TRACK
662 				&& msg->what <= M_SELECT_VIDEO_TRACK_END) {
663 				fController->SelectVideoTrack(msg->what - M_SELECT_VIDEO_TRACK);
664 				break;
665 			}
666 			// let BWindow handle the rest
667 			BWindow::MessageReceived(msg);
668 	}
669 }
670 
671 
672 void
673 MainWin::WindowActivated(bool active)
674 {
675 	fController->PlayerActivated(active);
676 }
677 
678 
679 bool
680 MainWin::QuitRequested()
681 {
682 	be_app->PostMessage(M_PLAYER_QUIT);
683 	return true;
684 }
685 
686 
687 void
688 MainWin::MenusBeginning()
689 {
690 	_SetupVideoAspectItems(fVideoAspectMenu);
691 }
692 
693 
694 // #pragma mark -
695 
696 
697 void
698 MainWin::OpenPlaylistItem(const PlaylistItemRef& item)
699 {
700 	printf("MainWin::OpenPlaylistItem\n");
701 
702 	status_t err = fController->SetTo(item);
703 	if (err != B_OK) {
704 		BAutolock _(fPlaylist);
705 		if (fPlaylist->CountItems() == 1) {
706 			// display error if this is the only file we're supposed to play
707 			BString message;
708 			message << "The file '";
709 			message << item->Name();
710 			message << "' could not be opened.\n\n";
711 
712 			if (err == B_MEDIA_NO_HANDLER) {
713 				// give a more detailed message for the most likely of all
714 				// errors
715 				message << "There is no decoder installed to handle the "
716 					"file format, or the decoder has trouble with the specific "
717 					"version of the format.";
718 			} else {
719 				message << "Error: " << strerror(err);
720 			}
721 			(new BAlert("error", message.String(), "OK"))->Go();
722 		} else {
723 			// just go to the next file and don't bother user
724 			fPlaylist->SetCurrentItemIndex(fPlaylist->CurrentItemIndex() + 1);
725 		}
726 		fHasFile = false;
727 		fHasVideo = false;
728 		fHasAudio = false;
729 		SetTitle(NAME);
730 	} else {
731 		fHasFile = true;
732 		fHasVideo = fController->VideoTrackCount() != 0;
733 		fHasAudio = fController->AudioTrackCount() != 0;
734 		SetTitle(item->Name().String());
735 	}
736 	_SetupWindow();
737 }
738 
739 
740 void
741 MainWin::ShowFileInfo()
742 {
743 	if (!fInfoWin)
744 		fInfoWin = new InfoWin(Frame().LeftTop(), fController);
745 
746 	if (fInfoWin->Lock()) {
747 		if (fInfoWin->IsHidden())
748 			fInfoWin->Show();
749 		else
750 			fInfoWin->Activate();
751 		fInfoWin->Unlock();
752 	}
753 }
754 
755 
756 void
757 MainWin::ShowPlaylistWindow()
758 {
759 	if (fPlaylistWindow->Lock()) {
760 		// make sure the window shows on the same workspace as ourself
761 		uint32 workspaces = Workspaces();
762 		if (fPlaylistWindow->Workspaces() != workspaces)
763 			fPlaylistWindow->SetWorkspaces(workspaces);
764 
765 		// show or activate
766 		if (fPlaylistWindow->IsHidden())
767 			fPlaylistWindow->Show();
768 		else
769 			fPlaylistWindow->Activate();
770 
771 		fPlaylistWindow->Unlock();
772 	}
773 }
774 
775 
776 void
777 MainWin::VideoAspectChange(int forcedWidth, int forcedHeight, float widthScale)
778 {
779 	// Force specific source size and pixel width scale.
780 	if (fHasVideo) {
781 		int width;
782 		int height;
783 		fController->GetSize(&width, &height);
784 		VideoFormatChange(forcedWidth, forcedHeight,
785 			lround(width * widthScale), height);
786 	}
787 }
788 
789 
790 void
791 MainWin::VideoAspectChange(float widthScale)
792 {
793 	// Called when video aspect ratio changes and the original
794 	// width/height should be restored too, display aspect is not known,
795 	// only pixel width scale.
796 	if (fHasVideo) {
797 		int width;
798 		int height;
799 		fController->GetSize(&width, &height);
800 		VideoFormatChange(width, height, lround(width * widthScale), height);
801 	}
802 }
803 
804 
805 void
806 MainWin::VideoAspectChange(int widthAspect, int heightAspect)
807 {
808 	// Called when video aspect ratio changes and the original
809 	// width/height should be restored too.
810 	if (fHasVideo) {
811 		int width;
812 		int height;
813 		fController->GetSize(&width, &height);
814 		VideoFormatChange(width, height, widthAspect, heightAspect);
815 	}
816 }
817 
818 
819 void
820 MainWin::VideoFormatChange(int width, int height, int widthAspect,
821 	int heightAspect)
822 {
823 	// Called when video format or aspect ratio changes.
824 
825 	printf("VideoFormatChange enter: width %d, height %d, "
826 		"aspect ratio: %d:%d\n", width, height, widthAspect, heightAspect);
827 
828 	// remember current view scale
829 	int percent = _CurrentVideoSizeInPercent();
830 
831  	fSourceWidth = width;
832  	fSourceHeight = height;
833  	fWidthAspect = widthAspect;
834  	fHeightAspect = heightAspect;
835 
836 	if (percent == 100)
837 		_ResizeWindow(100);
838 	else
839 	 	FrameResized(Bounds().Width(), Bounds().Height());
840 
841 	printf("VideoFormatChange leave\n");
842 }
843 
844 
845 // #pragma mark -
846 
847 
848 void
849 MainWin::_RefsReceived(BMessage* msg)
850 {
851 	// the playlist ist replaced by dropped files
852 	// or the dropped files are appended to the end
853 	// of the existing playlist if <shift> is pressed
854 	BAutolock _(fPlaylist);
855 	int32 appendIndex = modifiers() & B_SHIFT_KEY ?
856 		fPlaylist->CountItems() : -1;
857 	msg->AddInt32("append_index", appendIndex);
858 
859 	// forward the message to the playlist window,
860 	// so that undo/redo is used for modifying the playlist
861 	fPlaylistWindow->PostMessage(msg);
862 }
863 
864 
865 void
866 MainWin::_SetupWindow()
867 {
868 //	printf("MainWin::_SetupWindow\n");
869 	// Populate the track menus
870 	_SetupTrackMenus(fAudioTrackMenu, fVideoTrackMenu);
871 	// Enable both if a file was loaded
872 	fAudioTrackMenu->SetEnabled(fHasFile);
873 	fVideoTrackMenu->SetEnabled(fHasFile);
874 
875 	fVideoMenu->SetEnabled(fHasVideo);
876 	fAudioMenu->SetEnabled(fHasAudio);
877 	int previousSourceWidth = fSourceWidth;
878 	int previousSourceHeight = fSourceHeight;
879 	int previousWidthAspect = fWidthAspect;
880 	int previousHeightAspect = fHeightAspect;
881 	if (fHasVideo) {
882 		fController->GetSize(&fSourceWidth, &fSourceHeight,
883 			&fWidthAspect, &fHeightAspect);
884 	} else {
885 		fSourceWidth = 0;
886 		fSourceHeight = 0;
887 		fWidthAspect = 1;
888 		fHeightAspect = 1;
889 	}
890 	_UpdateControlsEnabledStatus();
891 
892 	// Adopt the size and window layout if necessary
893 	if (previousSourceWidth != fSourceWidth
894 		|| previousSourceHeight != fSourceHeight
895 		|| previousWidthAspect != fWidthAspect
896 		|| previousHeightAspect != fHeightAspect) {
897 
898 		_SetWindowSizeLimits();
899 
900 		if (!fIsFullscreen) {
901 			// Resize to 100% but stay on screen
902 			_ResizeWindow(100, true);
903 		} else {
904 			// Make sure we relayout the video view when in full screen mode
905 			FrameResized(Frame().Width(), Frame().Height());
906 		}
907 	}
908 
909 	fVideoView->MakeFocus();
910 }
911 
912 
913 void
914 MainWin::_CreateMenu()
915 {
916 	fFileMenu = new BMenu(NAME);
917 	fPlaylistMenu = new BMenu("Playlist"B_UTF8_ELLIPSIS);
918 	fAudioMenu = new BMenu("Audio");
919 	fVideoMenu = new BMenu("Video");
920 	fVideoAspectMenu = new BMenu("Aspect Ratio");
921 	fSettingsMenu = new BMenu("Settings");
922 	fAudioTrackMenu = new BMenu("Track");
923 	fVideoTrackMenu = new BMenu("Track");
924 
925 	fMenuBar->AddItem(fFileMenu);
926 	fMenuBar->AddItem(fAudioMenu);
927 	fMenuBar->AddItem(fVideoMenu);
928 	fMenuBar->AddItem(fSettingsMenu);
929 
930 	fFileMenu->AddItem(new BMenuItem("New Player"B_UTF8_ELLIPSIS,
931 		new BMessage(M_FILE_NEWPLAYER), 'N'));
932 	fFileMenu->AddSeparatorItem();
933 
934 //	fFileMenu->AddItem(new BMenuItem("Open File"B_UTF8_ELLIPSIS,
935 //		new BMessage(M_FILE_OPEN), 'O'));
936 	// Add recent files
937 	BRecentFilesList recentFiles(10, false, NULL, kAppSig);
938 	BMenuItem *item = new BMenuItem(recentFiles.NewFileListMenu(
939 		"Open File"B_UTF8_ELLIPSIS, new BMessage(B_REFS_RECEIVED),
940 		NULL, this, 10, false, NULL, 0, kAppSig), new BMessage(M_FILE_OPEN));
941 	item->SetShortcut('O', 0);
942 	fFileMenu->AddItem(item);
943 
944 	fFileMenu->AddItem(new BMenuItem("File Info"B_UTF8_ELLIPSIS,
945 		new BMessage(M_FILE_INFO), 'I'));
946 	fFileMenu->AddItem(fPlaylistMenu);
947 	fPlaylistMenu->Superitem()->SetShortcut('P', B_COMMAND_KEY);
948 	fPlaylistMenu->Superitem()->SetMessage(new BMessage(M_FILE_PLAYLIST));
949 
950 	fFileMenu->AddSeparatorItem();
951 	fFileMenu->AddItem(new BMenuItem("About " NAME B_UTF8_ELLIPSIS,
952 		new BMessage(B_ABOUT_REQUESTED)));
953 	fFileMenu->AddSeparatorItem();
954 	fFileMenu->AddItem(new BMenuItem("Close", new BMessage(M_FILE_CLOSE), 'W'));
955 	fFileMenu->AddItem(new BMenuItem("Quit", new BMessage(M_FILE_QUIT), 'Q'));
956 
957 	fPlaylistMenu->SetRadioMode(true);
958 
959 	fAudioMenu->AddItem(fAudioTrackMenu);
960 
961 	fVideoMenu->AddItem(fVideoTrackMenu);
962 	fVideoMenu->AddSeparatorItem();
963 	BMessage* resizeMessage = new BMessage(M_VIEW_SIZE);
964 	resizeMessage->AddInt32("size", 50);
965 	fVideoMenu->AddItem(new BMenuItem("50% scale", resizeMessage, '0'));
966 
967 	resizeMessage = new BMessage(M_VIEW_SIZE);
968 	resizeMessage->AddInt32("size", 100);
969 	fVideoMenu->AddItem(new BMenuItem("100% scale", resizeMessage, '1'));
970 
971 	resizeMessage = new BMessage(M_VIEW_SIZE);
972 	resizeMessage->AddInt32("size", 200);
973 	fVideoMenu->AddItem(new BMenuItem("200% scale", resizeMessage, '2'));
974 
975 	resizeMessage = new BMessage(M_VIEW_SIZE);
976 	resizeMessage->AddInt32("size", 300);
977 	fVideoMenu->AddItem(new BMenuItem("300% scale", resizeMessage, '3'));
978 
979 	resizeMessage = new BMessage(M_VIEW_SIZE);
980 	resizeMessage->AddInt32("size", 400);
981 	fVideoMenu->AddItem(new BMenuItem("400% scale", resizeMessage, '4'));
982 
983 	fVideoMenu->AddSeparatorItem();
984 
985 	fVideoMenu->AddItem(new BMenuItem("Full Screen",
986 		new BMessage(M_TOGGLE_FULLSCREEN), 'F'));
987 
988 	fVideoMenu->AddSeparatorItem();
989 
990 	_SetupVideoAspectItems(fVideoAspectMenu);
991 	fVideoMenu->AddItem(fVideoAspectMenu);
992 
993 	fNoInterfaceMenuItem = new BMenuItem("No Interface",
994 		new BMessage(M_TOGGLE_NO_INTERFACE), 'B');
995 	fSettingsMenu->AddItem(fNoInterfaceMenuItem);
996 	fSettingsMenu->AddItem(new BMenuItem("Always on Top",
997 		new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'T'));
998 	fSettingsMenu->AddSeparatorItem();
999 	item = new BMenuItem("Settings"B_UTF8_ELLIPSIS,
1000 		new BMessage(M_SETTINGS), 'S');
1001 	fSettingsMenu->AddItem(item);
1002 	item->SetTarget(be_app);
1003 }
1004 
1005 
1006 void
1007 MainWin::_SetupVideoAspectItems(BMenu* menu)
1008 {
1009 	BMenuItem* item;
1010 	while ((item = menu->RemoveItem(0L)) != NULL)
1011 		delete item;
1012 
1013 	int width;
1014 	int height;
1015 	int widthAspect;
1016 	int heightAspect;
1017 	fController->GetSize(&width, &height, &widthAspect, &heightAspect);
1018 		// We don't care if there is a video track at all. In that
1019 		// case we should end up not marking any item.
1020 
1021 	// NOTE: The item marking may end up marking for example both
1022 	// "Stream Settings" and "16 : 9" if the stream settings happen to
1023 	// be "16 : 9".
1024 
1025 	menu->AddItem(item = new BMenuItem("Stream Settings",
1026 		new BMessage(M_ASPECT_SAME_AS_SOURCE)));
1027 	item->SetMarked(widthAspect == fWidthAspect
1028 		&& heightAspect == fHeightAspect);
1029 
1030 	menu->AddItem(item = new BMenuItem("No Aspect Correction",
1031 		new BMessage(M_ASPECT_NO_DISTORTION)));
1032 	item->SetMarked(width == fWidthAspect && height == fHeightAspect);
1033 
1034 	menu->AddSeparatorItem();
1035 
1036 	menu->AddItem(item = new BMenuItem("4 : 3",
1037 		new BMessage(M_ASPECT_4_3)));
1038 	item->SetMarked(fWidthAspect == 4 && fHeightAspect == 3);
1039 	menu->AddItem(item = new BMenuItem("16 : 9",
1040 		new BMessage(M_ASPECT_16_9)));
1041 	item->SetMarked(fWidthAspect == 16 && fHeightAspect == 9);
1042 
1043 	menu->AddSeparatorItem();
1044 
1045 	menu->AddItem(item = new BMenuItem("1.66 : 1",
1046 		new BMessage(M_ASPECT_83_50)));
1047 	item->SetMarked(fWidthAspect == 83 && fHeightAspect == 50);
1048 	menu->AddItem(item = new BMenuItem("1.75 : 1",
1049 		new BMessage(M_ASPECT_7_4)));
1050 	item->SetMarked(fWidthAspect == 7 && fHeightAspect == 4);
1051 	menu->AddItem(item = new BMenuItem("1.85 : 1 (American)",
1052 		new BMessage(M_ASPECT_37_20)));
1053 	item->SetMarked(fWidthAspect == 37 && fHeightAspect == 20);
1054 	menu->AddItem(item = new BMenuItem("2.35 : 1 (Cinemascope)",
1055 		new BMessage(M_ASPECT_47_20)));
1056 	item->SetMarked(fWidthAspect == 47 && fHeightAspect == 20);
1057 }
1058 
1059 
1060 void
1061 MainWin::_SetupTrackMenus(BMenu* audioTrackMenu, BMenu* videoTrackMenu)
1062 {
1063 	audioTrackMenu->RemoveItems(0, audioTrackMenu->CountItems(), true);
1064 	videoTrackMenu->RemoveItems(0, videoTrackMenu->CountItems(), true);
1065 
1066 	char s[100];
1067 
1068 	int count = fController->AudioTrackCount();
1069 	int current = fController->CurrentAudioTrack();
1070 	for (int i = 0; i < count; i++) {
1071 		sprintf(s, "Track %d", i + 1);
1072 		BMenuItem* item = new BMenuItem(s,
1073 			new BMessage(M_SELECT_AUDIO_TRACK + i));
1074 		item->SetMarked(i == current);
1075 		audioTrackMenu->AddItem(item);
1076 	}
1077 	if (!count) {
1078 		audioTrackMenu->AddItem(new BMenuItem("none", new BMessage(M_DUMMY)));
1079 		audioTrackMenu->ItemAt(0)->SetMarked(true);
1080 	}
1081 
1082 
1083 	count = fController->VideoTrackCount();
1084 	current = fController->CurrentVideoTrack();
1085 	for (int i = 0; i < count; i++) {
1086 		sprintf(s, "Track %d", i + 1);
1087 		BMenuItem* item = new BMenuItem(s,
1088 			new BMessage(M_SELECT_VIDEO_TRACK + i));
1089 		item->SetMarked(i == current);
1090 		videoTrackMenu->AddItem(item);
1091 	}
1092 	if (!count) {
1093 		videoTrackMenu->AddItem(new BMenuItem("none", new BMessage(M_DUMMY)));
1094 		videoTrackMenu->ItemAt(0)->SetMarked(true);
1095 	}
1096 }
1097 
1098 
1099 void
1100 MainWin::_GetMinimumWindowSize(int& width, int& height) const
1101 {
1102 	width = MIN_WIDTH;
1103 	height = 0;
1104 	if (!fNoInterface) {
1105 		width = max_c(width, fMenuBarWidth);
1106 		width = max_c(width, fControlsWidth);
1107 		height = fMenuBarHeight + fControlsHeight;
1108 	}
1109 }
1110 
1111 
1112 void
1113 MainWin::_GetUnscaledVideoSize(int& videoWidth, int& videoHeight) const
1114 {
1115 	if (fWidthAspect != 0 && fHeightAspect != 0) {
1116 		videoWidth = fSourceHeight * fWidthAspect / fHeightAspect;
1117 		videoHeight = fSourceWidth * fHeightAspect / fWidthAspect;
1118 		// Use the scaling which produces an enlarged view.
1119 		if (videoWidth > fSourceWidth) {
1120 			// Enlarge width
1121 			videoHeight = fSourceHeight;
1122 		} else {
1123 			// Enlarge height
1124 			videoWidth = fSourceWidth;
1125 		}
1126 	} else {
1127 		videoWidth = fSourceWidth;
1128 		videoHeight = fSourceHeight;
1129 	}
1130 }
1131 
1132 void
1133 MainWin::_SetWindowSizeLimits()
1134 {
1135 	int minWidth;
1136 	int minHeight;
1137 	_GetMinimumWindowSize(minWidth, minHeight);
1138 	SetSizeLimits(minWidth - 1, 32000, minHeight - 1, fHasVideo ?
1139 		32000 : minHeight - 1);
1140 }
1141 
1142 
1143 int
1144 MainWin::_CurrentVideoSizeInPercent() const
1145 {
1146 	if (!fHasVideo)
1147 		return 0;
1148 
1149 	int videoWidth;
1150 	int videoHeight;
1151 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1152 
1153 	int viewWidth = fVideoView->Bounds().IntegerWidth() + 1;
1154 	int viewHeight = fVideoView->Bounds().IntegerHeight() + 1;
1155 
1156 	int widthPercent = videoWidth * 100 / viewWidth;
1157 	int heightPercent = videoHeight * 100 / viewHeight;
1158 
1159 	if (widthPercent > heightPercent)
1160 		return widthPercent;
1161 	return heightPercent;
1162 }
1163 
1164 
1165 void
1166 MainWin::_ResizeWindow(int percent, bool stayOnScreen)
1167 {
1168 	// Get required window size
1169 	int videoWidth;
1170 	int videoHeight;
1171 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1172 
1173 	videoWidth = (videoWidth * percent) / 100;
1174 	videoHeight = (videoHeight * percent) / 100;
1175 
1176 	// Calculate and set the minimum window size
1177 	int width;
1178 	int height;
1179 	_GetMinimumWindowSize(width, height);
1180 
1181 	width = max_c(width, videoWidth) - 1;
1182 	height = height + videoHeight - 1;
1183 
1184 	if (stayOnScreen) {
1185 		BRect screenFrame(BScreen(this).Frame());
1186 		BRect frame(Frame());
1187 		BRect decoratorFrame(DecoratorFrame());
1188 
1189 		// Shrink the screen frame by the window border size
1190 		screenFrame.top += frame.top - decoratorFrame.top;
1191 		screenFrame.left += frame.left - decoratorFrame.left;
1192 		screenFrame.right += frame.right - decoratorFrame.right;
1193 		screenFrame.bottom += frame.bottom - decoratorFrame.bottom;
1194 
1195 		// Update frame to what the new size would be
1196 		frame.right = frame.left + width;
1197 		frame.bottom = frame.top + height;
1198 
1199 		if (!screenFrame.Contains(frame)) {
1200 			// Resize the window so it doesn't extend outside the current
1201 			// screen frame.
1202 			if (frame.Width() > screenFrame.Width()
1203 				|| frame.Height() > screenFrame.Height()) {
1204 				// too large
1205 				int widthDiff
1206 					= frame.IntegerWidth() - screenFrame.IntegerWidth();
1207 				int heightDiff
1208 					= frame.IntegerHeight() - screenFrame.IntegerHeight();
1209 
1210 				float shrinkScale;
1211 				if (widthDiff > heightDiff)
1212 					shrinkScale = (float)(width - widthDiff) / width;
1213 				else
1214 					shrinkScale = (float)(height - heightDiff) / height;
1215 
1216 				// Resize width/height and center window
1217 				width = lround(width * shrinkScale);
1218 				height = lround(height * shrinkScale);
1219 				MoveTo((screenFrame.left + screenFrame.right - width) / 2,
1220 					(screenFrame.top + screenFrame.bottom - height) / 2);
1221 			} else {
1222 				// just off-screen on one or more sides
1223 				int offsetX = 0;
1224 				int offsetY = 0;
1225 				if (frame.left < screenFrame.left)
1226 					offsetX = (int)(screenFrame.left - frame.left);
1227 				else if (frame.right > screenFrame.right)
1228 					offsetX = (int)(screenFrame.right - frame.right);
1229 				if (frame.top < screenFrame.top)
1230 					offsetX = (int)(screenFrame.top - frame.top);
1231 				else if (frame.bottom > screenFrame.bottom)
1232 					offsetX = (int)(screenFrame.bottom - frame.bottom);
1233 				MoveBy(offsetX, offsetY);
1234 			}
1235 		}
1236 	}
1237 
1238 	ResizeTo(width, height);
1239 }
1240 
1241 
1242 void
1243 MainWin::_ResizeVideoView(int x, int y, int width, int height)
1244 {
1245 	printf("_ResizeVideoView: %d,%d, width %d, height %d\n", x, y,
1246 		width, height);
1247 
1248 	// Keep aspect ratio, place video view inside
1249 	// the background area (may create black bars).
1250 	int videoWidth;
1251 	int videoHeight;
1252 	_GetUnscaledVideoSize(videoWidth, videoHeight);
1253 	float scaledWidth  = videoWidth;
1254 	float scaledHeight = videoHeight;
1255 	float factor = min_c(width / scaledWidth, height / scaledHeight);
1256 	int renderWidth = lround(scaledWidth * factor);
1257 	int renderHeight = lround(scaledHeight * factor);
1258 	if (renderWidth > width)
1259 		renderWidth = width;
1260 	if (renderHeight > height)
1261 		renderHeight = height;
1262 
1263 	int xOffset = x + (width - renderWidth) / 2;
1264 	int yOffset = y + (height - renderHeight) / 2;
1265 
1266 	fVideoView->MoveTo(xOffset, yOffset);
1267 	fVideoView->ResizeTo(renderWidth - 1, renderHeight - 1);
1268 }
1269 
1270 
1271 // #pragma mark -
1272 
1273 
1274 void
1275 MainWin::_MouseDown(BMessage *msg, BView* originalHandler)
1276 {
1277 	BPoint screen_where;
1278 	uint32 buttons = msg->FindInt32("buttons");
1279 
1280 	// On Zeta, only "screen_where" is relyable, "where" and "be:view_where"
1281 	// seem to be broken
1282 	if (B_OK != msg->FindPoint("screen_where", &screen_where)) {
1283 		// Workaround for BeOS R5, it has no "screen_where"
1284 		if (!originalHandler || msg->FindPoint("where", &screen_where) < B_OK)
1285 			return;
1286 		originalHandler->ConvertToScreen(&screen_where);
1287 	}
1288 
1289 //	msg->PrintToStream();
1290 
1291 //	if (1 == msg->FindInt32("buttons") && msg->FindInt32("clicks") == 1) {
1292 
1293 	if (1 == buttons && msg->FindInt32("clicks") % 2 == 0) {
1294 		BRect r(screen_where.x - 1, screen_where.y - 1, screen_where.x + 1,
1295 			screen_where.y + 1);
1296 		if (r.Contains(fMouseDownMousePos)) {
1297 			PostMessage(M_TOGGLE_FULLSCREEN);
1298 			return;
1299 		}
1300 	}
1301 
1302 	if (2 == buttons && msg->FindInt32("clicks") % 2 == 0) {
1303 		BRect r(screen_where.x - 1, screen_where.y - 1, screen_where.x + 1,
1304 			screen_where.y + 1);
1305 		if (r.Contains(fMouseDownMousePos)) {
1306 			PostMessage(M_TOGGLE_NO_INTERFACE);
1307 			return;
1308 		}
1309 	}
1310 
1311 /*
1312 		// very broken in Zeta:
1313 		fMouseDownMousePos = fVideoView->ConvertToScreen(
1314 			msg->FindPoint("where"));
1315 */
1316 	fMouseDownMousePos = screen_where;
1317 	fMouseDownWindowPos = Frame().LeftTop();
1318 
1319 	if (buttons == 1 && !fIsFullscreen) {
1320 		// start mouse tracking
1321 		fVideoView->SetMouseEventMask(B_POINTER_EVENTS | B_NO_POINTER_HISTORY
1322 			/* | B_LOCK_WINDOW_FOCUS */);
1323 		fMouseDownTracking = true;
1324 	}
1325 
1326 	// pop up a context menu if right mouse button is down for 200 ms
1327 
1328 	if ((buttons & 2) == 0)
1329 		return;
1330 	bigtime_t start = system_time();
1331 	bigtime_t delay = 200000;
1332 	BPoint location;
1333 	do {
1334 		fVideoView->GetMouse(&location, &buttons);
1335 		if ((buttons & 2) == 0)
1336 			break;
1337 		snooze(1000);
1338 	} while (system_time() - start < delay);
1339 
1340 	if (buttons & 2)
1341 		_ShowContextMenu(screen_where);
1342 }
1343 
1344 
1345 void
1346 MainWin::_MouseMoved(BMessage *msg, BView* originalHandler)
1347 {
1348 //	msg->PrintToStream();
1349 
1350 	BPoint mousePos;
1351 	uint32 buttons = msg->FindInt32("buttons");
1352 
1353 	if (1 == buttons && fMouseDownTracking && !fIsFullscreen) {
1354 /*
1355 		// very broken in Zeta:
1356 		BPoint mousePos = msg->FindPoint("where");
1357 		printf("view where: %.0f, %.0f => ", mousePos.x, mousePos.y);
1358 		fVideoView->ConvertToScreen(&mousePos);
1359 */
1360 		// On Zeta, only "screen_where" is relyable, "where"
1361 		// and "be:view_where" seem to be broken
1362 		if (B_OK != msg->FindPoint("screen_where", &mousePos)) {
1363 			// Workaround for BeOS R5, it has no "screen_where"
1364 			if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK)
1365 				return;
1366 			originalHandler->ConvertToScreen(&mousePos);
1367 		}
1368 //		printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y);
1369 		float delta_x = mousePos.x - fMouseDownMousePos.x;
1370 		float delta_y = mousePos.y - fMouseDownMousePos.y;
1371 		float x = fMouseDownWindowPos.x + delta_x;
1372 		float y = fMouseDownWindowPos.y + delta_y;
1373 //		printf("move window to %.0f, %.0f\n", x, y);
1374 		MoveTo(x, y);
1375 	}
1376 }
1377 
1378 
1379 void
1380 MainWin::_MouseUp(BMessage *msg)
1381 {
1382 //	msg->PrintToStream();
1383 	fMouseDownTracking = false;
1384 }
1385 
1386 
1387 void
1388 MainWin::_ShowContextMenu(const BPoint &screen_point)
1389 {
1390 	printf("Show context menu\n");
1391 	BPopUpMenu *menu = new BPopUpMenu("context menu", false, false);
1392 	BMenuItem *item;
1393 	menu->AddItem(item = new BMenuItem("Full Screen",
1394 		new BMessage(M_TOGGLE_FULLSCREEN), 'F'));
1395 	item->SetMarked(fIsFullscreen);
1396 	item->SetEnabled(fHasVideo);
1397 
1398 	BMenu* aspectSubMenu = new BMenu("Aspect Ratio");
1399 	_SetupVideoAspectItems(aspectSubMenu);
1400 	aspectSubMenu->SetTargetForItems(this);
1401 	menu->AddItem(item = new BMenuItem(aspectSubMenu));
1402 	item->SetEnabled(fHasVideo);
1403 
1404 	menu->AddItem(item = new BMenuItem("No Interface",
1405 		new BMessage(M_TOGGLE_NO_INTERFACE), 'B'));
1406 	item->SetMarked(fNoInterface);
1407 	item->SetEnabled(fHasVideo);
1408 
1409 	menu->AddSeparatorItem();
1410 
1411 	// Add track selector menus
1412 	BMenu* audioTrackMenu = new BMenu("Audio Track");
1413 	BMenu* videoTrackMenu = new BMenu("Video Track");
1414 	_SetupTrackMenus(audioTrackMenu, videoTrackMenu);
1415 
1416 	audioTrackMenu->SetTargetForItems(this);
1417 	videoTrackMenu->SetTargetForItems(this);
1418 
1419 	menu->AddItem(item = new BMenuItem(audioTrackMenu));
1420 	item->SetEnabled(fHasAudio);
1421 
1422 	menu->AddItem(item = new BMenuItem(videoTrackMenu));
1423 	item->SetEnabled(fHasVideo);
1424 
1425 	menu->AddSeparatorItem();
1426 
1427 	menu->AddItem(new BMenuItem("About " NAME B_UTF8_ELLIPSIS,
1428 		new BMessage(B_ABOUT_REQUESTED)));
1429 	menu->AddSeparatorItem();
1430 	menu->AddItem(new BMenuItem("Quit", new BMessage(M_FILE_QUIT), 'Q'));
1431 
1432 	menu->SetTargetForItems(this);
1433 	BRect r(screen_point.x - 5, screen_point.y - 5, screen_point.x + 5,
1434 		screen_point.y + 5);
1435 	menu->Go(screen_point, true, true, r, true);
1436 }
1437 
1438 
1439 /*!	Trap keys that are about to be send to background or renderer view.
1440 	Return true if it shouldn't be passed to the view.
1441 */
1442 bool
1443 MainWin::_KeyDown(BMessage *msg)
1444 {
1445 	// TODO: use the shortcut mechanism instead!
1446 
1447 	uint32 key = msg->FindInt32("key");
1448 	uint32 rawChar = msg->FindInt32("raw_char");
1449 	uint32 modifier = msg->FindInt32("modifiers");
1450 
1451 	printf("key 0x%lx, rawChar 0x%lx, modifiers 0x%lx\n", key, rawChar,
1452 		modifier);
1453 
1454 	// ignore the system modifier namespace
1455 	if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
1456 			== (B_CONTROL_KEY | B_COMMAND_KEY))
1457 		return false;
1458 
1459 	switch (rawChar) {
1460 		case B_SPACE:
1461 			fController->TogglePlaying();
1462 			return true;
1463 
1464 		case B_ESCAPE:
1465 			if (!fIsFullscreen)
1466 				break;
1467 
1468 			PostMessage(M_TOGGLE_FULLSCREEN);
1469 			return true;
1470 
1471 		case B_ENTER:		// Enter / Return
1472 			if (modifier & B_COMMAND_KEY) {
1473 				PostMessage(M_TOGGLE_FULLSCREEN);
1474 				return true;
1475 			} else
1476 				break;
1477 
1478 		case B_TAB:
1479 			if ((modifier & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
1480 					| B_MENU_KEY)) == 0) {
1481 				PostMessage(M_TOGGLE_FULLSCREEN);
1482 				return true;
1483 			} else
1484 				break;
1485 
1486 		case B_UP_ARROW:
1487 			if ((modifier & B_COMMAND_KEY) != 0)
1488 				PostMessage(M_SKIP_NEXT);
1489 			else
1490 				PostMessage(M_VOLUME_UP);
1491 			return true;
1492 
1493 		case B_DOWN_ARROW:
1494 			if ((modifier & B_COMMAND_KEY) != 0)
1495 				PostMessage(M_SKIP_PREV);
1496 			else
1497 				PostMessage(M_VOLUME_DOWN);
1498 			return true;
1499 
1500 		case B_RIGHT_ARROW:
1501 			if ((modifier & B_COMMAND_KEY) != 0)
1502 				PostMessage(M_VOLUME_UP);
1503 			else
1504 				PostMessage(M_SKIP_NEXT);
1505 			return true;
1506 
1507 		case B_LEFT_ARROW:
1508 			if ((modifier & B_COMMAND_KEY) != 0)
1509 				PostMessage(M_VOLUME_DOWN);
1510 			else
1511 				PostMessage(M_SKIP_PREV);
1512 			return true;
1513 
1514 		case B_PAGE_UP:
1515 			PostMessage(M_SKIP_NEXT);
1516 			return true;
1517 
1518 		case B_PAGE_DOWN:
1519 			PostMessage(M_SKIP_PREV);
1520 			return true;
1521 	}
1522 
1523 	switch (key) {
1524 		case 0x3a:  		// numeric keypad +
1525 			if ((modifier & B_COMMAND_KEY) == 0) {
1526 				PostMessage(M_VOLUME_UP);
1527 				return true;
1528 			} else {
1529 				break;
1530 			}
1531 
1532 		case 0x25:  		// numeric keypad -
1533 			if ((modifier & B_COMMAND_KEY) == 0) {
1534 				PostMessage(M_VOLUME_DOWN);
1535 				return true;
1536 			} else {
1537 				break;
1538 			}
1539 
1540 		case 0x38:			// numeric keypad up arrow
1541 			PostMessage(M_VOLUME_UP);
1542 			return true;
1543 
1544 		case 0x59:			// numeric keypad down arrow
1545 			PostMessage(M_VOLUME_DOWN);
1546 			return true;
1547 
1548 		case 0x39:			// numeric keypad page up
1549 		case 0x4a:			// numeric keypad right arrow
1550 			PostMessage(M_SKIP_NEXT);
1551 			return true;
1552 
1553 		case 0x5a:			// numeric keypad page down
1554 		case 0x48:			// numeric keypad left arrow
1555 			PostMessage(M_SKIP_PREV);
1556 			return true;
1557 
1558 		case 0x34:			// delete button
1559 		case 0x3e: 			// d for delete
1560 		case 0x2b:			// t for Trash
1561 			if ((modifiers() & B_COMMAND_KEY) != 0) {
1562 				BAutolock _(fPlaylist);
1563 				BMessage removeMessage(M_PLAYLIST_REMOVE_AND_PUT_INTO_TRASH);
1564 				removeMessage.AddInt32("playlist index",
1565 					fPlaylist->CurrentItemIndex());
1566 				fPlaylistWindow->PostMessage(&removeMessage);
1567 				return true;
1568 			}
1569 			break;
1570 	}
1571 
1572 	return false;
1573 }
1574 
1575 
1576 // #pragma mark -
1577 
1578 
1579 void
1580 MainWin::_ToggleFullscreen()
1581 {
1582 	printf("_ToggleFullscreen enter\n");
1583 
1584 	if (!fHasVideo) {
1585 		printf("_ToggleFullscreen - ignoring, as we don't have a video\n");
1586 		return;
1587 	}
1588 
1589 	fIsFullscreen = !fIsFullscreen;
1590 
1591 	if (fIsFullscreen) {
1592 		// switch to fullscreen
1593 
1594 		fSavedFrame = Frame();
1595 		printf("saving current frame: %d %d %d %d\n", int(fSavedFrame.left),
1596 			int(fSavedFrame.top), int(fSavedFrame.right),
1597 			int(fSavedFrame.bottom));
1598 		BScreen screen(this);
1599 		BRect rect(screen.Frame());
1600 
1601 		Hide();
1602 		MoveTo(rect.left, rect.top);
1603 		ResizeTo(rect.Width(), rect.Height());
1604 		Show();
1605 
1606 	} else {
1607 		// switch back from full screen mode
1608 
1609 		Hide();
1610 		MoveTo(fSavedFrame.left, fSavedFrame.top);
1611 		ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
1612 		Show();
1613 	}
1614 
1615 	fVideoView->SetFullscreen(fIsFullscreen);
1616 
1617 	_MarkItem(fSettingsMenu, M_TOGGLE_FULLSCREEN, fIsFullscreen);
1618 
1619 	printf("_ToggleFullscreen leave\n");
1620 }
1621 
1622 void
1623 MainWin::_ToggleAlwaysOnTop()
1624 {
1625 	fAlwaysOnTop = !fAlwaysOnTop;
1626 	SetFeel(fAlwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL);
1627 
1628 	_MarkItem(fSettingsMenu, M_TOGGLE_ALWAYS_ON_TOP, fAlwaysOnTop);
1629 }
1630 
1631 
1632 void
1633 MainWin::_ToggleNoInterface()
1634 {
1635 	printf("_ToggleNoInterface enter\n");
1636 
1637 	if (fIsFullscreen || !fHasVideo) {
1638 		// Fullscreen playback is always without interface and
1639 		// audio playback is always with interface. So we ignore these
1640 		// two states here.
1641 		printf("_ToggleNoControls leave, doing nothing, we are fullscreen\n");
1642 		return;
1643 	}
1644 
1645 	fNoInterface = !fNoInterface;
1646 	_SetWindowSizeLimits();
1647 
1648 	if (fNoInterface) {
1649 		MoveBy(0, fMenuBarHeight);
1650 		ResizeBy(0, -(fControlsHeight + fMenuBarHeight));
1651 		SetLook(B_BORDERED_WINDOW_LOOK);
1652 	} else {
1653 		MoveBy(0, -fMenuBarHeight);
1654 		ResizeBy(0, fControlsHeight + fMenuBarHeight);
1655 		SetLook(B_TITLED_WINDOW_LOOK);
1656 	}
1657 
1658 	_MarkItem(fSettingsMenu, M_TOGGLE_NO_INTERFACE, fNoInterface);
1659 
1660 	printf("_ToggleNoInterface leave\n");
1661 }
1662 
1663 
1664 // #pragma mark -
1665 
1666 
1667 void
1668 MainWin::_UpdateControlsEnabledStatus()
1669 {
1670 	uint32 enabledButtons = 0;
1671 	if (fHasVideo || fHasAudio) {
1672 		enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED
1673 			| SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED;
1674 	}
1675 	if (fHasAudio)
1676 		enabledButtons |= VOLUME_ENABLED;
1677 
1678 	BAutolock _(fPlaylist);
1679 	bool canSkipPrevious, canSkipNext;
1680 	fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext);
1681 	if (canSkipPrevious)
1682 		enabledButtons |= SKIP_BACK_ENABLED;
1683 	if (canSkipNext)
1684 		enabledButtons |= SKIP_FORWARD_ENABLED;
1685 
1686 	fControls->SetEnabled(enabledButtons);
1687 
1688 	fNoInterfaceMenuItem->SetEnabled(fHasVideo);
1689 }
1690 
1691 
1692 void
1693 MainWin::_UpdatePlaylistMenu()
1694 {
1695 	if (!fPlaylist->Lock())
1696 		return;
1697 
1698 	fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true);
1699 
1700 	int32 count = fPlaylist->CountItems();
1701 	for (int32 i = 0; i < count; i++) {
1702 		PlaylistItem* item = fPlaylist->ItemAtFast(i);
1703 		_AddPlaylistItem(item, i);
1704 	}
1705 	fPlaylistMenu->SetTargetForItems(this);
1706 
1707 	_MarkPlaylistItem(fPlaylist->CurrentItemIndex());
1708 
1709 	fPlaylist->Unlock();
1710 }
1711 
1712 
1713 void
1714 MainWin::_AddPlaylistItem(PlaylistItem* item, int32 index)
1715 {
1716 	BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION);
1717 	message->AddInt32("index", index);
1718 	BMenuItem* menuItem = new BMenuItem(item->Name().String(), message);
1719 	fPlaylistMenu->AddItem(menuItem, index);
1720 }
1721 
1722 
1723 void
1724 MainWin::_RemovePlaylistItem(int32 index)
1725 {
1726 	delete fPlaylistMenu->RemoveItem(index);
1727 }
1728 
1729 
1730 void
1731 MainWin::_MarkPlaylistItem(int32 index)
1732 {
1733 	if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) {
1734 		item->SetMarked(true);
1735 		// ... and in case the menu is currently on screen:
1736 		if (fPlaylistMenu->LockLooper()) {
1737 			fPlaylistMenu->Invalidate();
1738 			fPlaylistMenu->UnlockLooper();
1739 		}
1740 	}
1741 }
1742 
1743 
1744 void
1745 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark)
1746 {
1747 	if (BMenuItem* item = menu->FindItem(command))
1748 		item->SetMarked(mark);
1749 }
1750 
1751 
1752 void
1753 MainWin::_AdoptGlobalSettings()
1754 {
1755 	mpSettings settings = Settings::CurrentSettings();
1756 		// thread safe
1757 
1758 	fCloseWhenDonePlayingMovie = settings.closeWhenDonePlayingMovie;
1759 	fCloseWhenDonePlayingSound = settings.closeWhenDonePlayingSound;
1760 }
1761 
1762 
1763