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