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 <MessageFormat.h> 24 #include <Mime.h> 25 #include <NodeInfo.h> 26 #include <Screen.h> 27 #include <String.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_PRIu32 " x %" B_PRIu32, format.Width(), 418 format.Height()); 419 420 // encoded has output as 1st field... 421 char fpsString[20]; 422 snprintf(fpsString, sizeof(fpsString), B_TRANSLATE("%.3f fps"), 423 videoFormat.field_rate); 424 info << ", " << fpsString; 425 426 fVideoConfigInfo->SetText(info.String()); 427 428 if (fController->IsOverlayActive()) 429 fDisplayModeInfo->SetText(B_TRANSLATE("Overlay")); 430 else 431 fDisplayModeInfo->SetText(B_TRANSLATE("DrawBitmap")); 432 } 433 434 fVideoSeparator->SetVisible(visible); 435 _SetVisible(fVideoLabel, visible); 436 _SetVisible(fVideoFormatInfo, visible); 437 _SetVisible(fVideoConfigInfo, visible); 438 _SetVisible(fDisplayModeLabel, visible); 439 _SetVisible(fDisplayModeInfo, visible); 440 } 441 442 443 void 444 InfoWin::_UpdateAudio() 445 { 446 bool visible = fController->AudioTrackCount() > 0; 447 if (visible) { 448 BString info; 449 media_format format; 450 media_raw_audio_format audioFormat = {}; 451 452 status_t status = fController->GetEncodedAudioFormat(&format); 453 if (status != B_OK) { 454 info << "(" << strerror(status) << ")\n"; 455 } else if (format.type == B_MEDIA_ENCODED_AUDIO) { 456 audioFormat = format.u.encoded_audio.output; 457 media_codec_info mci; 458 status = fController->GetAudioCodecInfo(&mci); 459 if (status != B_OK) { 460 if (format.user_data_type == B_CODEC_TYPE_INFO) { 461 info << (char *)format.user_data << " " 462 << B_TRANSLATE("(not supported)"); 463 } else 464 info = strerror(status); 465 } else 466 info = mci.pretty_name; 467 } else if (format.type == B_MEDIA_RAW_AUDIO) { 468 audioFormat = format.u.raw_audio; 469 info = B_TRANSLATE("raw audio"); 470 } else 471 info = B_TRANSLATE("unknown format"); 472 473 fAudioFormatInfo->SetText(info.String()); 474 475 uint32 bitsPerSample = 8 * (audioFormat.format 476 & 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 BMessageFormat 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