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