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