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