1 /* 2 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. 3 * Copyright 2018-2024, Andrew Lindesay <apl@lindesay.co.nz>. 4 * All rights reserved. Distributed under the terms of the MIT License. 5 */ 6 #include "PackageInfoView.h" 7 8 #include <algorithm> 9 10 #include <Alert.h> 11 #include <Autolock.h> 12 #include <Bitmap.h> 13 #include <Button.h> 14 #include <CardLayout.h> 15 #include <Catalog.h> 16 #include <ColumnListView.h> 17 #include <ControlLook.h> 18 #include <Font.h> 19 #include <GridView.h> 20 #include <LayoutBuilder.h> 21 #include <LayoutUtils.h> 22 #include <LocaleRoster.h> 23 #include <Message.h> 24 #include <OutlineListView.h> 25 #include <ScrollView.h> 26 #include <SpaceLayoutItem.h> 27 #include <StatusBar.h> 28 #include <StringView.h> 29 #include <TabView.h> 30 #include <Url.h> 31 32 #include <package/hpkg/PackageReader.h> 33 #include <package/hpkg/NoErrorOutput.h> 34 #include <package/hpkg/PackageContentHandler.h> 35 #include <package/hpkg/PackageEntry.h> 36 37 #include "BitmapView.h" 38 #include "GeneralContentScrollView.h" 39 #include "LinkView.h" 40 #include "LinkedBitmapView.h" 41 #include "LocaleUtils.h" 42 #include "Logger.h" 43 #include "MarkupTextView.h" 44 #include "MessagePackageListener.h" 45 #include "PackageContentsView.h" 46 #include "PackageInfo.h" 47 #include "PackageManager.h" 48 #include "PackageUtils.h" 49 #include "ProcessCoordinatorFactory.h" 50 #include "RatingView.h" 51 #include "ScrollableGroupView.h" 52 #include "SharedIcons.h" 53 #include "TextView.h" 54 55 56 #undef B_TRANSLATION_CONTEXT 57 #define B_TRANSLATION_CONTEXT "PackageInfoView" 58 59 60 enum { 61 TAB_ABOUT = 0, 62 TAB_RATINGS = 1, 63 TAB_CHANGELOG = 2, 64 TAB_CONTENTS = 3 65 }; 66 67 68 static const float kContentTint = (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0f; 69 static const uint32 kScreenshotSize = 320; 70 71 72 class RatingsScrollView : public GeneralContentScrollView { 73 public: 74 RatingsScrollView(const char* name, BView* target) 75 : 76 GeneralContentScrollView(name, target) 77 { 78 } 79 80 virtual void DoLayout() 81 { 82 GeneralContentScrollView::DoLayout(); 83 84 BScrollBar* scrollBar = ScrollBar(B_VERTICAL); 85 BView* target = Target(); 86 if (target != NULL && scrollBar != NULL) { 87 // Set the scroll steps 88 BView* item = target->ChildAt(0); 89 if (item != NULL) { 90 scrollBar->SetSteps(item->MinSize().height + 1, 91 item->MinSize().height + 1); 92 } 93 } 94 } 95 }; 96 97 98 // #pragma mark - rating stats 99 100 101 class DiagramBarView : public BView { 102 public: 103 DiagramBarView() 104 : 105 BView("diagram bar view", B_WILL_DRAW), 106 fValue(0.0f) 107 { 108 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 109 SetHighUIColor(B_CONTROL_MARK_COLOR); 110 } 111 112 virtual ~DiagramBarView() 113 { 114 } 115 116 virtual void AttachedToWindow() 117 { 118 } 119 120 virtual void Draw(BRect updateRect) 121 { 122 FillRect(updateRect, B_SOLID_LOW); 123 124 if (fValue <= 0.0f) 125 return; 126 127 BRect rect(Bounds()); 128 rect.right = ceilf(rect.left + fValue * rect.Width()); 129 130 FillRect(rect, B_SOLID_HIGH); 131 } 132 133 virtual BSize MinSize() 134 { 135 return BSize(64, 10); 136 } 137 138 virtual BSize PreferredSize() 139 { 140 return MinSize(); 141 } 142 143 virtual BSize MaxSize() 144 { 145 return BSize(64, 10); 146 } 147 148 void SetValue(float value) 149 { 150 if (fValue != value) { 151 fValue = value; 152 Invalidate(); 153 } 154 } 155 156 private: 157 float fValue; 158 }; 159 160 161 // #pragma mark - TitleView 162 163 164 enum { 165 MSG_MOUSE_ENTERED_RATING = 'menr', 166 MSG_MOUSE_EXITED_RATING = 'mexr', 167 }; 168 169 170 class TransitReportingButton : public BButton { 171 public: 172 TransitReportingButton(const char* name, const char* label, 173 BMessage* message) 174 : 175 BButton(name, label, message), 176 fTransitMessage(NULL) 177 { 178 } 179 180 virtual ~TransitReportingButton() 181 { 182 SetTransitMessage(NULL); 183 } 184 185 virtual void MouseMoved(BPoint point, uint32 transit, 186 const BMessage* dragMessage) 187 { 188 BButton::MouseMoved(point, transit, dragMessage); 189 190 if (fTransitMessage != NULL && transit == B_EXITED_VIEW) 191 Invoke(fTransitMessage); 192 } 193 194 void SetTransitMessage(BMessage* message) 195 { 196 if (fTransitMessage != message) { 197 delete fTransitMessage; 198 fTransitMessage = message; 199 } 200 } 201 202 private: 203 BMessage* fTransitMessage; 204 }; 205 206 207 class TransitReportingRatingView : public RatingView, public BInvoker { 208 public: 209 TransitReportingRatingView(BMessage* transitMessage) 210 : 211 RatingView("package rating view"), 212 fTransitMessage(transitMessage) 213 { 214 } 215 216 virtual ~TransitReportingRatingView() 217 { 218 delete fTransitMessage; 219 } 220 221 virtual void MouseMoved(BPoint point, uint32 transit, 222 const BMessage* dragMessage) 223 { 224 RatingView::MouseMoved(point, transit, dragMessage); 225 226 if (fTransitMessage != NULL && transit == B_ENTERED_VIEW) 227 Invoke(fTransitMessage); 228 } 229 230 private: 231 BMessage* fTransitMessage; 232 }; 233 234 235 class TitleView : public BGroupView { 236 public: 237 TitleView(PackageIconRepository& packageIconRepository) 238 : 239 BGroupView("title view", B_HORIZONTAL), 240 fPackageIconRepository(packageIconRepository) 241 { 242 fIconView = new BitmapView("package icon view"); 243 fTitleView = new BStringView("package title view", ""); 244 fPublisherView = new BStringView("package publisher view", ""); 245 246 // Title font 247 BFont font; 248 GetFont(&font); 249 font_family family; 250 font_style style; 251 font.SetSize(ceilf(font.Size() * 1.5f)); 252 font.GetFamilyAndStyle(&family, &style); 253 font.SetFamilyAndStyle(family, "Bold"); 254 fTitleView->SetFont(&font); 255 256 // Publisher font 257 GetFont(&font); 258 font.SetSize(std::max(9.0f, floorf(font.Size() * 0.92f))); 259 font.SetFamilyAndStyle(family, "Italic"); 260 fPublisherView->SetFont(&font); 261 fPublisherView->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT); 262 263 // slightly bigger font 264 GetFont(&font); 265 font.SetSize(ceilf(font.Size() * 1.2f)); 266 267 // Version info 268 fVersionInfo = new BStringView("package version info", ""); 269 fVersionInfo->SetFont(&font); 270 fVersionInfo->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT); 271 272 // Rating view 273 fRatingView = new TransitReportingRatingView( 274 new BMessage(MSG_MOUSE_ENTERED_RATING)); 275 276 fAvgRating = new BStringView("package average rating", ""); 277 fAvgRating->SetFont(&font); 278 fAvgRating->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT); 279 280 fVoteInfo = new BStringView("package vote info", ""); 281 // small font 282 GetFont(&font); 283 font.SetSize(std::max(9.0f, floorf(font.Size() * 0.85f))); 284 fVoteInfo->SetFont(&font); 285 fVoteInfo->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT); 286 287 // Rate button 288 fRateButton = new TransitReportingButton("rate", 289 B_TRANSLATE("Rate package" B_UTF8_ELLIPSIS), 290 new BMessage(MSG_RATE_PACKAGE)); 291 fRateButton->SetTransitMessage(new BMessage(MSG_MOUSE_EXITED_RATING)); 292 fRateButton->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 293 B_ALIGN_VERTICAL_CENTER)); 294 295 // Rating group 296 BView* ratingStack = new BView("rating stack", 0); 297 fRatingLayout = new BCardLayout(); 298 ratingStack->SetLayout(fRatingLayout); 299 ratingStack->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 300 ratingStack->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 301 302 BGroupView* ratingGroup = new BGroupView(B_HORIZONTAL, 303 B_USE_SMALL_SPACING); 304 BLayoutBuilder::Group<>(ratingGroup) 305 .Add(fRatingView) 306 .Add(fAvgRating) 307 .Add(fVoteInfo) 308 ; 309 310 ratingStack->AddChild(ratingGroup); 311 ratingStack->AddChild(fRateButton); 312 fRatingLayout->SetVisibleItem((int32)0); 313 314 BLayoutBuilder::Group<>(this) 315 .Add(fIconView) 316 .AddGroup(B_VERTICAL, 1.0f, 2.2f) 317 .Add(fTitleView) 318 .Add(fPublisherView) 319 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)) 320 .End() 321 .AddGlue(0.1f) 322 .Add(ratingStack, 0.8f) 323 .AddGlue(0.2f) 324 .AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING, 2.0f) 325 .Add(fVersionInfo) 326 .AddGlue() 327 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)) 328 .End() 329 ; 330 331 Clear(); 332 } 333 334 virtual ~TitleView() 335 { 336 } 337 338 virtual void AttachedToWindow() 339 { 340 fRateButton->SetTarget(this); 341 fRatingView->SetTarget(this); 342 } 343 344 virtual void MessageReceived(BMessage* message) 345 { 346 switch (message->what) { 347 case MSG_RATE_PACKAGE: 348 // Forward to window (The button has us as target so 349 // we receive the message below.) 350 Window()->PostMessage(MSG_RATE_PACKAGE); 351 break; 352 353 case MSG_MOUSE_ENTERED_RATING: 354 fRatingLayout->SetVisibleItem(1); 355 break; 356 357 case MSG_MOUSE_EXITED_RATING: 358 fRatingLayout->SetVisibleItem((int32)0); 359 break; 360 } 361 } 362 363 void SetPackage(const PackageInfoRef package) 364 { 365 BitmapHolderRef bitmapHolderRef; 366 BSize iconSize = BControlLook::ComposeIconSize(32.0); 367 status_t iconResult = fPackageIconRepository.GetIcon(package->Name(), iconSize.Width() + 1, 368 bitmapHolderRef); 369 370 if (iconResult == B_OK) 371 fIconView->SetBitmap(bitmapHolderRef); 372 else 373 fIconView->UnsetBitmap(); 374 375 BString title; 376 PackageUtils::TitleOrName(package, title); 377 fTitleView->SetText(title); 378 379 BString publisher = package->Publisher().Name(); 380 if (publisher.CountChars() > 45) { 381 fPublisherView->SetToolTip(publisher); 382 fPublisherView->SetText(publisher.TruncateChars(45) 383 .Append(B_UTF8_ELLIPSIS)); 384 } else { 385 fPublisherView->SetToolTip(""); 386 fPublisherView->SetText(publisher); 387 } 388 389 fVersionInfo->SetText(package->Version().ToString()); 390 391 UserRatingInfoRef userRatingInfo = package->UserRatingInfo(); 392 UserRatingSummaryRef userRatingSummary; 393 394 if (userRatingInfo.IsSet()) { 395 userRatingSummary = userRatingInfo->Summary(); 396 } 397 398 bool hasAverageRating = false; 399 400 if (userRatingSummary.IsSet()) { 401 if (userRatingSummary->AverageRating() != RATING_MISSING) { 402 hasAverageRating = true; 403 } 404 } 405 406 if (hasAverageRating) { 407 fRatingView->SetRating(userRatingSummary->AverageRating()); 408 409 BString avgRating; 410 avgRating.SetToFormat("%.1f", userRatingSummary->AverageRating()); 411 fAvgRating->SetText(avgRating); 412 413 BString votes; 414 votes.SetToFormat("%d", userRatingSummary->RatingCount()); 415 416 BString voteInfo(B_TRANSLATE("(%Votes%)")); 417 voteInfo.ReplaceAll("%Votes%", votes); 418 419 fVoteInfo->SetText(voteInfo); 420 } else { 421 fRatingView->SetRating(RATING_MISSING); 422 fAvgRating->SetText(""); 423 fVoteInfo->SetText(B_TRANSLATE("n/a")); 424 } 425 426 InvalidateLayout(); 427 Invalidate(); 428 } 429 430 void Clear() 431 { 432 fIconView->UnsetBitmap(); 433 fTitleView->SetText(""); 434 fPublisherView->SetText(""); 435 fVersionInfo->SetText(""); 436 fRatingView->SetRating(RATING_MISSING); 437 fAvgRating->SetText(""); 438 fVoteInfo->SetText(""); 439 } 440 441 private: 442 PackageIconRepository& fPackageIconRepository; 443 444 BitmapView* fIconView; 445 446 BStringView* fTitleView; 447 BStringView* fPublisherView; 448 449 BStringView* fVersionInfo; 450 451 BCardLayout* fRatingLayout; 452 453 TransitReportingRatingView* fRatingView; 454 BStringView* fAvgRating; 455 BStringView* fVoteInfo; 456 457 TransitReportingButton* fRateButton; 458 }; 459 460 461 // #pragma mark - PackageActionView 462 463 464 class PackageActionView : public BView { 465 public: 466 PackageActionView(ProcessCoordinatorConsumer* processCoordinatorConsumer, 467 Model* model) 468 : 469 BView("about view", B_WILL_DRAW), 470 fModel(model), 471 fLayout(new BGroupLayout(B_HORIZONTAL)), 472 fProcessCoordinatorConsumer(processCoordinatorConsumer), 473 fStatusLabel(NULL), 474 fStatusBar(NULL) 475 { 476 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 477 SetLayout(fLayout); 478 fLayout->AddItem(BSpaceLayoutItem::CreateGlue()); 479 } 480 481 virtual ~PackageActionView() 482 { 483 Clear(); 484 } 485 486 virtual void MessageReceived(BMessage* message) 487 { 488 switch (message->what) { 489 case MSG_PKG_INSTALL: 490 case MSG_PKG_UNINSTALL: 491 case MSG_PKG_OPEN: 492 _RunPackageAction(message); 493 break; 494 default: 495 BView::MessageReceived(message); 496 break; 497 } 498 } 499 500 void SetPackage(const PackageInfoRef package) 501 { 502 if (PackageUtils::State(package) == DOWNLOADING) { 503 AdoptDownloadProgress(package); 504 } else { 505 AdoptActions(package); 506 } 507 } 508 509 void AdoptActions(const PackageInfoRef package) 510 { 511 PackageManager manager( 512 BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_HOME); 513 514 // TODO: if the given package is either a system package 515 // or a system dependency, show a message indicating that status 516 // so the user knows why no actions are presented 517 std::vector<PackageActionRef> actions; 518 VectorCollector<PackageActionRef> actionsCollector(actions); 519 manager.CollectPackageActions(package, actionsCollector); 520 521 if (_IsClearNeededToAdoptActions(actions)) { 522 Clear(); 523 _CreateAllNewButtonsForAdoptActions(actions); 524 } else { 525 _UpdateExistingButtonsForAdoptActions(actions); 526 } 527 } 528 529 void AdoptDownloadProgress(const PackageInfoRef package) 530 { 531 if (fButtons.CountItems() > 0) 532 Clear(); 533 534 if (fStatusBar == NULL) { 535 fStatusLabel = new BStringView("progress label", 536 B_TRANSLATE("Downloading:")); 537 fLayout->AddView(fStatusLabel); 538 539 fStatusBar = new BStatusBar("progress"); 540 fStatusBar->SetMaxValue(100.0); 541 fStatusBar->SetExplicitMinSize( 542 BSize(StringWidth("XXX") * 5, B_SIZE_UNSET)); 543 544 fLayout->AddView(fStatusBar); 545 } 546 547 fStatusBar->SetTo(PackageUtils::DownloadProgress(package) * 100.0); 548 } 549 550 void Clear() 551 { 552 for (int32 i = fButtons.CountItems() - 1; i >= 0; i--) { 553 BButton* button = (BButton*)fButtons.ItemAtFast(i); 554 button->RemoveSelf(); 555 delete button; 556 } 557 fButtons.MakeEmpty(); 558 559 if (fStatusBar != NULL) { 560 fStatusBar->RemoveSelf(); 561 delete fStatusBar; 562 fStatusBar = NULL; 563 } 564 if (fStatusLabel != NULL) { 565 fStatusLabel->RemoveSelf(); 566 delete fStatusLabel; 567 fStatusLabel = NULL; 568 } 569 } 570 571 private: 572 bool _IsClearNeededToAdoptActions(std::vector<PackageActionRef> actions) 573 { 574 if (fStatusBar != NULL) 575 return true; 576 if (fButtons.CountItems() != static_cast<int32>(actions.size())) 577 return true; 578 return false; 579 } 580 581 void _UpdateExistingButtonsForAdoptActions( 582 std::vector<PackageActionRef> actions) 583 { 584 int32 index = 0; 585 for (int32 i = actions.size() - 1; i >= 0; i--) { 586 const PackageActionRef& action = actions[i]; 587 BMessage* message = new BMessage(action->Message()); 588 BButton* button = (BButton*)fButtons.ItemAtFast(index++); 589 button->SetLabel(action->Title()); 590 button->SetMessage(message); 591 } 592 } 593 594 void _CreateAllNewButtonsForAdoptActions( 595 std::vector<PackageActionRef> actions) 596 { 597 for (int32 i = actions.size() - 1; i >= 0; i--) { 598 const PackageActionRef& action = actions[i]; 599 BMessage* message = new BMessage(action->Message()); 600 BButton* button = new BButton(action->Title(), message); 601 fLayout->AddView(button); 602 button->SetTarget(this); 603 604 fButtons.AddItem(button); 605 } 606 } 607 608 bool _MatchesPackageActionMessage(BButton *button, BMessage* message) 609 { 610 if (button == NULL) 611 return false; 612 BMessage* buttonMessage = button->Message(); 613 if (buttonMessage == NULL) 614 return false; 615 return buttonMessage == message; 616 } 617 618 /*! Since the action has been fired; it should not be possible 619 to run it again because this may make no sense. For this 620 reason, disable the corresponding button. 621 */ 622 623 void _DisableButtonForPackageActionMessage(BMessage* message) 624 { 625 for (int32 i = 0; i < fButtons.CountItems(); i++) { 626 BButton* button = static_cast<BButton*>(fButtons.ItemAt(i)); 627 if (_MatchesPackageActionMessage(button, message)) 628 button->SetEnabled(false); 629 } 630 } 631 632 void _RunPackageAction(BMessage* message) 633 { 634 ProcessCoordinator *processCoordinator = 635 ProcessCoordinatorFactory::CreatePackageActionCoordinator( 636 fModel, message); 637 fProcessCoordinatorConsumer->Consume(processCoordinator); 638 _DisableButtonForPackageActionMessage(message); 639 } 640 641 private: 642 Model* fModel; 643 BGroupLayout* fLayout; 644 ProcessCoordinatorConsumer* 645 fProcessCoordinatorConsumer; 646 BList fButtons; 647 648 BStringView* fStatusLabel; 649 BStatusBar* fStatusBar; 650 }; 651 652 653 // #pragma mark - AboutView 654 655 656 enum { 657 MSG_VISIT_PUBLISHER_WEBSITE = 'vpws', 658 }; 659 660 661 class AboutView : public BView { 662 public: 663 AboutView() 664 : 665 BView("about view", 0) 666 { 667 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 668 669 fDescriptionView = new MarkupTextView("description view"); 670 fDescriptionView->SetViewUIColor(ViewUIColor(), kContentTint); 671 fDescriptionView->SetInsets(be_plain_font->Size()); 672 673 BScrollView* scrollView = new GeneralContentScrollView( 674 "description scroll view", fDescriptionView); 675 676 BFont smallFont; 677 GetFont(&smallFont); 678 smallFont.SetSize(std::max(9.0f, ceilf(smallFont.Size() * 0.85f))); 679 680 fScreenshotView = new LinkedBitmapView("screenshot view", 681 new BMessage(MSG_SHOW_SCREENSHOT)); 682 fScreenshotView->SetExplicitMinSize(BSize(64.0f, 64.0f)); 683 fScreenshotView->SetExplicitMaxSize( 684 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 685 fScreenshotView->SetExplicitAlignment( 686 BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP)); 687 688 fWebsiteIconView = new BitmapView("website icon view"); 689 fWebsiteLinkView = new LinkView("website link view", "", 690 new BMessage(MSG_VISIT_PUBLISHER_WEBSITE)); 691 fWebsiteLinkView->SetFont(&smallFont); 692 693 BGroupView* leftGroup = new BGroupView(B_VERTICAL, 694 B_USE_DEFAULT_SPACING); 695 696 fScreenshotView->SetViewUIColor(ViewUIColor(), kContentTint); 697 fWebsiteLinkView->SetViewUIColor(ViewUIColor(), kContentTint); 698 699 BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f) 700 .AddGroup(leftGroup, 1.0f) 701 .Add(fScreenshotView) 702 .AddGroup(B_HORIZONTAL) 703 .AddGrid(B_USE_HALF_ITEM_SPACING, B_USE_HALF_ITEM_SPACING) 704 .Add(fWebsiteIconView, 0, 1) 705 .Add(fWebsiteLinkView, 1, 1) 706 .End() 707 .End() 708 .SetInsets(B_USE_DEFAULT_SPACING) 709 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)) 710 .End() 711 .Add(scrollView, 2.0f) 712 713 .SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED)) 714 .SetInsets(0.0f, -1.0f, -1.0f, -1.0f) 715 ; 716 } 717 718 virtual ~AboutView() 719 { 720 Clear(); 721 } 722 723 virtual void AttachedToWindow() 724 { 725 fScreenshotView->SetTarget(this); 726 fWebsiteLinkView->SetTarget(this); 727 } 728 729 virtual void AllAttached() 730 { 731 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 732 733 for (int32 index = 0; index < CountChildren(); ++index) 734 ChildAt(index)->AdoptParentColors(); 735 } 736 737 virtual void MessageReceived(BMessage* message) 738 { 739 switch (message->what) { 740 case MSG_SHOW_SCREENSHOT: 741 { 742 // Forward to window for now 743 Window()->PostMessage(message, Window()); 744 break; 745 } 746 747 case MSG_VISIT_PUBLISHER_WEBSITE: 748 { 749 BUrl url(fWebsiteLinkView->Text()); 750 url.OpenWithPreferredApplication(); 751 break; 752 } 753 754 default: 755 BView::MessageReceived(message); 756 break; 757 } 758 } 759 760 void SetScreenshotThumbnail(BitmapHolderRef bitmapHolderRef) 761 { 762 if (bitmapHolderRef.IsSet()) { 763 fScreenshotView->SetBitmap(bitmapHolderRef); 764 fScreenshotView->SetEnabled(true); 765 } else { 766 fScreenshotView->UnsetBitmap(); 767 fScreenshotView->SetEnabled(false); 768 } 769 } 770 771 void SetPackage(const PackageInfoRef package) 772 { 773 PackageLocalizedTextRef localizedText = package->LocalizedText(); 774 BString summary = ""; 775 BString description = ""; 776 777 if (localizedText.IsSet()) { 778 summary = localizedText->Summary(); 779 description = localizedText->Description(); 780 } 781 782 fDescriptionView->SetText(summary, description); 783 fWebsiteIconView->SetBitmap(SharedIcons::IconHTMLPackage16Scaled()); 784 _SetContactInfo(fWebsiteLinkView, package->Publisher().Website()); 785 } 786 787 void Clear() 788 { 789 fDescriptionView->SetText(""); 790 fWebsiteIconView->UnsetBitmap(); 791 fWebsiteLinkView->SetText(""); 792 fScreenshotView->UnsetBitmap(); 793 fScreenshotView->SetEnabled(false); 794 } 795 796 private: 797 void _SetContactInfo(LinkView* view, const BString& string) 798 { 799 if (string.Length() > 0) { 800 view->SetText(string); 801 view->SetEnabled(true); 802 } else { 803 view->SetText(B_TRANSLATE("<no info>")); 804 view->SetEnabled(false); 805 } 806 } 807 808 private: 809 MarkupTextView* fDescriptionView; 810 811 LinkedBitmapView* fScreenshotView; 812 813 BitmapView* fWebsiteIconView; 814 LinkView* fWebsiteLinkView; 815 }; 816 817 818 // #pragma mark - UserRatingsView 819 820 821 class RatingItemView : public BGroupView { 822 public: 823 RatingItemView(const UserRatingRef rating) 824 : 825 BGroupView(B_HORIZONTAL, 0.0f) 826 { 827 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 828 829 BGroupLayout* verticalGroup = new BGroupLayout(B_VERTICAL, 0.0f); 830 GroupLayout()->AddItem(verticalGroup); 831 832 { 833 BStringView* userNicknameView = new BStringView("user-nickname", 834 rating->User().NickName()); 835 userNicknameView->SetFont(be_bold_font); 836 verticalGroup->AddView(userNicknameView); 837 } 838 839 BGroupLayout* ratingGroup = 840 new BGroupLayout(B_HORIZONTAL, B_USE_DEFAULT_SPACING); 841 verticalGroup->AddItem(ratingGroup); 842 843 if (rating->Rating() >= 0) { 844 RatingView* ratingView = new RatingView("package rating view"); 845 ratingView->SetRating(rating->Rating()); 846 ratingGroup->AddView(ratingView); 847 } 848 849 { 850 BString createTimestampPresentation = 851 LocaleUtils::TimestampToDateTimeString( 852 rating->CreateTimestamp()); 853 854 BString ratingContextDescription( 855 B_TRANSLATE("%hd.timestamp% (version %hd.version%)")); 856 ratingContextDescription.ReplaceAll("%hd.timestamp%", 857 createTimestampPresentation); 858 ratingContextDescription.ReplaceAll("%hd.version%", 859 rating->PackageVersion()); 860 861 BStringView* ratingContextView = new BStringView("rating-context", 862 ratingContextDescription); 863 BFont versionFont(be_plain_font); 864 ratingContextView->SetFont(&versionFont); 865 ratingGroup->AddView(ratingContextView); 866 } 867 868 ratingGroup->AddItem(BSpaceLayoutItem::CreateGlue()); 869 870 if (rating->Comment() > 0) { 871 TextView* textView = new TextView("rating-text"); 872 ParagraphStyle paragraphStyle(textView->ParagraphStyle()); 873 paragraphStyle.SetJustify(true); 874 textView->SetParagraphStyle(paragraphStyle); 875 textView->SetText(rating->Comment()); 876 verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f)); 877 verticalGroup->AddView(textView); 878 verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f)); 879 } 880 881 verticalGroup->SetInsets(B_USE_DEFAULT_SPACING); 882 883 SetFlags(Flags() | B_WILL_DRAW); 884 } 885 886 void AllAttached() 887 { 888 for (int32 index = 0; index < CountChildren(); ++index) 889 ChildAt(index)->AdoptParentColors(); 890 } 891 892 void Draw(BRect rect) 893 { 894 rgb_color color = mix_color(ViewColor(), ui_color(B_PANEL_TEXT_COLOR), 64); 895 SetHighColor(color); 896 StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom()); 897 } 898 899 }; 900 901 902 class RatingSummaryView : public BGridView { 903 public: 904 RatingSummaryView() 905 : 906 BGridView("rating summary view", B_USE_HALF_ITEM_SPACING, 0.0f) 907 { 908 float tint = kContentTint - 0.1; 909 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, tint); 910 911 BLayoutBuilder::Grid<> layoutBuilder(this); 912 913 BFont smallFont; 914 GetFont(&smallFont); 915 smallFont.SetSize(std::max(9.0f, floorf(smallFont.Size() * 0.85f))); 916 917 for (int32 i = 0; i < 5; i++) { 918 BString label; 919 label.SetToFormat("%" B_PRId32, 5 - i); 920 fLabelViews[i] = new BStringView("", label); 921 fLabelViews[i]->SetFont(&smallFont); 922 fLabelViews[i]->SetViewUIColor(ViewUIColor(), tint); 923 layoutBuilder.Add(fLabelViews[i], 0, i); 924 925 fDiagramBarViews[i] = new DiagramBarView(); 926 layoutBuilder.Add(fDiagramBarViews[i], 1, i); 927 928 fCountViews[i] = new BStringView("", ""); 929 fCountViews[i]->SetFont(&smallFont); 930 fCountViews[i]->SetViewUIColor(ViewUIColor(), tint); 931 fCountViews[i]->SetAlignment(B_ALIGN_RIGHT); 932 layoutBuilder.Add(fCountViews[i], 2, i); 933 } 934 935 layoutBuilder.SetInsets(5); 936 } 937 938 void SetToSummary(const UserRatingSummaryRef summary) { 939 if (!summary.IsSet()) 940 Clear(); 941 else { 942 // note that the logic here inverts the ordering of the stars so that 943 // star 5 is at the top. 944 945 for (int32 i = 0; i < 5; i++) { 946 int32 count = summary->RatingCountByStar(5 - i); 947 948 BString label; 949 label.SetToFormat("%" B_PRId32, count); 950 fCountViews[i]->SetText(label); 951 952 int ratingCount = summary->RatingCount(); 953 954 if (ratingCount > 0) { 955 fDiagramBarViews[i]->SetValue( 956 static_cast<float>(count) / static_cast<float>(ratingCount)); 957 } else 958 fDiagramBarViews[i]->SetValue(0.0f); 959 } 960 } 961 } 962 963 void Clear() { 964 for (int32 i = 0; i < 5; i++) { 965 fCountViews[i]->SetText(""); 966 fDiagramBarViews[i]->SetValue(0.0f); 967 } 968 } 969 970 private: 971 BStringView* fLabelViews[5]; 972 DiagramBarView* fDiagramBarViews[5]; 973 BStringView* fCountViews[5]; 974 }; 975 976 977 class UserRatingsView : public BGroupView { 978 public: 979 UserRatingsView() 980 : 981 BGroupView("package ratings view", B_HORIZONTAL) 982 { 983 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 984 985 fRatingSummaryView = new RatingSummaryView(); 986 987 ScrollableGroupView* ratingsContainerView = new ScrollableGroupView(); 988 ratingsContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR, 989 kContentTint); 990 fRatingContainerLayout = ratingsContainerView->GroupLayout(); 991 992 BScrollView* scrollView = new RatingsScrollView( 993 "ratings scroll view", ratingsContainerView); 994 scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 995 B_SIZE_UNLIMITED)); 996 997 BLayoutBuilder::Group<>(this) 998 .AddGroup(B_VERTICAL) 999 .Add(fRatingSummaryView, 0.0f) 1000 .AddGlue() 1001 .SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f) 1002 .End() 1003 .AddStrut(64.0) 1004 .Add(scrollView, 1.0f) 1005 .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f) 1006 ; 1007 } 1008 1009 virtual ~UserRatingsView() 1010 { 1011 Clear(); 1012 } 1013 1014 void SetPackage(const PackageInfoRef package) 1015 { 1016 ClearRatings(); 1017 1018 UserRatingInfoRef userRatingInfo = package->UserRatingInfo(); 1019 UserRatingSummaryRef userRatingSummary; 1020 1021 if (userRatingInfo.IsSet()) 1022 userRatingSummary = userRatingInfo->Summary(); 1023 1024 fRatingSummaryView->SetToSummary(userRatingSummary); 1025 1026 int count = 0; 1027 1028 if (userRatingInfo.IsSet()) 1029 count = userRatingInfo->CountUserRatings(); 1030 1031 if (count == 0) { 1032 BStringView* noRatingsView = new BStringView("no ratings", 1033 B_TRANSLATE("No user ratings available.")); 1034 noRatingsView->SetViewUIColor(ViewUIColor(), kContentTint); 1035 noRatingsView->SetAlignment(B_ALIGN_CENTER); 1036 noRatingsView->SetHighColor(disable_color(ui_color(B_PANEL_TEXT_COLOR), 1037 ViewColor())); 1038 noRatingsView->SetExplicitMaxSize( 1039 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 1040 fRatingContainerLayout->AddView(0, noRatingsView); 1041 } else { 1042 for (int i = count - 1; i >= 0; i--) { 1043 UserRatingRef rating = userRatingInfo->UserRatingAtIndex(i); 1044 // was previously filtering comments just for the current 1045 // user's language, but as there are not so many comments at 1046 // the moment, just show all of them for now. 1047 RatingItemView* view = new RatingItemView(rating); 1048 fRatingContainerLayout->AddView(0, view); 1049 } 1050 } 1051 1052 InvalidateLayout(); 1053 } 1054 1055 void Clear() 1056 { 1057 fRatingSummaryView->Clear(); 1058 ClearRatings(); 1059 } 1060 1061 void ClearRatings() 1062 { 1063 for (int32 i = fRatingContainerLayout->CountItems() - 1; 1064 BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) { 1065 BView* view = dynamic_cast<RatingItemView*>(item->View()); 1066 if (view == NULL) 1067 view = dynamic_cast<BStringView*>(item->View()); 1068 if (view != NULL) { 1069 view->RemoveSelf(); 1070 delete view; 1071 } 1072 } 1073 } 1074 1075 private: 1076 BGroupLayout* fRatingContainerLayout; 1077 RatingSummaryView* fRatingSummaryView; 1078 }; 1079 1080 1081 // #pragma mark - ContentsView 1082 1083 1084 class ContentsView : public BGroupView { 1085 public: 1086 ContentsView() 1087 : 1088 BGroupView("package contents view", B_HORIZONTAL) 1089 { 1090 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 1091 1092 fPackageContents = new PackageContentsView("contents_list"); 1093 AddChild(fPackageContents); 1094 1095 } 1096 1097 virtual ~ContentsView() 1098 { 1099 } 1100 1101 virtual void Draw(BRect updateRect) 1102 { 1103 } 1104 1105 void SetPackage(const PackageInfoRef package) 1106 { 1107 fPackageContents->SetPackage(package); 1108 } 1109 1110 void Clear() 1111 { 1112 fPackageContents->Clear(); 1113 } 1114 1115 private: 1116 PackageContentsView* fPackageContents; 1117 }; 1118 1119 1120 // #pragma mark - ChangelogView 1121 1122 1123 class ChangelogView : public BGroupView { 1124 public: 1125 ChangelogView() 1126 : 1127 BGroupView("package changelog view", B_HORIZONTAL) 1128 { 1129 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 1130 1131 fTextView = new MarkupTextView("changelog view"); 1132 fTextView->SetLowUIColor(ViewUIColor()); 1133 fTextView->SetInsets(be_plain_font->Size()); 1134 1135 BScrollView* scrollView = new GeneralContentScrollView( 1136 "changelog scroll view", fTextView); 1137 1138 BLayoutBuilder::Group<>(this) 1139 .Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f)) 1140 .Add(scrollView, 1.0f) 1141 .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f) 1142 ; 1143 } 1144 1145 virtual ~ChangelogView() 1146 { 1147 } 1148 1149 virtual void Draw(BRect updateRect) 1150 { 1151 } 1152 1153 void SetPackage(const PackageInfoRef package) 1154 { 1155 PackageLocalizedTextRef localizedText = package->LocalizedText(); 1156 BString changelog; 1157 1158 if (localizedText.IsSet()) { 1159 if (localizedText->HasChangelog()) 1160 changelog = localizedText->Changelog(); 1161 } 1162 1163 if (changelog.Length() > 0) 1164 fTextView->SetText(changelog); 1165 else 1166 fTextView->SetDisabledText(B_TRANSLATE("No changelog available.")); 1167 } 1168 1169 void Clear() 1170 { 1171 fTextView->SetText(""); 1172 } 1173 1174 private: 1175 MarkupTextView* fTextView; 1176 }; 1177 1178 1179 // #pragma mark - PagesView 1180 1181 1182 class PagesView : public BTabView { 1183 public: 1184 PagesView() 1185 : 1186 BTabView("pages view", B_WIDTH_FROM_WIDEST) 1187 { 1188 SetBorder(B_NO_BORDER); 1189 1190 fAboutView = new AboutView(); 1191 fUserRatingsView = new UserRatingsView(); 1192 fChangelogView = new ChangelogView(); 1193 fContentsView = new ContentsView(); 1194 1195 AddTab(fAboutView); 1196 AddTab(fUserRatingsView); 1197 AddTab(fChangelogView); 1198 AddTab(fContentsView); 1199 1200 TabAt(TAB_ABOUT)->SetLabel(B_TRANSLATE("About")); 1201 TabAt(TAB_RATINGS)->SetLabel(B_TRANSLATE("Ratings")); 1202 TabAt(TAB_CHANGELOG)->SetLabel(B_TRANSLATE("Changelog")); 1203 TabAt(TAB_CONTENTS)->SetLabel(B_TRANSLATE("Contents")); 1204 1205 Select(TAB_ABOUT); 1206 } 1207 1208 virtual ~PagesView() 1209 { 1210 Clear(); 1211 } 1212 1213 void SetScreenshotThumbnail(BitmapHolderRef bitmap) 1214 { 1215 fAboutView->SetScreenshotThumbnail(bitmap); 1216 } 1217 1218 void SetPackage(const PackageInfoRef package, bool switchToDefaultTab) 1219 { 1220 if (switchToDefaultTab) 1221 Select(TAB_ABOUT); 1222 1223 bool enableChangelogTab = false; 1224 bool enableContentsTab = false; 1225 1226 if (package.IsSet()) { 1227 PackageLocalizedTextRef localizedText = package->LocalizedText(); 1228 1229 if (localizedText.IsSet()) 1230 enableChangelogTab = localizedText->HasChangelog(); 1231 1232 enableContentsTab = PackageUtils::IsActivatedOrLocalFile(package); 1233 } 1234 1235 TabAt(TAB_CHANGELOG)->SetEnabled(enableChangelogTab); 1236 TabAt(TAB_CONTENTS)->SetEnabled(enableContentsTab); 1237 Invalidate(TabFrame(TAB_CHANGELOG)); 1238 Invalidate(TabFrame(TAB_CONTENTS)); 1239 1240 fAboutView->SetPackage(package); 1241 fUserRatingsView->SetPackage(package); 1242 fChangelogView->SetPackage(package); 1243 fContentsView->SetPackage(package); 1244 } 1245 1246 void Clear() 1247 { 1248 fAboutView->Clear(); 1249 fUserRatingsView->Clear(); 1250 fChangelogView->Clear(); 1251 fContentsView->Clear(); 1252 } 1253 1254 private: 1255 AboutView* fAboutView; 1256 UserRatingsView* fUserRatingsView; 1257 ChangelogView* fChangelogView; 1258 ContentsView* fContentsView; 1259 }; 1260 1261 1262 // #pragma mark - PackageInfoView 1263 1264 1265 PackageInfoView::PackageInfoView(Model* model, 1266 ProcessCoordinatorConsumer* processCoordinatorConsumer) 1267 : 1268 BView("package info view", 0), 1269 fModel(model), 1270 fPackageListener(new(std::nothrow) OnePackageMessagePackageListener(this)), 1271 fProcessCoordinatorConsumer(processCoordinatorConsumer) 1272 { 1273 fCardLayout = new BCardLayout(); 1274 SetLayout(fCardLayout); 1275 1276 BGroupView* noPackageCard = new BGroupView("no package card", B_VERTICAL); 1277 AddChild(noPackageCard); 1278 1279 BStringView* noPackageView = new BStringView("no package view", 1280 B_TRANSLATE("Click a package to view information")); 1281 noPackageView->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT); 1282 noPackageView->SetExplicitAlignment(BAlignment( 1283 B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER)); 1284 1285 BLayoutBuilder::Group<>(noPackageCard) 1286 .Add(noPackageView) 1287 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)) 1288 ; 1289 1290 BGroupView* packageCard = new BGroupView("package card", B_VERTICAL); 1291 AddChild(packageCard); 1292 1293 fCardLayout->SetVisibleItem((int32)0); 1294 1295 fTitleView = new TitleView(fModel->GetPackageIconRepository()); 1296 fPackageActionView = new PackageActionView(processCoordinatorConsumer, 1297 model); 1298 fPackageActionView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 1299 B_SIZE_UNSET)); 1300 fPagesView = new PagesView(); 1301 1302 BLayoutBuilder::Group<>(packageCard) 1303 .AddGroup(B_HORIZONTAL, 0.0f) 1304 .Add(fTitleView, 6.0f) 1305 .Add(fPackageActionView, 1.0f) 1306 .SetInsets( 1307 B_USE_DEFAULT_SPACING, 0.0f, 1308 B_USE_DEFAULT_SPACING, 0.0f) 1309 .End() 1310 .Add(fPagesView) 1311 ; 1312 1313 Clear(); 1314 } 1315 1316 1317 PackageInfoView::~PackageInfoView() 1318 { 1319 fPackageListener->SetPackage(PackageInfoRef(NULL)); 1320 delete fPackageListener; 1321 } 1322 1323 1324 void 1325 PackageInfoView::AttachedToWindow() 1326 { 1327 } 1328 1329 1330 void 1331 PackageInfoView::MessageReceived(BMessage* message) 1332 { 1333 switch (message->what) { 1334 case MSG_UPDATE_PACKAGE: 1335 { 1336 if (!fPackageListener->Package().IsSet()) 1337 break; 1338 1339 BString name; 1340 uint32 changes; 1341 if (message->FindString("name", &name) != B_OK 1342 || message->FindUInt32("changes", &changes) != B_OK) { 1343 break; 1344 } 1345 1346 const PackageInfoRef& package = fPackageListener->Package(); 1347 if (package->Name() != name) 1348 break; 1349 1350 BAutolock _(fModel->Lock()); 1351 1352 if ((changes & PKG_CHANGED_LOCALIZED_TEXT) != 0 1353 || (changes & PKG_CHANGED_SCREENSHOTS) != 0 1354 || (changes & PKG_CHANGED_RATINGS) != 0 1355 || (changes & PKG_CHANGED_LOCAL_INFO) != 0) { 1356 fPagesView->SetPackage(package, false); 1357 } 1358 1359 if ((changes & PKG_CHANGED_LOCALIZED_TEXT) != 0 || (changes & PKG_CHANGED_RATINGS) != 0) 1360 fTitleView->SetPackage(package); 1361 1362 if ((changes & PKG_CHANGED_LOCAL_INFO) != 0) 1363 fPackageActionView->SetPackage(package); 1364 1365 break; 1366 } 1367 default: 1368 BView::MessageReceived(message); 1369 break; 1370 } 1371 } 1372 1373 1374 void 1375 PackageInfoView::SetPackage(const PackageInfoRef& packageRef) 1376 { 1377 BAutolock _(fModel->Lock()); 1378 1379 if (!packageRef.IsSet()) { 1380 Clear(); 1381 return; 1382 } 1383 1384 bool switchToDefaultTab = true; 1385 if (fPackage == packageRef) { 1386 // When asked to display the already showing package ref, 1387 // don't switch to the default tab. 1388 switchToDefaultTab = false; 1389 } else if (fPackage.IsSet() && packageRef.IsSet() 1390 && fPackage->Name() == packageRef->Name()) { 1391 // When asked to display a different PackageInfo instance, 1392 // but it has the same package title as the already showing 1393 // instance, this probably means there was a repository 1394 // refresh and we are in fact still requested to show the 1395 // same package as before the refresh. 1396 switchToDefaultTab = false; 1397 } 1398 1399 fTitleView->SetPackage(packageRef); 1400 fPackageActionView->SetPackage(packageRef); 1401 fPagesView->SetPackage(packageRef, switchToDefaultTab); 1402 1403 _SetPackageScreenshotThumb(packageRef); 1404 1405 fCardLayout->SetVisibleItem(1); 1406 1407 fPackageListener->SetPackage(packageRef); 1408 1409 // Set the fPackage reference last, so we keep a reference to the 1410 // previous package before switching all the views to the new package. 1411 // Otherwise the PackageInfo instance may go away because we had the 1412 // last reference. And some of the views, the PackageActionView for 1413 // example, keeps references to stuff from the previous package and 1414 // access it while switching to the new package. 1415 fPackage = packageRef; 1416 } 1417 1418 1419 /*! See if the screenshot is already cached; if it is then load it 1420 immediately. If it is not then trigger a process to start it in 1421 the background. A message will come through later once it is 1422 cached and ready to load. 1423 */ 1424 1425 void 1426 PackageInfoView::_SetPackageScreenshotThumb(const PackageInfoRef& package) 1427 { 1428 ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package); 1429 bool hasCachedBitmap = false; 1430 1431 if (desiredCoordinate.IsValid()) { 1432 bool present = false; 1433 if (fModel->GetPackageScreenshotRepository()->HasCachedScreenshot( 1434 desiredCoordinate, &present) != B_OK) { 1435 HDERROR("unable to ascertain if screenshot is present for pkg [%s]", package->Name().String()); 1436 } else { 1437 if (present) { 1438 HDDEBUG("screenshot is already cached for [%s] -- will load it", package->Name().String()); 1439 _HandleScreenshotCached(package, desiredCoordinate); 1440 hasCachedBitmap = true; 1441 } else { 1442 HDDEBUG("screenshot is not cached [%s] -- will cache it", package->Name().String()); 1443 ProcessCoordinator *processCoordinator = 1444 ProcessCoordinatorFactory::CacheScreenshotCoordinator( 1445 fModel, desiredCoordinate); 1446 fProcessCoordinatorConsumer->Consume(processCoordinator); 1447 } 1448 } 1449 } else 1450 HDDEBUG("no screenshot for pkg [%s]", package->Name().String()); 1451 1452 if (!hasCachedBitmap) 1453 fPagesView->SetScreenshotThumbnail(BitmapHolderRef()); 1454 } 1455 1456 1457 /*static*/ const ScreenshotCoordinate 1458 PackageInfoView::_ScreenshotThumbCoordinate(const PackageInfoRef& package) 1459 { 1460 if (!package.IsSet()) 1461 return ScreenshotCoordinate(); 1462 1463 PackageScreenshotInfoRef screenshotInfo = package->ScreenshotInfo(); 1464 1465 if (!screenshotInfo.IsSet() || screenshotInfo->Count() == 0) 1466 return ScreenshotCoordinate(); 1467 1468 ScreenshotInfoRef screenshot = screenshotInfo->ScreenshotAtIndex(0); 1469 1470 if (!screenshot.IsSet()) 1471 return ScreenshotCoordinate(); 1472 1473 uint32 screenshotSizeScaled 1474 = MAX(static_cast<uint32>(BControlLook::ComposeIconSize(kScreenshotSize).Width()), 1475 MAX_IMAGE_SIZE); 1476 1477 return ScreenshotCoordinate(screenshot->Code(), screenshotSizeScaled + 1, 1478 screenshotSizeScaled + 1); 1479 } 1480 1481 1482 /*! This message will arrive when the data in the screenshot cache 1483 contains a new screenshot. This logic can check to see if the 1484 screenshot arriving is the one that is being waited for and 1485 would then load the screenshot from the cache and display it. 1486 */ 1487 1488 void 1489 PackageInfoView::HandleScreenshotCached(const ScreenshotCoordinate& coordinate) 1490 { 1491 HDINFO("handle screenshot cached [%s] %" B_PRIu16 " x %" B_PRIu16, coordinate.Code().String(), 1492 coordinate.Width(), coordinate.Height()); 1493 _HandleScreenshotCached(fPackage, coordinate); 1494 } 1495 1496 1497 void 1498 PackageInfoView::_HandleScreenshotCached(const PackageInfoRef& package, 1499 const ScreenshotCoordinate& coordinate) 1500 { 1501 ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package); 1502 1503 if (desiredCoordinate.IsValid() && desiredCoordinate == coordinate) { 1504 HDDEBUG("screenshot [%s] has been cached and matched; will load", 1505 coordinate.Code().String()); 1506 BitmapHolderRef bitmapHolderRef; 1507 if (fModel->GetPackageScreenshotRepository()->CacheAndLoadScreenshot(coordinate, 1508 bitmapHolderRef) != B_OK) { 1509 HDERROR("unable to load the screenshot [%s]", coordinate.Code().String()); 1510 } else { 1511 fPagesView->SetScreenshotThumbnail(bitmapHolderRef); 1512 } 1513 } 1514 } 1515 1516 1517 void 1518 PackageInfoView::Clear() 1519 { 1520 BAutolock _(fModel->Lock()); 1521 1522 fTitleView->Clear(); 1523 fPackageActionView->Clear(); 1524 fPagesView->Clear(); 1525 1526 fCardLayout->SetVisibleItem((int32)0); 1527 1528 fPackageListener->SetPackage(PackageInfoRef(NULL)); 1529 1530 fPackage.Unset(); 1531 } 1532