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