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