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