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