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