1 /*
2 * InfoWin.cpp - Media Player for the Haiku Operating System
3 *
4 * Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de>
5 * Copyright 2015 Axel Dörfler <axeld@pinc-software.de>
6 *
7 * Released under the terms of the MIT license.
8 */
9
10
11 #include "InfoWin.h"
12
13 #include <math.h>
14 #include <stdio.h>
15 #include <string.h>
16
17 #include <Bitmap.h>
18 #include <Catalog.h>
19 #include <ControlLook.h>
20 #include <Debug.h>
21 #include <LayoutBuilder.h>
22 #include <MediaDefs.h>
23 #include <Mime.h>
24 #include <NodeInfo.h>
25 #include <Screen.h>
26 #include <String.h>
27 #include <StringFormat.h>
28 #include <StringForRate.h>
29 #include <StringView.h>
30 #include <TextView.h>
31
32 #include "Controller.h"
33 #include "ControllerObserver.h"
34 #include "PlaylistItem.h"
35
36
37 #define MIN_WIDTH 500
38
39
40 #undef B_TRANSLATION_CONTEXT
41 #define B_TRANSLATION_CONTEXT "MediaPlayer-InfoWin"
42
43
44 class IconView : public BView {
45 public:
46 IconView(const char* name, int32 iconSize);
47 virtual ~IconView();
48
49 status_t SetIcon(const PlaylistItem* item);
50 status_t SetIcon(const char* mimeType);
51 void SetGenericIcon();
52
53 virtual void GetPreferredSize(float* _width, float* _height);
54 virtual void AttachedToWindow();
55 virtual void Draw(BRect updateRect);
56
57 private:
58 BBitmap* fIconBitmap;
59 };
60
61
IconView(const char * name,int32 iconSize)62 IconView::IconView(const char* name, int32 iconSize)
63 :
64 BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
65 fIconBitmap(NULL)
66 {
67 fIconBitmap = new BBitmap(BRect(0, 0, iconSize - 1, iconSize - 1),
68 B_RGBA32);
69 SetExplicitMaxSize(PreferredSize());
70 }
71
72
~IconView()73 IconView::~IconView()
74 {
75 delete fIconBitmap;
76 }
77
78
79 status_t
SetIcon(const PlaylistItem * item)80 IconView::SetIcon(const PlaylistItem* item)
81 {
82 return item->GetIcon(fIconBitmap, B_LARGE_ICON);
83 }
84
85
86 status_t
SetIcon(const char * mimeTypeString)87 IconView::SetIcon(const char* mimeTypeString)
88 {
89 if (!mimeTypeString)
90 return B_BAD_VALUE;
91
92 // get type icon
93 BMimeType mimeType(mimeTypeString);
94 status_t status = mimeType.GetIcon(fIconBitmap, B_LARGE_ICON);
95
96 // get supertype icon
97 if (status != B_OK) {
98 BMimeType superType;
99 status = mimeType.GetSupertype(&superType);
100 if (status == B_OK)
101 status = superType.GetIcon(fIconBitmap, B_LARGE_ICON);
102 }
103
104 return status;
105 }
106
107
108 void
SetGenericIcon()109 IconView::SetGenericIcon()
110 {
111 // get default icon
112 BMimeType genericType(B_FILE_MIME_TYPE);
113 if (genericType.GetIcon(fIconBitmap, B_LARGE_ICON) != B_OK) {
114 // clear bitmap
115 uint8 transparent = 0;
116 if (fIconBitmap->ColorSpace() == B_CMAP8)
117 transparent = B_TRANSPARENT_MAGIC_CMAP8;
118
119 memset(fIconBitmap->Bits(), transparent, fIconBitmap->BitsLength());
120 }
121 }
122
123
124 void
GetPreferredSize(float * _width,float * _height)125 IconView::GetPreferredSize(float* _width, float* _height)
126 {
127 if (_width != NULL) {
128 *_width = fIconBitmap->Bounds().Width()
129 + 2 * be_control_look->DefaultItemSpacing();
130 }
131 if (_height != NULL) {
132 *_height = fIconBitmap->Bounds().Height()
133 + 2 * be_control_look->DefaultItemSpacing();
134 }
135 }
136
137
138 void
AttachedToWindow()139 IconView::AttachedToWindow()
140 {
141 if (Parent() != NULL)
142 SetViewColor(Parent()->ViewColor());
143 else
144 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
145 }
146
147
148 void
Draw(BRect updateRect)149 IconView::Draw(BRect updateRect)
150 {
151 BRect rect(Bounds());
152
153 if (fIconBitmap != NULL) {
154 // Draw bitmap centered within the view
155 SetDrawingMode(B_OP_ALPHA);
156 DrawBitmap(fIconBitmap, BPoint(rect.left
157 + (rect.Width() - fIconBitmap->Bounds().Width()) / 2,
158 rect.top + (rect.Height() - fIconBitmap->Bounds().Height()) / 2));
159 }
160 }
161
162
163 // #pragma mark -
164
165
InfoWin(BPoint leftTop,Controller * controller)166 InfoWin::InfoWin(BPoint leftTop, Controller* controller)
167 :
168 BWindow(BRect(leftTop.x, leftTop.y, leftTop.x + MIN_WIDTH - 1,
169 leftTop.y + 300), B_TRANSLATE("File info"), B_TITLED_WINDOW,
170 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_ZOOMABLE),
171 fController(controller),
172 fControllerObserver(new ControllerObserver(this,
173 OBSERVE_FILE_CHANGES | OBSERVE_TRACK_CHANGES | OBSERVE_STAT_CHANGES))
174 {
175 fIconView = new IconView("background", B_LARGE_ICON);
176
177 fFilenameView = _CreateInfo("filename");
178 BFont bigFont(be_plain_font);
179 bigFont.SetSize(bigFont.Size() * 1.5f);
180 fFilenameView->SetFont(&bigFont);
181
182 // Create info views
183
184 BStringView* containerLabel = _CreateLabel("containerLabel",
185 B_TRANSLATE("Container"));
186 fContainerInfo = _CreateInfo("container");
187
188 fVideoSeparator = _CreateSeparator();
189 fVideoLabel = _CreateLabel("videoLabel", B_TRANSLATE("Video"));
190 fVideoFormatInfo = _CreateInfo("videoFormat");
191 fVideoConfigInfo = _CreateInfo("videoConfig");
192 fDisplayModeLabel = _CreateLabel("displayModeLabel",
193 B_TRANSLATE("Display mode"));
194 fDisplayModeInfo = _CreateInfo("displayMode");
195
196 fAudioSeparator = _CreateSeparator();
197 fAudioLabel = _CreateLabel("audioLabel", B_TRANSLATE("Audio"));
198 fAudioFormatInfo = _CreateInfo("audioFormat");
199 fAudioConfigInfo = _CreateInfo("audioConfig");
200
201 BStringView* durationLabel = _CreateLabel("durationLabel",
202 B_TRANSLATE("Duration"));
203 fDurationInfo = _CreateInfo("duration");
204
205 BStringView* locationLabel = _CreateLabel("locationLabel",
206 B_TRANSLATE("Location"));
207 fLocationInfo = _CreateInfo("location");
208
209 fCopyrightSeparator = _CreateSeparator();
210 fCopyrightLabel = _CreateLabel("copyrightLabel", B_TRANSLATE("Copyright"));
211 fCopyrightInfo = _CreateInfo("copyright");
212
213 BLayoutBuilder::Group<>(this, B_VERTICAL)
214 .SetInsets(B_USE_DEFAULT_SPACING)
215 .AddGroup(B_HORIZONTAL)
216 .Add(fIconView, 0)
217 .Add(fFilenameView, 1)
218 .End()
219 .AddGrid(2, 13)
220 .Add(containerLabel, 0, 0)
221 .Add(fContainerInfo, 1, 0)
222 .Add(fVideoSeparator, 0, 1)
223 .Add(fVideoLabel, 0, 2)
224 .Add(fVideoFormatInfo, 1, 2)
225 .Add(fVideoConfigInfo, 1, 3)
226 .Add(fDisplayModeLabel, 0, 4)
227 .Add(fDisplayModeInfo, 1, 4)
228 .Add(fAudioSeparator, 0, 5)
229 .Add(fAudioLabel, 0, 6)
230 .Add(fAudioFormatInfo, 1, 6)
231 .Add(fAudioConfigInfo, 1, 7)
232 .Add(_CreateSeparator(), 0, 8)
233 .Add(durationLabel, 0, 9)
234 .Add(fDurationInfo, 1, 9)
235 .Add(_CreateSeparator(), 0, 10)
236 .Add(locationLabel, 0, 11)
237 .Add(fLocationInfo, 1, 11)
238 .Add(fCopyrightSeparator, 0, 12)
239 .Add(fCopyrightLabel, 0, 12)
240 .Add(fCopyrightInfo, 1, 12)
241 .SetColumnWeight(0, 0)
242 .SetColumnWeight(1, 1)
243 .SetSpacing(B_USE_DEFAULT_SPACING, 0)
244 .SetExplicitMinSize(BSize(MIN_WIDTH, B_SIZE_UNSET));
245
246 fController->AddListener(fControllerObserver);
247 Update();
248
249 UpdateSizeLimits();
250
251 // Move window on screen if needed
252 BScreen screen(this);
253 if (screen.Frame().bottom < Frame().bottom)
254 MoveBy(0, screen.Frame().bottom - Frame().bottom);
255 if (screen.Frame().right < Frame().right)
256 MoveBy(0, screen.Frame().right - Frame().right);
257
258 Show();
259 }
260
261
~InfoWin()262 InfoWin::~InfoWin()
263 {
264 fController->RemoveListener(fControllerObserver);
265 delete fControllerObserver;
266 }
267
268
269 void
MessageReceived(BMessage * msg)270 InfoWin::MessageReceived(BMessage* msg)
271 {
272 switch (msg->what) {
273 case MSG_CONTROLLER_FILE_FINISHED:
274 break;
275 case MSG_CONTROLLER_FILE_CHANGED:
276 Update(INFO_ALL);
277 break;
278 case MSG_CONTROLLER_VIDEO_TRACK_CHANGED:
279 Update(INFO_VIDEO | INFO_STATS);
280 break;
281 case MSG_CONTROLLER_AUDIO_TRACK_CHANGED:
282 Update(INFO_AUDIO | INFO_STATS);
283 break;
284 case MSG_CONTROLLER_VIDEO_STATS_CHANGED:
285 case MSG_CONTROLLER_AUDIO_STATS_CHANGED:
286 Update(INFO_STATS);
287 break;
288 default:
289 BWindow::MessageReceived(msg);
290 break;
291 }
292 }
293
294
295 bool
QuitRequested()296 InfoWin::QuitRequested()
297 {
298 Hide();
299 return false;
300 }
301
302
303 void
Pulse()304 InfoWin::Pulse()
305 {
306 if (IsHidden())
307 return;
308 Update(INFO_STATS);
309 }
310
311
312 // #pragma mark -
313
314
315 void
Update(uint32 which)316 InfoWin::Update(uint32 which)
317 {
318 if (!fController->Lock())
319 return;
320
321 if ((which & INFO_FILE) != 0)
322 _UpdateFile();
323
324 // video track format information
325 if ((which & INFO_VIDEO) != 0)
326 _UpdateVideo();
327
328 // audio track format information
329 if ((which & INFO_AUDIO) != 0)
330 _UpdateAudio();
331
332 // statistics
333 if ((which & INFO_STATS) != 0) {
334 _UpdateDuration();
335 // TODO: demux/video/audio/... perfs (Kb/info)
336 }
337
338 if ((which & INFO_TRANSPORT) != 0) {
339 // Transport protocol info (file, http, rtsp, ...)
340 }
341
342 if ((which & INFO_COPYRIGHT)!=0)
343 _UpdateCopyright();
344
345 fController->Unlock();
346 }
347
348
349 void
_UpdateFile()350 InfoWin::_UpdateFile()
351 {
352 bool iconSet = false;
353 if (fController->HasFile()) {
354 const PlaylistItem* item = fController->Item();
355 iconSet = fIconView->SetIcon(item) == B_OK;
356 media_file_format fileFormat;
357 status_t status = fController->GetFileFormatInfo(&fileFormat);
358 if (status == B_OK) {
359 fContainerInfo->SetText(fileFormat.pretty_name);
360 if (!iconSet)
361 iconSet = fIconView->SetIcon(fileFormat.mime_type) == B_OK;
362 } else
363 fContainerInfo->SetText(strerror(status));
364
365 BString info;
366 if (fController->GetLocation(&info) != B_OK)
367 info = B_TRANSLATE("<unknown>");
368 fLocationInfo->SetText(info.String());
369 fLocationInfo->SetToolTip(info.String());
370
371 if (fController->GetName(&info) != B_OK || info.IsEmpty())
372 info = B_TRANSLATE("<unnamed media>");
373 fFilenameView->SetText(info.String());
374 fFilenameView->SetToolTip(info.String());
375 } else {
376 fFilenameView->SetText(B_TRANSLATE("<no media>"));
377 fContainerInfo->SetText("-");
378 fLocationInfo->SetText("-");
379 }
380
381 if (!iconSet)
382 fIconView->SetGenericIcon();
383 }
384
385
386 void
_UpdateVideo()387 InfoWin::_UpdateVideo()
388 {
389 bool visible = fController->VideoTrackCount() > 0;
390 if (visible) {
391 BString info;
392 media_format format;
393 media_raw_video_format videoFormat = {};
394 status_t status = fController->GetEncodedVideoFormat(&format);
395 if (status != B_OK) {
396 info << "(" << strerror(status) << ")\n";
397 } else if (format.type == B_MEDIA_ENCODED_VIDEO) {
398 videoFormat = format.u.encoded_video.output;
399 media_codec_info mci;
400 status = fController->GetVideoCodecInfo(&mci);
401 if (status != B_OK) {
402 if (format.user_data_type == B_CODEC_TYPE_INFO) {
403 info << (char *)format.user_data << " "
404 << B_TRANSLATE("(not supported)");
405 } else
406 info = strerror(status);
407 } else
408 info << mci.pretty_name; //<< "(" << mci.short_name << ")";
409 } else if (format.type == B_MEDIA_RAW_VIDEO) {
410 videoFormat = format.u.raw_video;
411 info << B_TRANSLATE("raw video");
412 } else
413 info << B_TRANSLATE("unknown format");
414
415 fVideoFormatInfo->SetText(info.String());
416
417 info.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRIu32 " × %" B_PRIu32,
418 "The '×' is the Unicode multiplication sign U+00D7"),
419 format.Width(), format.Height());
420
421 // encoded has output as 1st field...
422 char fpsString[20];
423 snprintf(fpsString, sizeof(fpsString), B_TRANSLATE("%.3f fps"),
424 videoFormat.field_rate);
425 info << ", " << fpsString;
426
427 fVideoConfigInfo->SetText(info.String());
428
429 if (fController->IsOverlayActive())
430 fDisplayModeInfo->SetText(B_TRANSLATE("Overlay"));
431 else
432 fDisplayModeInfo->SetText(B_TRANSLATE("DrawBitmap"));
433 }
434
435 fVideoSeparator->SetVisible(visible);
436 _SetVisible(fVideoLabel, visible);
437 _SetVisible(fVideoFormatInfo, visible);
438 _SetVisible(fVideoConfigInfo, visible);
439 _SetVisible(fDisplayModeLabel, visible);
440 _SetVisible(fDisplayModeInfo, visible);
441 }
442
443
444 void
_UpdateAudio()445 InfoWin::_UpdateAudio()
446 {
447 bool visible = fController->AudioTrackCount() > 0;
448 if (visible) {
449 BString info;
450 media_format format;
451 media_raw_audio_format audioFormat = {};
452
453 status_t status = fController->GetEncodedAudioFormat(&format);
454 if (status != B_OK) {
455 info << "(" << strerror(status) << ")\n";
456 } else if (format.type == B_MEDIA_ENCODED_AUDIO) {
457 audioFormat = format.u.encoded_audio.output;
458 media_codec_info mci;
459 status = fController->GetAudioCodecInfo(&mci);
460 if (status != B_OK) {
461 if (format.user_data_type == B_CODEC_TYPE_INFO) {
462 info << (char *)format.user_data << " "
463 << B_TRANSLATE("(not supported)");
464 } else
465 info = strerror(status);
466 } else
467 info = mci.pretty_name;
468 } else if (format.type == B_MEDIA_RAW_AUDIO) {
469 audioFormat = format.u.raw_audio;
470 info = B_TRANSLATE("raw audio");
471 } else
472 info = B_TRANSLATE("unknown format");
473
474 fAudioFormatInfo->SetText(info.String());
475
476 int bitsPerSample = 8 * (audioFormat.format & media_raw_audio_format::B_AUDIO_SIZE_MASK);
477 uint32 channelCount = audioFormat.channel_count;
478 float sr = audioFormat.frame_rate;
479
480 info.Truncate(0);
481
482 if (bitsPerSample > 0) {
483 char bitString[20];
484 snprintf(bitString, sizeof(bitString), B_TRANSLATE("%d Bit"),
485 bitsPerSample);
486 info << bitString << " ";
487 }
488
489 static BStringFormat channelFormat(B_TRANSLATE(
490 "{0, plural, =1{Mono} =2{Stereo} other{# Channels}}"));
491 channelFormat.Format(info, channelCount);
492
493 info << ", ";
494 if (sr > 0.0) {
495 char rateString[20];
496 snprintf(rateString, sizeof(rateString),
497 B_TRANSLATE("%.3f kHz"), sr / 1000);
498 info << rateString;
499 } else {
500 BString rateString = B_TRANSLATE("%d kHz");
501 rateString.ReplaceFirst("%d", "??");
502 info << rateString;
503 }
504 if (format.type == B_MEDIA_ENCODED_AUDIO) {
505 float br = format.u.encoded_audio.bit_rate;
506 char string[20] = "";
507 if (br > 0.0)
508 info << ", " << string_for_rate(br, string, sizeof(string));
509 }
510
511 fAudioConfigInfo->SetText(info.String());
512 }
513
514 fAudioSeparator->SetVisible(visible);
515 _SetVisible(fAudioLabel, visible);
516 _SetVisible(fAudioFormatInfo, visible);
517 _SetVisible(fAudioConfigInfo, visible);
518 }
519
520
521 void
_UpdateDuration()522 InfoWin::_UpdateDuration()
523 {
524 if (!fController->HasFile()) {
525 fDurationInfo->SetText("-");
526 return;
527 }
528
529 BString info;
530
531 bigtime_t d = fController->TimeDuration() / 1000;
532 bigtime_t v = d / (3600 * 1000);
533 d = d % (3600 * 1000);
534 bool hours = v > 0;
535 if (hours)
536 info << v << ":";
537 v = d / (60 * 1000);
538 d = d % (60 * 1000);
539 info << v << ":";
540 v = d / 1000;
541 if (v < 10)
542 info << '0';
543 info << v;
544 if (hours)
545 info << " " << B_TRANSLATE_COMMENT("h", "Hours");
546 else
547 info << " " << B_TRANSLATE_COMMENT("min", "Minutes");
548
549 fDurationInfo->SetText(info.String());
550 }
551
552
553 void
_UpdateCopyright()554 InfoWin::_UpdateCopyright()
555 {
556 BString info;
557
558 bool visible = fController->HasFile()
559 && fController->GetCopyright(&info) == B_OK && !info.IsEmpty();
560 if (visible)
561 fCopyrightInfo->SetText(info.String());
562
563 fCopyrightSeparator->SetVisible(visible);
564 _SetVisible(fCopyrightLabel, visible);
565 _SetVisible(fCopyrightInfo, visible);
566 }
567
568
569 // #pragma mark -
570
571
572 BStringView*
_CreateLabel(const char * name,const char * label)573 InfoWin::_CreateLabel(const char* name, const char* label)
574 {
575 static const rgb_color kLabelColor = tint_color(
576 ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_3_TINT);
577
578 BStringView* view = new BStringView(name, label);
579 view->SetAlignment(B_ALIGN_RIGHT);
580 view->SetHighColor(kLabelColor);
581
582 return view;
583 }
584
585
586 BStringView*
_CreateInfo(const char * name)587 InfoWin::_CreateInfo(const char* name)
588 {
589 BStringView* view = new BStringView(name, "");
590 view->SetExplicitMinSize(BSize(200, B_SIZE_UNSET));
591 view->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
592 view->SetTruncation(B_TRUNCATE_SMART);
593
594 return view;
595 }
596
597
598 BLayoutItem*
_CreateSeparator()599 InfoWin::_CreateSeparator()
600 {
601 return BSpaceLayoutItem::CreateVerticalStrut(
602 be_control_look->ComposeSpacing(B_USE_HALF_ITEM_SPACING));
603 }
604
605
606 void
_SetVisible(BView * view,bool visible)607 InfoWin::_SetVisible(BView* view, bool visible)
608 {
609 bool hidden = view->IsHidden(view);
610 if (hidden && visible)
611 view->Show();
612 else if (!hidden && !visible)
613 view->Hide();
614 }
615