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