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