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
44class IconView : public BView {
45public:
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
57private:
58			BBitmap*			fIconBitmap;
59};
60
61
62IconView::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
73IconView::~IconView()
74{
75	delete fIconBitmap;
76}
77
78
79status_t
80IconView::SetIcon(const PlaylistItem* item)
81{
82	return item->GetIcon(fIconBitmap, B_LARGE_ICON);
83}
84
85
86status_t
87IconView::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
108void
109IconView::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
124void
125IconView::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
138void
139IconView::AttachedToWindow()
140{
141	if (Parent() != NULL)
142		SetViewColor(Parent()->ViewColor());
143	else
144		SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
145}
146
147
148void
149IconView::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
166InfoWin::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
262InfoWin::~InfoWin()
263{
264	fController->RemoveListener(fControllerObserver);
265	delete fControllerObserver;
266}
267
268
269void
270InfoWin::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
295bool
296InfoWin::QuitRequested()
297{
298	Hide();
299	return false;
300}
301
302
303void
304InfoWin::Pulse()
305{
306	if (IsHidden())
307		return;
308	Update(INFO_STATS);
309}
310
311
312// #pragma mark -
313
314
315void
316InfoWin::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
349void
350InfoWin::_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
386void
387InfoWin::_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
444void
445InfoWin::_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		uint32 bitsPerSample = 8 * (audioFormat.format
477			& media_raw_audio_format::B_AUDIO_SIZE_MASK);
478		uint32 channelCount = audioFormat.channel_count;
479		float sr = audioFormat.frame_rate;
480
481		info.Truncate(0);
482
483		if (bitsPerSample > 0) {
484			char bitString[20];
485			snprintf(bitString, sizeof(bitString), B_TRANSLATE("%d Bit"),
486				bitsPerSample);
487			info << bitString << " ";
488		}
489
490		static BStringFormat channelFormat(B_TRANSLATE(
491			"{0, plural, =1{Mono} =2{Stereo} other{# Channels}}"));
492		channelFormat.Format(info, channelCount);
493
494		info << ", ";
495		if (sr > 0.0) {
496			char rateString[20];
497			snprintf(rateString, sizeof(rateString),
498				B_TRANSLATE("%.3f kHz"), sr / 1000);
499			info << rateString;
500		} else {
501			BString rateString = B_TRANSLATE("%d kHz");
502			rateString.ReplaceFirst("%d", "??");
503			info << rateString;
504		}
505		if (format.type == B_MEDIA_ENCODED_AUDIO) {
506			float br = format.u.encoded_audio.bit_rate;
507			char string[20] = "";
508			if (br > 0.0)
509				info << ", " << string_for_rate(br, string, sizeof(string));
510		}
511
512		fAudioConfigInfo->SetText(info.String());
513	}
514
515	fAudioSeparator->SetVisible(visible);
516	_SetVisible(fAudioLabel, visible);
517	_SetVisible(fAudioFormatInfo, visible);
518	_SetVisible(fAudioConfigInfo, visible);
519}
520
521
522void
523InfoWin::_UpdateDuration()
524{
525	if (!fController->HasFile()) {
526		fDurationInfo->SetText("-");
527		return;
528	}
529
530	BString info;
531
532	bigtime_t d = fController->TimeDuration() / 1000;
533	bigtime_t v = d / (3600 * 1000);
534	d = d % (3600 * 1000);
535	bool hours = v > 0;
536	if (hours)
537		info << v << ":";
538	v = d / (60 * 1000);
539	d = d % (60 * 1000);
540	info << v << ":";
541	v = d / 1000;
542	if (v < 10)
543		info << '0';
544	info << v;
545	if (hours)
546		info << " " << B_TRANSLATE_COMMENT("h", "Hours");
547	else
548		info << " " << B_TRANSLATE_COMMENT("min", "Minutes");
549
550	fDurationInfo->SetText(info.String());
551}
552
553
554void
555InfoWin::_UpdateCopyright()
556{
557	BString info;
558
559	bool visible = fController->HasFile()
560		&& fController->GetCopyright(&info) == B_OK && !info.IsEmpty();
561	if (visible)
562		fCopyrightInfo->SetText(info.String());
563
564	fCopyrightSeparator->SetVisible(visible);
565	_SetVisible(fCopyrightLabel, visible);
566	_SetVisible(fCopyrightInfo, visible);
567}
568
569
570// #pragma mark -
571
572
573BStringView*
574InfoWin::_CreateLabel(const char* name, const char* label)
575{
576	static const rgb_color kLabelColor = tint_color(
577		ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_3_TINT);
578
579	BStringView* view = new BStringView(name, label);
580	view->SetAlignment(B_ALIGN_RIGHT);
581	view->SetHighColor(kLabelColor);
582
583	return view;
584}
585
586
587BStringView*
588InfoWin::_CreateInfo(const char* name)
589{
590	BStringView* view = new BStringView(name, "");
591	view->SetExplicitMinSize(BSize(200, B_SIZE_UNSET));
592	view->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
593	view->SetTruncation(B_TRUNCATE_SMART);
594
595	return view;
596}
597
598
599BLayoutItem*
600InfoWin::_CreateSeparator()
601{
602	return BSpaceLayoutItem::CreateVerticalStrut(
603		be_control_look->ComposeSpacing(B_USE_HALF_ITEM_SPACING));
604}
605
606
607void
608InfoWin::_SetVisible(BView* view, bool visible)
609{
610	bool hidden = view->IsHidden(view);
611	if (hidden && visible)
612		view->Show();
613	else if (!hidden && !visible)
614		view->Hide();
615}
616