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 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 522 void 523 InfoWin::_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 554 void 555 InfoWin::_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 573 BStringView* 574 InfoWin::_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 587 BStringView* 588 InfoWin::_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 599 BLayoutItem* 600 InfoWin::_CreateSeparator() 601 { 602 return BSpaceLayoutItem::CreateVerticalStrut( 603 be_control_look->ComposeSpacing(B_USE_HALF_ITEM_SPACING)); 604 } 605 606 607 void 608 InfoWin::_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