xref: /haiku/src/apps/mediaplayer/InfoWin.cpp (revision 47c05920fde47c2618efccd24bd82f1e79cdf05a)
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 
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 
73 IconView::~IconView()
74 {
75 	delete fIconBitmap;
76 }
77 
78 
79 status_t
80 IconView::SetIcon(const PlaylistItem* item)
81 {
82 	return item->GetIcon(fIconBitmap, B_LARGE_ICON);
83 }
84 
85 
86 status_t
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
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
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
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
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 
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 
262 InfoWin::~InfoWin()
263 {
264 	fController->RemoveListener(fControllerObserver);
265 	delete fControllerObserver;
266 }
267 
268 
269 void
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
296 InfoWin::QuitRequested()
297 {
298 	Hide();
299 	return false;
300 }
301 
302 
303 void
304 InfoWin::Pulse()
305 {
306 	if (IsHidden())
307 		return;
308 	Update(INFO_STATS);
309 }
310 
311 
312 // #pragma mark -
313 
314 
315 void
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
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
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
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
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
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*
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*
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*
599 InfoWin::_CreateSeparator()
600 {
601 	return BSpaceLayoutItem::CreateVerticalStrut(
602 		be_control_look->ComposeSpacing(B_USE_HALF_ITEM_SPACING));
603 }
604 
605 
606 void
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