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 return; 1001 } 1002 1003 for (int i = count - 1; i >= 0; i--) { 1004 UserRatingRef rating = package->UserRatingAtIndex(i); 1005 // was previously filtering comments just for the current 1006 // user's language, but as there are not so many comments at 1007 // the moment, just show all of them for now. 1008 RatingItemView* view = new RatingItemView(rating); 1009 fRatingContainerLayout->AddView(0, view); 1010 } 1011 } 1012 1013 void Clear() 1014 { 1015 fRatingSummaryView->Clear(); 1016 ClearRatings(); 1017 } 1018 1019 void ClearRatings() 1020 { 1021 for (int32 i = fRatingContainerLayout->CountItems() - 1; 1022 BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) { 1023 BView* view = dynamic_cast<RatingItemView*>(item->View()); 1024 if (view == NULL) 1025 view = dynamic_cast<BStringView*>(item->View()); 1026 if (view != NULL) { 1027 view->RemoveSelf(); 1028 delete view; 1029 } 1030 } 1031 } 1032 1033 private: 1034 BGroupLayout* fRatingContainerLayout; 1035 RatingSummaryView* fRatingSummaryView; 1036 }; 1037 1038 1039 // #pragma mark - ContentsView 1040 1041 1042 class ContentsView : public BGroupView { 1043 public: 1044 ContentsView() 1045 : 1046 BGroupView("package contents view", B_HORIZONTAL) 1047 { 1048 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 1049 1050 fPackageContents = new PackageContentsView("contents_list"); 1051 AddChild(fPackageContents); 1052 1053 } 1054 1055 virtual ~ContentsView() 1056 { 1057 } 1058 1059 virtual void Draw(BRect updateRect) 1060 { 1061 } 1062 1063 void SetPackage(const PackageInfoRef package) 1064 { 1065 fPackageContents->SetPackage(package); 1066 } 1067 1068 void Clear() 1069 { 1070 fPackageContents->Clear(); 1071 } 1072 1073 private: 1074 PackageContentsView* fPackageContents; 1075 }; 1076 1077 1078 // #pragma mark - ChangelogView 1079 1080 1081 class ChangelogView : public BGroupView { 1082 public: 1083 ChangelogView() 1084 : 1085 BGroupView("package changelog view", B_HORIZONTAL) 1086 { 1087 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 1088 1089 fTextView = new MarkupTextView("changelog view"); 1090 fTextView->SetLowUIColor(ViewUIColor()); 1091 fTextView->SetInsets(be_plain_font->Size()); 1092 1093 BScrollView* scrollView = new GeneralContentScrollView( 1094 "changelog scroll view", fTextView); 1095 1096 BLayoutBuilder::Group<>(this) 1097 .Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f)) 1098 .Add(scrollView, 1.0f) 1099 .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f) 1100 ; 1101 } 1102 1103 virtual ~ChangelogView() 1104 { 1105 } 1106 1107 virtual void Draw(BRect updateRect) 1108 { 1109 } 1110 1111 void SetPackage(const PackageInfoRef package) 1112 { 1113 const BString& changelog = package->Changelog(); 1114 if (changelog.Length() > 0) 1115 fTextView->SetText(changelog); 1116 else 1117 fTextView->SetDisabledText(B_TRANSLATE("No changelog available.")); 1118 } 1119 1120 void Clear() 1121 { 1122 fTextView->SetText(""); 1123 } 1124 1125 private: 1126 MarkupTextView* fTextView; 1127 }; 1128 1129 1130 // #pragma mark - PagesView 1131 1132 1133 class PagesView : public BTabView { 1134 public: 1135 PagesView() 1136 : 1137 BTabView("pages view", B_WIDTH_FROM_WIDEST) 1138 { 1139 SetBorder(B_NO_BORDER); 1140 1141 fAboutView = new AboutView(); 1142 fUserRatingsView = new UserRatingsView(); 1143 fChangelogView = new ChangelogView(); 1144 fContentsView = new ContentsView(); 1145 1146 AddTab(fAboutView); 1147 AddTab(fUserRatingsView); 1148 AddTab(fChangelogView); 1149 AddTab(fContentsView); 1150 1151 TabAt(TAB_ABOUT)->SetLabel(B_TRANSLATE("About")); 1152 TabAt(TAB_RATINGS)->SetLabel(B_TRANSLATE("Ratings")); 1153 TabAt(TAB_CHANGELOG)->SetLabel(B_TRANSLATE("Changelog")); 1154 TabAt(TAB_CONTENTS)->SetLabel(B_TRANSLATE("Contents")); 1155 1156 Select(TAB_ABOUT); 1157 } 1158 1159 virtual ~PagesView() 1160 { 1161 Clear(); 1162 } 1163 1164 void SetScreenshotThumbnail(const BitmapRef& bitmap) 1165 { 1166 fAboutView->SetScreenshotThumbnail(bitmap); 1167 } 1168 1169 void SetPackage(const PackageInfoRef package, bool switchToDefaultTab) 1170 { 1171 if (switchToDefaultTab) 1172 Select(TAB_ABOUT); 1173 1174 TabAt(TAB_CHANGELOG)->SetEnabled( 1175 package.IsSet() && package->HasChangelog()); 1176 TabAt(TAB_CONTENTS)->SetEnabled( 1177 package.IsSet() 1178 && (package->State() == ACTIVATED || package->IsLocalFile())); 1179 Invalidate(TabFrame(TAB_CHANGELOG)); 1180 Invalidate(TabFrame(TAB_CONTENTS)); 1181 1182 fAboutView->SetPackage(package); 1183 fUserRatingsView->SetPackage(package); 1184 fChangelogView->SetPackage(package); 1185 fContentsView->SetPackage(package); 1186 } 1187 1188 void Clear() 1189 { 1190 fAboutView->Clear(); 1191 fUserRatingsView->Clear(); 1192 fChangelogView->Clear(); 1193 fContentsView->Clear(); 1194 } 1195 1196 private: 1197 AboutView* fAboutView; 1198 UserRatingsView* fUserRatingsView; 1199 ChangelogView* fChangelogView; 1200 ContentsView* fContentsView; 1201 }; 1202 1203 1204 // #pragma mark - PackageInfoView 1205 1206 1207 PackageInfoView::PackageInfoView(Model* model, 1208 ProcessCoordinatorConsumer* processCoordinatorConsumer) 1209 : 1210 BView("package info view", 0), 1211 fModel(model), 1212 fPackageListener(new(std::nothrow) OnePackageMessagePackageListener(this)), 1213 fProcessCoordinatorConsumer(processCoordinatorConsumer) 1214 { 1215 fCardLayout = new BCardLayout(); 1216 SetLayout(fCardLayout); 1217 1218 BGroupView* noPackageCard = new BGroupView("no package card", B_VERTICAL); 1219 AddChild(noPackageCard); 1220 1221 BStringView* noPackageView = new BStringView("no package view", 1222 B_TRANSLATE("Click a package to view information")); 1223 noPackageView->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT); 1224 noPackageView->SetExplicitAlignment(BAlignment( 1225 B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER)); 1226 1227 BLayoutBuilder::Group<>(noPackageCard) 1228 .Add(noPackageView) 1229 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)) 1230 ; 1231 1232 BGroupView* packageCard = new BGroupView("package card", B_VERTICAL); 1233 AddChild(packageCard); 1234 1235 fCardLayout->SetVisibleItem((int32)0); 1236 1237 fTitleView = new TitleView(fModel->GetPackageIconRepository()); 1238 fPackageActionView = new PackageActionView(processCoordinatorConsumer, 1239 model); 1240 fPackageActionView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 1241 B_SIZE_UNSET)); 1242 fPagesView = new PagesView(); 1243 1244 BLayoutBuilder::Group<>(packageCard) 1245 .AddGroup(B_HORIZONTAL, 0.0f) 1246 .Add(fTitleView, 6.0f) 1247 .Add(fPackageActionView, 1.0f) 1248 .SetInsets( 1249 B_USE_DEFAULT_SPACING, 0.0f, 1250 B_USE_DEFAULT_SPACING, 0.0f) 1251 .End() 1252 .Add(fPagesView) 1253 ; 1254 1255 Clear(); 1256 } 1257 1258 1259 PackageInfoView::~PackageInfoView() 1260 { 1261 fPackageListener->SetPackage(PackageInfoRef(NULL)); 1262 delete fPackageListener; 1263 } 1264 1265 1266 void 1267 PackageInfoView::AttachedToWindow() 1268 { 1269 } 1270 1271 1272 void 1273 PackageInfoView::MessageReceived(BMessage* message) 1274 { 1275 switch (message->what) { 1276 case MSG_UPDATE_PACKAGE: 1277 { 1278 if (!fPackageListener->Package().IsSet()) 1279 break; 1280 1281 BString name; 1282 uint32 changes; 1283 if (message->FindString("name", &name) != B_OK 1284 || message->FindUInt32("changes", &changes) != B_OK) { 1285 break; 1286 } 1287 1288 const PackageInfoRef& package = fPackageListener->Package(); 1289 if (package->Name() != name) 1290 break; 1291 1292 BAutolock _(fModel->Lock()); 1293 1294 if ((changes & PKG_CHANGED_SUMMARY) != 0 1295 || (changes & PKG_CHANGED_DESCRIPTION) != 0 1296 || (changes & PKG_CHANGED_SCREENSHOTS) != 0 1297 || (changes & PKG_CHANGED_TITLE) != 0 1298 || (changes & PKG_CHANGED_RATINGS) != 0 1299 || (changes & PKG_CHANGED_STATE) != 0 1300 || (changes & PKG_CHANGED_CHANGELOG) != 0) { 1301 fPagesView->SetPackage(package, false); 1302 } 1303 1304 if ((changes & PKG_CHANGED_TITLE) != 0 1305 || (changes & PKG_CHANGED_RATINGS) != 0) { 1306 fTitleView->SetPackage(package); 1307 } 1308 1309 if ((changes & PKG_CHANGED_STATE) != 0) 1310 fPackageActionView->SetPackage(package); 1311 1312 break; 1313 } 1314 default: 1315 BView::MessageReceived(message); 1316 break; 1317 } 1318 } 1319 1320 1321 void 1322 PackageInfoView::SetPackage(const PackageInfoRef& packageRef) 1323 { 1324 BAutolock _(fModel->Lock()); 1325 1326 if (!packageRef.IsSet()) { 1327 Clear(); 1328 return; 1329 } 1330 1331 bool switchToDefaultTab = true; 1332 if (fPackage == packageRef) { 1333 // When asked to display the already showing package ref, 1334 // don't switch to the default tab. 1335 switchToDefaultTab = false; 1336 } else if (fPackage.IsSet() && packageRef.IsSet() 1337 && fPackage->Name() == packageRef->Name()) { 1338 // When asked to display a different PackageInfo instance, 1339 // but it has the same package title as the already showing 1340 // instance, this probably means there was a repository 1341 // refresh and we are in fact still requested to show the 1342 // same package as before the refresh. 1343 switchToDefaultTab = false; 1344 } 1345 1346 fTitleView->SetPackage(packageRef); 1347 fPackageActionView->SetPackage(packageRef); 1348 fPagesView->SetPackage(packageRef, switchToDefaultTab); 1349 1350 _SetPackageScreenshotThumb(packageRef); 1351 1352 fCardLayout->SetVisibleItem(1); 1353 1354 fPackageListener->SetPackage(packageRef); 1355 1356 // Set the fPackage reference last, so we keep a reference to the 1357 // previous package before switching all the views to the new package. 1358 // Otherwise the PackageInfo instance may go away because we had the 1359 // last reference. And some of the views, the PackageActionView for 1360 // example, keeps references to stuff from the previous package and 1361 // access it while switching to the new package. 1362 fPackage = packageRef; 1363 } 1364 1365 1366 /*! See if the screenshot is already cached; if it is then load it 1367 immediately. If it is not then trigger a process to start it in 1368 the background. A message will come through later once it is 1369 cached and ready to load. 1370 */ 1371 1372 void 1373 PackageInfoView::_SetPackageScreenshotThumb(const PackageInfoRef& package) 1374 { 1375 ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package); 1376 bool hasCachedBitmap = false; 1377 1378 if (desiredCoordinate.IsValid()) { 1379 bool present = false; 1380 if (fModel->GetPackageScreenshotRepository()->HasCachedScreenshot( 1381 desiredCoordinate, &present) != B_OK) { 1382 HDERROR("unable to ascertain if screenshot is present for pkg [%s]", package->Name().String()); 1383 } else { 1384 if (present) { 1385 HDDEBUG("screenshot is already cached for [%s] -- will load it", package->Name().String()); 1386 _HandleScreenshotCached(package, desiredCoordinate); 1387 hasCachedBitmap = true; 1388 } else { 1389 HDDEBUG("screenshot is not cached [%s] -- will cache it", package->Name().String()); 1390 ProcessCoordinator *processCoordinator = 1391 ProcessCoordinatorFactory::CacheScreenshotCoordinator( 1392 fModel, desiredCoordinate); 1393 fProcessCoordinatorConsumer->Consume(processCoordinator); 1394 } 1395 } 1396 } else 1397 HDDEBUG("no screenshot for pkg [%s]", package->Name().String()); 1398 1399 if (!hasCachedBitmap) 1400 fPagesView->SetScreenshotThumbnail(BitmapRef()); 1401 } 1402 1403 1404 /*static*/ const ScreenshotCoordinate 1405 PackageInfoView::_ScreenshotThumbCoordinate(const PackageInfoRef& package) 1406 { 1407 if (!package.IsSet()) 1408 return ScreenshotCoordinate(); 1409 if (package->CountScreenshotInfos() == 0) 1410 return ScreenshotCoordinate(); 1411 return ScreenshotCoordinate(package->ScreenshotInfoAtIndex(0)->Code(), kScreenshotSize, kScreenshotSize); 1412 } 1413 1414 1415 /*! This message will arrive when the data in the screenshot cache 1416 contains a new screenshot. This logic can check to see if the 1417 screenshot arriving is the one that is being waited for and 1418 would then load the screenshot from the cache and display it. 1419 */ 1420 1421 void 1422 PackageInfoView::HandleScreenshotCached(const ScreenshotCoordinate& coordinate) 1423 { 1424 _HandleScreenshotCached(fPackage, coordinate); 1425 } 1426 1427 1428 void 1429 PackageInfoView::_HandleScreenshotCached(const PackageInfoRef& package, 1430 const ScreenshotCoordinate& coordinate) 1431 { 1432 ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package); 1433 bool hasBitmap = false; 1434 1435 if (desiredCoordinate.IsValid() && desiredCoordinate == coordinate) { 1436 HDDEBUG("screenshot [%s] has been cached and matched; will load", 1437 coordinate.Code().String()); 1438 BitmapRef bitmapRef; 1439 if (fModel->GetPackageScreenshotRepository()->CacheAndLoadScreenshot( 1440 coordinate, &bitmapRef) != B_OK) { 1441 HDERROR("unable to load the screenshot [%s]", coordinate.Code().String()); 1442 } else { 1443 fPagesView->SetScreenshotThumbnail(bitmapRef); 1444 hasBitmap = true; 1445 } 1446 } 1447 1448 if (!hasBitmap) 1449 fPagesView->SetScreenshotThumbnail(BitmapRef()); 1450 } 1451 1452 1453 void 1454 PackageInfoView::Clear() 1455 { 1456 BAutolock _(fModel->Lock()); 1457 1458 fTitleView->Clear(); 1459 fPackageActionView->Clear(); 1460 fPagesView->Clear(); 1461 1462 fCardLayout->SetVisibleItem((int32)0); 1463 1464 fPackageListener->SetPackage(PackageInfoRef(NULL)); 1465 1466 fPackage.Unset(); 1467 } 1468