xref: /haiku/src/apps/mediaplayer/MainWin.cpp (revision 4c07199d8201fcf267e90be0d24b76799d03cea6)
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 	BMessage* message = new BMessage(M_SET_RATING);
1841 	message->AddInt32("rating", 0);
1842 	fResetRatingItem = new BMenuItem(B_TRANSLATE("Reset rating"), message);
1843 	fAttributesMenu->AddItem(fResetRatingItem);
1844 }
1845 
1846 
1847 void
1848 MainWin::_SetupVideoAspectItems(BMenu* menu)
1849 {
1850 	BMenuItem* item;
1851 	while ((item = menu->RemoveItem((int32)0)) != NULL)
1852 		delete item;
1853 
1854 	int width;
1855 	int height;
1856 	int widthAspect;
1857 	int heightAspect;
1858 	fController->GetSize(&width, &height, &widthAspect, &heightAspect);
1859 		// We don't care if there is a video track at all. In that
1860 		// case we should end up not marking any item.
1861 
1862 	// NOTE: The item marking may end up marking for example both
1863 	// "Stream Settings" and "16 : 9" if the stream settings happen to
1864 	// be "16 : 9".
1865 
1866 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Stream settings"),
1867 		new BMessage(M_ASPECT_SAME_AS_SOURCE), '1', B_SHIFT_KEY));
1868 	item->SetMarked(widthAspect == fWidthAspect
1869 		&& heightAspect == fHeightAspect);
1870 
1871 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("No aspect correction"),
1872 		new BMessage(M_ASPECT_NO_DISTORTION), '0', B_SHIFT_KEY));
1873 	item->SetMarked(width == fWidthAspect && height == fHeightAspect);
1874 
1875 	menu->AddSeparatorItem();
1876 
1877 	menu->AddItem(item = new BMenuItem("4 : 3",
1878 		new BMessage(M_ASPECT_4_3), 2, B_SHIFT_KEY));
1879 	item->SetMarked(fWidthAspect == 4 && fHeightAspect == 3);
1880 	menu->AddItem(item = new BMenuItem("16 : 9",
1881 		new BMessage(M_ASPECT_16_9), 3, B_SHIFT_KEY));
1882 	item->SetMarked(fWidthAspect == 16 && fHeightAspect == 9);
1883 
1884 	menu->AddSeparatorItem();
1885 
1886 	menu->AddItem(item = new BMenuItem("1.66 : 1",
1887 		new BMessage(M_ASPECT_83_50)));
1888 	item->SetMarked(fWidthAspect == 83 && fHeightAspect == 50);
1889 	menu->AddItem(item = new BMenuItem("1.75 : 1",
1890 		new BMessage(M_ASPECT_7_4)));
1891 	item->SetMarked(fWidthAspect == 7 && fHeightAspect == 4);
1892 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("1.85 : 1 (American)"),
1893 		new BMessage(M_ASPECT_37_20)));
1894 	item->SetMarked(fWidthAspect == 37 && fHeightAspect == 20);
1895 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("2.35 : 1 (Cinemascope)"),
1896 		new BMessage(M_ASPECT_47_20)));
1897 	item->SetMarked(fWidthAspect == 47 && fHeightAspect == 20);
1898 }
1899 
1900 
1901 void
1902 MainWin::_SetupTrackMenus(BMenu* audioTrackMenu, BMenu* videoTrackMenu,
1903 	BMenu* subTitleTrackMenu)
1904 {
1905 	audioTrackMenu->RemoveItems(0, audioTrackMenu->CountItems(), true);
1906 	videoTrackMenu->RemoveItems(0, videoTrackMenu->CountItems(), true);
1907 	subTitleTrackMenu->RemoveItems(0, subTitleTrackMenu->CountItems(), true);
1908 
1909 	char s[100];
1910 
1911 	int count = fController->AudioTrackCount();
1912 	int current = fController->CurrentAudioTrack();
1913 	for (int i = 0; i < count; i++) {
1914 		BMessage metaData;
1915 		const char* languageString = NULL;
1916 		if (fController->GetAudioMetaData(i, &metaData) == B_OK)
1917 			metaData.FindString("language", &languageString);
1918 		if (languageString != NULL) {
1919 			BLanguage language(languageString);
1920 			BString languageName;
1921 			if (language.GetName(languageName) == B_OK)
1922 				languageString = languageName.String();
1923 			snprintf(s, sizeof(s), "%s", languageString);
1924 		} else
1925 			snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1926 		BMenuItem* item = new BMenuItem(s,
1927 			new BMessage(M_SELECT_AUDIO_TRACK + i));
1928 		item->SetMarked(i == current);
1929 		audioTrackMenu->AddItem(item);
1930 	}
1931 	if (count == 0) {
1932 		audioTrackMenu->AddItem(new BMenuItem(B_TRANSLATE_CONTEXT("none",
1933 			"Audio track menu"), new BMessage(M_DUMMY)));
1934 		audioTrackMenu->ItemAt(0)->SetMarked(true);
1935 	}
1936 	audioTrackMenu->SetEnabled(count > 1);
1937 
1938 	count = fController->VideoTrackCount();
1939 	current = fController->CurrentVideoTrack();
1940 	for (int i = 0; i < count; i++) {
1941 		snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1942 		BMenuItem* item = new BMenuItem(s,
1943 			new BMessage(M_SELECT_VIDEO_TRACK + i));
1944 		item->SetMarked(i == current);
1945 		videoTrackMenu->AddItem(item);
1946 	}
1947 	if (count == 0) {
1948 		videoTrackMenu->AddItem(new BMenuItem(B_TRANSLATE("none"),
1949 			new BMessage(M_DUMMY)));
1950 		videoTrackMenu->ItemAt(0)->SetMarked(true);
1951 	}
1952 	videoTrackMenu->SetEnabled(count > 1);
1953 
1954 	count = fController->SubTitleTrackCount();
1955 	if (count > 0) {
1956 		current = fController->CurrentSubTitleTrack();
1957 		BMenuItem* item = new BMenuItem(
1958 			B_TRANSLATE_CONTEXT("Off", "Subtitles menu"),
1959 			new BMessage(M_SELECT_SUB_TITLE_TRACK - 1));
1960 		subTitleTrackMenu->AddItem(item);
1961 		item->SetMarked(current == -1);
1962 
1963 		subTitleTrackMenu->AddSeparatorItem();
1964 
1965 		for (int i = 0; i < count; i++) {
1966 			const char* name = fController->SubTitleTrackName(i);
1967 			if (name != NULL)
1968 				snprintf(s, sizeof(s), "%s", name);
1969 			else
1970 				snprintf(s, sizeof(s), B_TRANSLATE("Track %d"), i + 1);
1971 			item = new BMenuItem(s,
1972 				new BMessage(M_SELECT_SUB_TITLE_TRACK + i));
1973 			item->SetMarked(i == current);
1974 			subTitleTrackMenu->AddItem(item);
1975 		}
1976 	} else {
1977 		subTitleTrackMenu->AddItem(new BMenuItem(
1978 			B_TRANSLATE_CONTEXT("none", "Subtitles menu"),
1979 			new BMessage(M_DUMMY)));
1980 		subTitleTrackMenu->ItemAt(0)->SetMarked(true);
1981 	}
1982 	subTitleTrackMenu->SetEnabled(count > 0);
1983 }
1984 
1985 
1986 void
1987 MainWin::_UpdateAudioChannelCount(int32 audioTrackIndex)
1988 {
1989 	fControls->SetAudioChannelCount(fController->AudioTrackChannelCount());
1990 }
1991 
1992 
1993 void
1994 MainWin::_GetMinimumWindowSize(int& width, int& height) const
1995 {
1996 	width = MIN_WIDTH;
1997 	height = 0;
1998 	if (!fNoInterface) {
1999 		width = max_c(width, fMenuBarWidth);
2000 		width = max_c(width, fControlsWidth);
2001 		height = fMenuBarHeight + fControlsHeight;
2002 	}
2003 }
2004 
2005 
2006 void
2007 MainWin::_GetUnscaledVideoSize(int& videoWidth, int& videoHeight) const
2008 {
2009 	if (fWidthAspect != 0 && fHeightAspect != 0) {
2010 		videoWidth = fSourceHeight * fWidthAspect / fHeightAspect;
2011 		videoHeight = fSourceWidth * fHeightAspect / fWidthAspect;
2012 		// Use the scaling which produces an enlarged view.
2013 		if (videoWidth > fSourceWidth) {
2014 			// Enlarge width
2015 			videoHeight = fSourceHeight;
2016 		} else {
2017 			// Enlarge height
2018 			videoWidth = fSourceWidth;
2019 		}
2020 	} else {
2021 		videoWidth = fSourceWidth;
2022 		videoHeight = fSourceHeight;
2023 	}
2024 }
2025 
2026 
2027 void
2028 MainWin::_SetWindowSizeLimits()
2029 {
2030 	int minWidth;
2031 	int minHeight;
2032 	_GetMinimumWindowSize(minWidth, minHeight);
2033 	SetSizeLimits(minWidth - 1, 32000, minHeight - 1,
2034 		fHasVideo ? 32000 : minHeight - 1);
2035 }
2036 
2037 
2038 int
2039 MainWin::_CurrentVideoSizeInPercent() const
2040 {
2041 	if (!fHasVideo)
2042 		return 0;
2043 
2044 	int videoWidth;
2045 	int videoHeight;
2046 	_GetUnscaledVideoSize(videoWidth, videoHeight);
2047 
2048 	int viewWidth = fVideoView->Bounds().IntegerWidth() + 1;
2049 	int viewHeight = fVideoView->Bounds().IntegerHeight() + 1;
2050 
2051 	int widthPercent = viewWidth * 100 / videoWidth;
2052 	int heightPercent = viewHeight * 100 / videoHeight;
2053 
2054 	if (widthPercent > heightPercent)
2055 		return widthPercent;
2056 	return heightPercent;
2057 }
2058 
2059 
2060 void
2061 MainWin::_ZoomVideoView(int percentDiff)
2062 {
2063 	if (!fHasVideo)
2064 		return;
2065 
2066 	int percent = _CurrentVideoSizeInPercent();
2067 	int newSize = percent * (100 + percentDiff) / 100;
2068 
2069 	if (newSize < 25)
2070 		newSize = 25;
2071 	if (newSize > 400)
2072 		newSize = 400;
2073 	if (newSize != percent) {
2074 		BMessage message(M_VIEW_SIZE);
2075 		message.AddInt32("size", newSize);
2076 		PostMessage(&message);
2077 	}
2078 }
2079 
2080 
2081 void
2082 MainWin::_ResizeWindow(int percent, bool useNoVideoWidth, bool stayOnScreen)
2083 {
2084 	// Get required window size
2085 	int videoWidth;
2086 	int videoHeight;
2087 	_GetUnscaledVideoSize(videoWidth, videoHeight);
2088 
2089 	videoWidth = (videoWidth * percent) / 100;
2090 	videoHeight = (videoHeight * percent) / 100;
2091 
2092 	// Calculate and set the minimum window size
2093 	int width;
2094 	int height;
2095 	_GetMinimumWindowSize(width, height);
2096 
2097 	width = max_c(width, videoWidth) - 1;
2098 	if (useNoVideoWidth)
2099 		width = max_c(width, fNoVideoWidth);
2100 	height = height + videoHeight - 1;
2101 
2102 	if (stayOnScreen) {
2103 		BRect screenFrame(BScreen(this).Frame());
2104 		BRect frame(Frame());
2105 		BRect decoratorFrame(DecoratorFrame());
2106 
2107 		// Shrink the screen frame by the window border size
2108 		screenFrame.top += frame.top - decoratorFrame.top;
2109 		screenFrame.left += frame.left - decoratorFrame.left;
2110 		screenFrame.right += frame.right - decoratorFrame.right;
2111 		screenFrame.bottom += frame.bottom - decoratorFrame.bottom;
2112 
2113 		// Update frame to what the new size would be
2114 		frame.right = frame.left + width;
2115 		frame.bottom = frame.top + height;
2116 
2117 		if (!screenFrame.Contains(frame)) {
2118 			// Resize the window so it doesn't extend outside the current
2119 			// screen frame.
2120 			// We don't use BWindow::MoveOnScreen() in order to resize the
2121 			// window while keeping the same aspect ratio.
2122 			if (frame.Width() > screenFrame.Width()
2123 				|| frame.Height() > screenFrame.Height()) {
2124 				// too large
2125 				int widthDiff
2126 					= frame.IntegerWidth() - screenFrame.IntegerWidth();
2127 				int heightDiff
2128 					= frame.IntegerHeight() - screenFrame.IntegerHeight();
2129 
2130 				float shrinkScale;
2131 				if (widthDiff > heightDiff)
2132 					shrinkScale = (float)(width - widthDiff) / width;
2133 				else
2134 					shrinkScale = (float)(height - heightDiff) / height;
2135 
2136 				// Resize width/height and center window
2137 				width = lround(width * shrinkScale);
2138 				height = lround(height * shrinkScale);
2139 				MoveTo((screenFrame.left + screenFrame.right - width) / 2,
2140 					(screenFrame.top + screenFrame.bottom - height) / 2);
2141 			} else {
2142 				// just off-screen on one or more sides
2143 				int offsetX = 0;
2144 				int offsetY = 0;
2145 				if (frame.left < screenFrame.left)
2146 					offsetX = (int)(screenFrame.left - frame.left);
2147 				else if (frame.right > screenFrame.right)
2148 					offsetX = (int)(screenFrame.right - frame.right);
2149 				if (frame.top < screenFrame.top)
2150 					offsetY = (int)(screenFrame.top - frame.top);
2151 				else if (frame.bottom > screenFrame.bottom)
2152 					offsetY = (int)(screenFrame.bottom - frame.bottom);
2153 				MoveBy(offsetX, offsetY);
2154 			}
2155 		}
2156 	}
2157 
2158 	ResizeTo(width, height);
2159 }
2160 
2161 
2162 void
2163 MainWin::_ResizeVideoView(int x, int y, int width, int height)
2164 {
2165 	// Keep aspect ratio, place video view inside
2166 	// the background area (may create black bars).
2167 	int videoWidth;
2168 	int videoHeight;
2169 	_GetUnscaledVideoSize(videoWidth, videoHeight);
2170 	float scaledWidth  = videoWidth;
2171 	float scaledHeight = videoHeight;
2172 	float factor = min_c(width / scaledWidth, height / scaledHeight);
2173 	int renderWidth = lround(scaledWidth * factor);
2174 	int renderHeight = lround(scaledHeight * factor);
2175 	if (renderWidth > width)
2176 		renderWidth = width;
2177 	if (renderHeight > height)
2178 		renderHeight = height;
2179 
2180 	int xOffset = (width - renderWidth) / 2;
2181 	int yOffset = (height - renderHeight) / 2;
2182 
2183 	fVideoView->MoveTo(x, y);
2184 	fVideoView->ResizeTo(width - 1, height - 1);
2185 
2186 	BRect videoFrame(xOffset, yOffset,
2187 		xOffset + renderWidth - 1, yOffset + renderHeight - 1);
2188 
2189 	fVideoView->SetVideoFrame(videoFrame);
2190 	fVideoView->SetSubTitleMaxBottom(height - 1);
2191 }
2192 
2193 
2194 // #pragma mark -
2195 
2196 
2197 void
2198 MainWin::_MouseDown(BMessage* msg, BView* originalHandler)
2199 {
2200 	uint32 buttons = msg->FindInt32("buttons");
2201 
2202 	// On Zeta, only "screen_where" is reliable, "where" and "be:view_where"
2203 	// seem to be broken
2204 	BPoint screenWhere;
2205 	if (msg->FindPoint("screen_where", &screenWhere) != B_OK) {
2206 		// TODO: remove
2207 		// Workaround for BeOS R5, it has no "screen_where"
2208 		if (!originalHandler || msg->FindPoint("where", &screenWhere) < B_OK)
2209 			return;
2210 		originalHandler->ConvertToScreen(&screenWhere);
2211 	}
2212 
2213 	// double click handling
2214 
2215 	if (msg->FindInt32("clicks") % 2 == 0) {
2216 		BRect rect(screenWhere.x - 1, screenWhere.y - 1, screenWhere.x + 1,
2217 			screenWhere.y + 1);
2218 		if (rect.Contains(fMouseDownMousePos)) {
2219 			if (buttons == B_PRIMARY_MOUSE_BUTTON)
2220 				PostMessage(M_TOGGLE_FULLSCREEN);
2221 			else if (buttons == B_SECONDARY_MOUSE_BUTTON)
2222 				PostMessage(M_TOGGLE_NO_INTERFACE);
2223 
2224 			return;
2225 		}
2226 	}
2227 
2228 	fMouseDownMousePos = screenWhere;
2229 	fMouseDownWindowPos = Frame().LeftTop();
2230 
2231 	if (buttons == B_PRIMARY_MOUSE_BUTTON && !fIsFullscreen) {
2232 		// start mouse tracking
2233 		fVideoView->SetMouseEventMask(B_POINTER_EVENTS | B_NO_POINTER_HISTORY
2234 			/* | B_LOCK_WINDOW_FOCUS */);
2235 		fMouseDownTracking = true;
2236 	}
2237 
2238 	// pop up a context menu if right mouse button is down
2239 
2240 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
2241 		_ShowContextMenu(screenWhere);
2242 }
2243 
2244 
2245 void
2246 MainWin::_MouseMoved(BMessage* msg, BView* originalHandler)
2247 {
2248 //	msg->PrintToStream();
2249 
2250 	BPoint mousePos;
2251 	uint32 buttons = msg->FindInt32("buttons");
2252 	// On Zeta, only "screen_where" is reliable, "where"
2253 	// and "be:view_where" seem to be broken
2254 	if (msg->FindPoint("screen_where", &mousePos) != B_OK) {
2255 		// TODO: remove
2256 		// Workaround for BeOS R5, it has no "screen_where"
2257 		if (!originalHandler || msg->FindPoint("where", &mousePos) < B_OK)
2258 			return;
2259 		originalHandler->ConvertToScreen(&mousePos);
2260 	}
2261 
2262 	if (buttons == B_PRIMARY_MOUSE_BUTTON && fMouseDownTracking
2263 		&& !fIsFullscreen) {
2264 //		printf("screen where: %.0f, %.0f => ", mousePos.x, mousePos.y);
2265 		float delta_x = mousePos.x - fMouseDownMousePos.x;
2266 		float delta_y = mousePos.y - fMouseDownMousePos.y;
2267 		float x = fMouseDownWindowPos.x + delta_x;
2268 		float y = fMouseDownWindowPos.y + delta_y;
2269 //		printf("move window to %.0f, %.0f\n", x, y);
2270 		MoveTo(x, y);
2271 	}
2272 
2273 	bigtime_t eventTime;
2274 	if (msg->FindInt64("when", &eventTime) != B_OK)
2275 		eventTime = system_time();
2276 
2277 	if (buttons == 0 && fIsFullscreen) {
2278 		BPoint moveDelta = mousePos - fLastMousePos;
2279 		float moveDeltaDist
2280 			= sqrtf(moveDelta.x * moveDelta.x + moveDelta.y * moveDelta.y);
2281 		if (eventTime - fLastMouseMovedTime < 200000)
2282 			fMouseMoveDist += moveDeltaDist;
2283 		else
2284 			fMouseMoveDist = moveDeltaDist;
2285 		if (fMouseMoveDist > 5)
2286 			_ShowFullscreenControls(true);
2287 	}
2288 
2289 	fLastMousePos = mousePos;
2290 	fLastMouseMovedTime =eventTime;
2291 }
2292 
2293 
2294 void
2295 MainWin::_MouseUp(BMessage* msg)
2296 {
2297 	fMouseDownTracking = false;
2298 }
2299 
2300 
2301 void
2302 MainWin::_ShowContextMenu(const BPoint& screenPoint)
2303 {
2304 	printf("Show context menu\n");
2305 	BPopUpMenu* menu = new BPopUpMenu("context menu", false, false);
2306 	BMenuItem* item;
2307 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Full screen"),
2308 		new BMessage(M_TOGGLE_FULLSCREEN), B_ENTER));
2309 	item->SetMarked(fIsFullscreen);
2310 	item->SetEnabled(fHasVideo);
2311 
2312 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Hide interface"),
2313 		new BMessage(M_TOGGLE_NO_INTERFACE), 'H'));
2314 	item->SetMarked(fNoInterface);
2315 	item->SetEnabled(fHasVideo && !fIsFullscreen);
2316 
2317 	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Always on top"),
2318 		new BMessage(M_TOGGLE_ALWAYS_ON_TOP), 'A'));
2319 	item->SetMarked(fAlwaysOnTop);
2320 	item->SetEnabled(fHasVideo);
2321 
2322 	BMenu* aspectSubMenu = new BMenu(B_TRANSLATE("Aspect ratio"));
2323 	_SetupVideoAspectItems(aspectSubMenu);
2324 	aspectSubMenu->SetTargetForItems(this);
2325 	menu->AddItem(item = new BMenuItem(aspectSubMenu));
2326 	item->SetEnabled(fHasVideo);
2327 
2328 	menu->AddSeparatorItem();
2329 
2330 	// Add track selector menus
2331 	BMenu* audioTrackMenu = new BMenu(B_TRANSLATE("Audio track"));
2332 	BMenu* videoTrackMenu = new BMenu(B_TRANSLATE("Video track"));
2333 	BMenu* subTitleTrackMenu = new BMenu(B_TRANSLATE("Subtitles"));
2334 	_SetupTrackMenus(audioTrackMenu, videoTrackMenu, subTitleTrackMenu);
2335 
2336 	audioTrackMenu->SetTargetForItems(this);
2337 	videoTrackMenu->SetTargetForItems(this);
2338 	subTitleTrackMenu->SetTargetForItems(this);
2339 
2340 	menu->AddItem(item = new BMenuItem(audioTrackMenu));
2341 	item->SetEnabled(fHasAudio);
2342 
2343 	menu->AddItem(item = new BMenuItem(videoTrackMenu));
2344 	item->SetEnabled(fHasVideo);
2345 
2346 	menu->AddItem(item = new BMenuItem(subTitleTrackMenu));
2347 	item->SetEnabled(fHasVideo);
2348 
2349 	menu->AddSeparatorItem();
2350 	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), new BMessage(M_FILE_QUIT), 'Q'));
2351 
2352 	menu->SetTargetForItems(this);
2353 	BRect rect(screenPoint.x - 5, screenPoint.y - 5, screenPoint.x + 5,
2354 		screenPoint.y + 5);
2355 	menu->Go(screenPoint, true, true, rect, true);
2356 }
2357 
2358 
2359 /*!	Trap keys that are about to be send to background or renderer view.
2360 	Return true if it shouldn't be passed to the view.
2361 */
2362 bool
2363 MainWin::_KeyDown(BMessage* msg)
2364 {
2365 	uint32 key = msg->FindInt32("key");
2366 	uint32 rawChar = msg->FindInt32("raw_char");
2367 	uint32 modifier = msg->FindInt32("modifiers");
2368 
2369 //	printf("key 0x%lx, rawChar 0x%lx, modifiers 0x%lx\n", key, rawChar,
2370 //		modifier);
2371 
2372 	// ignore the system modifier namespace
2373 	if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
2374 			== (B_CONTROL_KEY | B_COMMAND_KEY))
2375 		return false;
2376 
2377 	switch (rawChar) {
2378 		case B_SPACE:
2379 			fController->TogglePlaying();
2380 			return true;
2381 
2382 		case 'm':
2383 			fController->ToggleMute();
2384 			return true;
2385 
2386 		case B_ESCAPE:
2387 			if (!fIsFullscreen)
2388 				break;
2389 
2390 			PostMessage(M_TOGGLE_FULLSCREEN);
2391 			return true;
2392 
2393 		case B_ENTER:		// Enter / Return
2394 			if ((modifier & B_COMMAND_KEY) != 0) {
2395 				PostMessage(M_TOGGLE_FULLSCREEN);
2396 				return true;
2397 			}
2398 			break;
2399 
2400 		case B_TAB:
2401 		case 'f':
2402 			if ((modifier & (B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY
2403 					| B_MENU_KEY)) == 0) {
2404 				PostMessage(M_TOGGLE_FULLSCREEN);
2405 				return true;
2406 			}
2407 			break;
2408 
2409 		case B_UP_ARROW:
2410 			if ((modifier & B_COMMAND_KEY) != 0)
2411 				PostMessage(M_SKIP_NEXT);
2412 			else
2413 				PostMessage(M_VOLUME_UP);
2414 			return true;
2415 
2416 		case B_DOWN_ARROW:
2417 			if ((modifier & B_COMMAND_KEY) != 0)
2418 				PostMessage(M_SKIP_PREV);
2419 			else
2420 				PostMessage(M_VOLUME_DOWN);
2421 			return true;
2422 
2423 		case B_RIGHT_ARROW:
2424 			if ((modifier & B_COMMAND_KEY) != 0)
2425 				PostMessage(M_SKIP_NEXT);
2426 			else if (fAllowWinding) {
2427 				BMessage windMessage(M_WIND);
2428 				if ((modifier & B_SHIFT_KEY) != 0) {
2429 					windMessage.AddInt64("how much", 30000000LL);
2430 					windMessage.AddInt64("frames", 5);
2431 				} else {
2432 					windMessage.AddInt64("how much", 5000000LL);
2433 					windMessage.AddInt64("frames", 1);
2434 				}
2435 				PostMessage(&windMessage);
2436 			}
2437 			return true;
2438 
2439 		case B_LEFT_ARROW:
2440 			if ((modifier & B_COMMAND_KEY) != 0)
2441 				PostMessage(M_SKIP_PREV);
2442 			else if (fAllowWinding) {
2443 				BMessage windMessage(M_WIND);
2444 				if ((modifier & B_SHIFT_KEY) != 0) {
2445 					windMessage.AddInt64("how much", -30000000LL);
2446 					windMessage.AddInt64("frames", -5);
2447 				} else {
2448 					windMessage.AddInt64("how much", -5000000LL);
2449 					windMessage.AddInt64("frames", -1);
2450 				}
2451 				PostMessage(&windMessage);
2452 			}
2453 			return true;
2454 
2455 		case B_PAGE_UP:
2456 			PostMessage(M_SKIP_NEXT);
2457 			return true;
2458 
2459 		case B_PAGE_DOWN:
2460 			PostMessage(M_SKIP_PREV);
2461 			return true;
2462 
2463 		case '+':
2464 			if ((modifier & B_COMMAND_KEY) == 0) {
2465 				_ZoomVideoView(10);
2466 				return true;
2467 			}
2468 			break;
2469 
2470 		case '-':
2471 			if ((modifier & B_COMMAND_KEY) == 0) {
2472 				_ZoomVideoView(-10);
2473 				return true;
2474 			}
2475 			break;
2476 
2477 		case B_DELETE:
2478 		case 'd': 			// d for delete
2479 		case 't':			// t for Trash
2480 			if ((modifiers() & B_COMMAND_KEY) != 0) {
2481 				BAutolock _(fPlaylist);
2482 				BMessage removeMessage(M_PLAYLIST_MOVE_TO_TRASH);
2483 				removeMessage.AddInt32("playlist index",
2484 					fPlaylist->CurrentItemIndex());
2485 				fPlaylistWindow->PostMessage(&removeMessage);
2486 				return true;
2487 			}
2488 			break;
2489 	}
2490 
2491 	switch (key) {
2492 		case 0x3a:  		// numeric keypad +
2493 			if ((modifier & B_COMMAND_KEY) == 0) {
2494 				_ZoomVideoView(10);
2495 				return true;
2496 			}
2497 			break;
2498 
2499 		case 0x25:  		// numeric keypad -
2500 			if ((modifier & B_COMMAND_KEY) == 0) {
2501 				_ZoomVideoView(-10);
2502 				return true;
2503 			}
2504 			break;
2505 
2506 		case 0x38:			// numeric keypad up arrow
2507 			PostMessage(M_VOLUME_UP);
2508 			return true;
2509 
2510 		case 0x59:			// numeric keypad down arrow
2511 			PostMessage(M_VOLUME_DOWN);
2512 			return true;
2513 
2514 		case 0x39:			// numeric keypad page up
2515 		case 0x4a:			// numeric keypad right arrow
2516 			PostMessage(M_SKIP_NEXT);
2517 			return true;
2518 
2519 		case 0x5a:			// numeric keypad page down
2520 		case 0x48:			// numeric keypad left arrow
2521 			PostMessage(M_SKIP_PREV);
2522 			return true;
2523 
2524 		// Playback controls along the bottom of the keyboard:
2525 		// Z X C (V) B  for US International
2526 		case 0x4c:
2527 			PostMessage(M_SKIP_PREV);
2528 			return true;
2529 		case 0x4d:
2530 			fController->TogglePlaying();
2531 			return true;
2532 		case 0x4e:
2533 			fController->Pause();
2534 			return true;
2535 		case 0x4f:
2536 			fController->Stop();
2537 			return true;
2538 		case 0x50:
2539 			PostMessage(M_SKIP_NEXT);
2540 			return true;
2541 	}
2542 
2543 	return false;
2544 }
2545 
2546 
2547 // #pragma mark -
2548 
2549 
2550 void
2551 MainWin::_ToggleFullscreen()
2552 {
2553 	printf("_ToggleFullscreen enter\n");
2554 
2555 	if (!fHasVideo) {
2556 		printf("_ToggleFullscreen - ignoring, as we don't have a video\n");
2557 		return;
2558 	}
2559 
2560 	fIsFullscreen = !fIsFullscreen;
2561 
2562 	if (fIsFullscreen) {
2563 		// switch to fullscreen
2564 
2565 		fSavedFrame = Frame();
2566 		printf("saving current frame: %d %d %d %d\n", int(fSavedFrame.left),
2567 			int(fSavedFrame.top), int(fSavedFrame.right),
2568 			int(fSavedFrame.bottom));
2569 		BScreen screen(this);
2570 		BRect rect(screen.Frame());
2571 
2572 		Hide();
2573 		MoveTo(rect.left, rect.top);
2574 		ResizeTo(rect.Width(), rect.Height());
2575 		Show();
2576 
2577 	} else {
2578 		// switch back from full screen mode
2579 		_ShowFullscreenControls(false, false);
2580 
2581 		Hide();
2582 		MoveTo(fSavedFrame.left, fSavedFrame.top);
2583 		ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
2584 		Show();
2585 	}
2586 
2587 	fVideoView->SetFullscreen(fIsFullscreen);
2588 
2589 	_MarkItem(fFileMenu, M_TOGGLE_FULLSCREEN, fIsFullscreen);
2590 
2591 	printf("_ToggleFullscreen leave\n");
2592 }
2593 
2594 void
2595 MainWin::_ToggleAlwaysOnTop()
2596 {
2597 	fAlwaysOnTop = !fAlwaysOnTop;
2598 	SetFeel(fAlwaysOnTop ? B_FLOATING_ALL_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL);
2599 
2600 	_MarkItem(fFileMenu, M_TOGGLE_ALWAYS_ON_TOP, fAlwaysOnTop);
2601 }
2602 
2603 
2604 void
2605 MainWin::_ToggleNoInterface()
2606 {
2607 	printf("_ToggleNoInterface enter\n");
2608 
2609 	if (fIsFullscreen || !fHasVideo) {
2610 		// Fullscreen playback is always without interface and
2611 		// audio playback is always with interface. So we ignore these
2612 		// two states here.
2613 		printf("_ToggleNoControls leave, doing nothing, we are fullscreen\n");
2614 		return;
2615 	}
2616 
2617 	fNoInterface = !fNoInterface;
2618 	_SetWindowSizeLimits();
2619 
2620 	if (fNoInterface) {
2621 		MoveBy(0, fMenuBarHeight);
2622 		ResizeBy(0, -(fControlsHeight + fMenuBarHeight));
2623 		SetLook(B_BORDERED_WINDOW_LOOK);
2624 	} else {
2625 		MoveBy(0, -fMenuBarHeight);
2626 		ResizeBy(0, fControlsHeight + fMenuBarHeight);
2627 		SetLook(B_TITLED_WINDOW_LOOK);
2628 	}
2629 
2630 	_MarkItem(fFileMenu, M_TOGGLE_NO_INTERFACE, fNoInterface);
2631 
2632 	printf("_ToggleNoInterface leave\n");
2633 }
2634 
2635 
2636 void
2637 MainWin::_ShowIfNeeded()
2638 {
2639 	// Only proceed if the window is already running
2640 	if (find_thread(NULL) != Thread())
2641 		return;
2642 
2643 	if (!fHasVideo && fNoVideoFrame.IsValid()) {
2644 		MoveTo(fNoVideoFrame.LeftTop());
2645 		ResizeTo(fNoVideoFrame.Width(), fNoVideoFrame.Height());
2646 		MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN);
2647 	} else if (fHasVideo && IsHidden())
2648 		CenterOnScreen();
2649 
2650 	fNoVideoFrame = BRect();
2651 
2652 	if (IsHidden()) {
2653 		Show();
2654 		UpdateIfNeeded();
2655 	}
2656 }
2657 
2658 
2659 void
2660 MainWin::_ShowFullscreenControls(bool show, bool animate)
2661 {
2662 	if (fShowsFullscreenControls == show)
2663 		return;
2664 
2665 	fShowsFullscreenControls = show;
2666 	fVideoView->SetFullscreenControlsVisible(show);
2667 
2668 	if (show) {
2669 		fControls->RemoveSelf();
2670 		fControls->MoveTo(fVideoView->Bounds().left,
2671 			fVideoView->Bounds().bottom + 1);
2672 		fVideoView->AddChild(fControls);
2673 		if (fScaleFullscreenControls)
2674 			fControls->SetSymbolScale(1.5f);
2675 
2676 		while (fControls->IsHidden())
2677 			fControls->Show();
2678 	}
2679 
2680 	if (animate) {
2681 		// Slide the controls into view. We need to do this with
2682 		// messages, otherwise we block the video playback for the
2683 		// time of the animation.
2684 		const float kAnimationOffsets[] = { 0.05, 0.2, 0.5, 0.2, 0.05 };
2685 		const int32 steps = sizeof(kAnimationOffsets) / sizeof(float);
2686 		float height = fControls->Bounds().Height();
2687 		float moveDist = show ? -height : height;
2688 		float originalY = fControls->Frame().top;
2689 		for (int32 i = 0; i < steps; i++) {
2690 			BMessage message(M_SLIDE_CONTROLS);
2691 			message.AddFloat("offset",
2692 				floorf(moveDist * kAnimationOffsets[i]));
2693 			PostMessage(&message, this);
2694 		}
2695 		BMessage finalMessage(M_FINISH_SLIDING_CONTROLS);
2696 		finalMessage.AddFloat("offset", originalY + moveDist);
2697 		finalMessage.AddBool("show", show);
2698 		PostMessage(&finalMessage, this);
2699 	} else if (!show) {
2700 		fControls->RemoveSelf();
2701 		fControls->MoveTo(fVideoView->Frame().left,
2702 			fVideoView->Frame().bottom + 1);
2703 		fBackground->AddChild(fControls);
2704 		fControls->SetSymbolScale(1.0f);
2705 
2706 		while (!fControls->IsHidden())
2707 			fControls->Hide();
2708 	}
2709 }
2710 
2711 
2712 // #pragma mark -
2713 
2714 
2715 void
2716 MainWin::_Wind(bigtime_t howMuch, int64 frames)
2717 {
2718 	if (!fAllowWinding || !fController->Lock())
2719 		return;
2720 
2721 	if (frames != 0 && fHasVideo && !fController->IsPlaying()) {
2722 		int64 newFrame = fController->CurrentFrame() + frames;
2723 		fController->SetFramePosition(newFrame);
2724 	} else {
2725 		bigtime_t seekTime = fController->TimePosition() + howMuch;
2726 		if (seekTime < 0) {
2727 			fInitialSeekPosition = seekTime;
2728 			PostMessage(M_SKIP_PREV);
2729 		} else if (seekTime > fController->TimeDuration()) {
2730 			fInitialSeekPosition = 0;
2731 			PostMessage(M_SKIP_NEXT);
2732 		} else
2733 			fController->SetTimePosition(seekTime);
2734 	}
2735 
2736 	fController->Unlock();
2737 	fAllowWinding = false;
2738 }
2739 
2740 
2741 // #pragma mark -
2742 
2743 
2744 void
2745 MainWin::_UpdatePlaylistItemFile()
2746 {
2747 	BAutolock locker(fPlaylist);
2748 	const FilePlaylistItem* item
2749 		= dynamic_cast<const FilePlaylistItem*>(fController->Item());
2750 	if (item == NULL)
2751 		return;
2752 
2753 	if (!fHasVideo && !fHasAudio)
2754 		return;
2755 
2756 	BNode node(&item->Ref());
2757 	if (node.InitCheck())
2758 		return;
2759 
2760 	locker.Unlock();
2761 
2762 	// Set some standard attributes of the currently played file.
2763 	// This should only be a temporary solution.
2764 
2765 	// Write duration
2766 	const char* kDurationAttrName = "Media:Length";
2767 	attr_info info;
2768 	status_t status = node.GetAttrInfo(kDurationAttrName, &info);
2769 	if (status != B_OK || info.size == 0) {
2770 		bigtime_t duration = fController->TimeDuration();
2771 		// TODO: Tracker does not seem to care about endian for scalar types
2772 		node.WriteAttr(kDurationAttrName, B_INT64_TYPE, 0, &duration,
2773 			sizeof(int64));
2774 	}
2775 
2776 	// Write audio bitrate
2777 	if (fHasAudio) {
2778 		status = node.GetAttrInfo("Audio:Bitrate", &info);
2779 		if (status != B_OK || info.size == 0) {
2780 			media_format format;
2781 			if (fController->GetEncodedAudioFormat(&format) == B_OK
2782 				&& format.type == B_MEDIA_ENCODED_AUDIO) {
2783 				int32 bitrate = (int32)(format.u.encoded_audio.bit_rate
2784 					/ 1000);
2785 				char text[256];
2786 				snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2787 				node.WriteAttr("Audio:Bitrate", B_STRING_TYPE, 0, text,
2788 					strlen(text) + 1);
2789 			}
2790 		}
2791 	}
2792 
2793 	// Write video bitrate
2794 	if (fHasVideo) {
2795 		status = node.GetAttrInfo("Video:Bitrate", &info);
2796 		if (status != B_OK || info.size == 0) {
2797 			media_format format;
2798 			if (fController->GetEncodedVideoFormat(&format) == B_OK
2799 				&& format.type == B_MEDIA_ENCODED_VIDEO) {
2800 				int32 bitrate = (int32)(format.u.encoded_video.avg_bit_rate
2801 					/ 1000);
2802 				char text[256];
2803 				snprintf(text, sizeof(text), "%" B_PRId32 " kbit", bitrate);
2804 				node.WriteAttr("Video:Bitrate", B_STRING_TYPE, 0, text,
2805 					strlen(text) + 1);
2806 			}
2807 		}
2808 	}
2809 
2810 	_UpdateAttributesMenu(node);
2811 }
2812 
2813 
2814 void
2815 MainWin::_UpdateAttributesMenu(const BNode& node)
2816 {
2817 	int32 rating = -1;
2818 
2819 	attr_info info;
2820 	status_t status = node.GetAttrInfo(kRatingAttrName, &info);
2821 	if (status == B_OK && info.type == B_INT32_TYPE) {
2822 		// Node has the Rating attribute.
2823 		node.ReadAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating,
2824 			sizeof(rating));
2825 	}
2826 
2827 	for (int32 i = 0; BMenuItem* item = fRatingMenu->ItemAt(i); i++)
2828 		item->SetMarked(i + 1 == rating);
2829 
2830 	fResetRatingItem->SetEnabled(rating > 0);
2831 }
2832 
2833 
2834 void
2835 MainWin::_SetRating(int32 rating)
2836 {
2837 	BAutolock locker(fPlaylist);
2838 	const FilePlaylistItem* item
2839 		= dynamic_cast<const FilePlaylistItem*>(fController->Item());
2840 	if (item == NULL)
2841 		return;
2842 
2843 	BNode node(&item->Ref());
2844 	if (node.InitCheck())
2845 		return;
2846 
2847 	locker.Unlock();
2848 
2849 	node.WriteAttr(kRatingAttrName, B_INT32_TYPE, 0, &rating, sizeof(rating));
2850 
2851 	// TODO: The whole mechnism should work like this:
2852 	// * There is already an attribute API for PlaylistItem, flesh it out!
2853 	// * FilePlaylistItem node-monitors it's file somehow.
2854 	// * FilePlaylistItem keeps attributes in sync and sends notications.
2855 	// * MainWin updates the menu according to FilePlaylistItem notifications.
2856 	// * PlaylistWin shows columns with attribute and other info.
2857 	// * PlaylistWin updates also upon FilePlaylistItem notifications.
2858 	// * This keeps attributes in sync when another app changes them.
2859 
2860 	_UpdateAttributesMenu(node);
2861 }
2862 
2863 
2864 void
2865 MainWin::_UpdateControlsEnabledStatus()
2866 {
2867 	uint32 enabledButtons = 0;
2868 	if (fHasVideo || fHasAudio) {
2869 		enabledButtons |= PLAYBACK_ENABLED | SEEK_ENABLED
2870 			| SEEK_BACK_ENABLED | SEEK_FORWARD_ENABLED;
2871 	}
2872 	if (fHasAudio)
2873 		enabledButtons |= VOLUME_ENABLED;
2874 
2875 	BAutolock _(fPlaylist);
2876 	bool canSkipPrevious, canSkipNext;
2877 	fPlaylist->GetSkipInfo(&canSkipPrevious, &canSkipNext);
2878 	if (canSkipPrevious)
2879 		enabledButtons |= SKIP_BACK_ENABLED;
2880 	if (canSkipNext)
2881 		enabledButtons |= SKIP_FORWARD_ENABLED;
2882 
2883 	fControls->SetEnabled(enabledButtons);
2884 
2885 	fNoInterfaceMenuItem->SetEnabled(fHasVideo);
2886 	fAttributesMenu->SetEnabled(fHasAudio || fHasVideo);
2887 }
2888 
2889 
2890 void
2891 MainWin::_UpdatePlaylistMenu()
2892 {
2893 	if (!fPlaylist->Lock())
2894 		return;
2895 
2896 	fPlaylistMenu->RemoveItems(0, fPlaylistMenu->CountItems(), true);
2897 
2898 	int32 count = fPlaylist->CountItems();
2899 	for (int32 i = 0; i < count; i++) {
2900 		PlaylistItem* item = fPlaylist->ItemAtFast(i);
2901 		_AddPlaylistItem(item, i);
2902 	}
2903 	fPlaylistMenu->SetTargetForItems(this);
2904 
2905 	_MarkPlaylistItem(fPlaylist->CurrentItemIndex());
2906 
2907 	fPlaylist->Unlock();
2908 }
2909 
2910 
2911 void
2912 MainWin::_AddPlaylistItem(PlaylistItem* item, int32 index)
2913 {
2914 	BMessage* message = new BMessage(M_SET_PLAYLIST_POSITION);
2915 	message->AddInt32("index", index);
2916 	BMenuItem* menuItem = new BMenuItem(item->Name().String(), message);
2917 	fPlaylistMenu->AddItem(menuItem, index);
2918 }
2919 
2920 
2921 void
2922 MainWin::_RemovePlaylistItem(int32 index)
2923 {
2924 	delete fPlaylistMenu->RemoveItem(index);
2925 }
2926 
2927 
2928 void
2929 MainWin::_MarkPlaylistItem(int32 index)
2930 {
2931 	if (BMenuItem* item = fPlaylistMenu->ItemAt(index)) {
2932 		item->SetMarked(true);
2933 		// ... and in case the menu is currently on screen:
2934 		if (fPlaylistMenu->LockLooper()) {
2935 			fPlaylistMenu->Invalidate();
2936 			fPlaylistMenu->UnlockLooper();
2937 		}
2938 	}
2939 }
2940 
2941 
2942 void
2943 MainWin::_MarkItem(BMenu* menu, uint32 command, bool mark)
2944 {
2945 	if (BMenuItem* item = menu->FindItem(command))
2946 		item->SetMarked(mark);
2947 }
2948 
2949 
2950 void
2951 MainWin::_AdoptGlobalSettings()
2952 {
2953 	mpSettings settings;
2954 	Settings::Default()->Get(settings);
2955 
2956 	fCloseWhenDonePlayingMovie = settings.closeWhenDonePlayingMovie;
2957 	fCloseWhenDonePlayingSound = settings.closeWhenDonePlayingSound;
2958 	fLoopMovies = settings.loopMovie;
2959 	fLoopSounds = settings.loopSound;
2960 	fScaleFullscreenControls = settings.scaleFullscreenControls;
2961 }
2962