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