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_EMAIL_PUBLISHER = 'emlp', 636 MSG_VISIT_PUBLISHER_WEBSITE = 'vpws', 637 }; 638 639 640 class AboutView : public BView { 641 public: 642 AboutView() 643 : 644 BView("about view", 0), 645 fEmailIcon("text/x-email"), 646 fWebsiteIcon("text/html") 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 // TODO: Clicking the screen shot view should open ShowImage with the 662 // the screen shot. This could be done by writing the screen shot to 663 // a temporary folder, launching ShowImage to display it, and writing 664 // all other screenshots associated with the package to the same folder 665 // so the user can use the ShowImage navigation to view the other 666 // screenshots. 667 fScreenshotView = new LinkedBitmapView("screenshot view", 668 new BMessage(MSG_SHOW_SCREENSHOT)); 669 fScreenshotView->SetExplicitMinSize(BSize(64.0f, 64.0f)); 670 fScreenshotView->SetExplicitMaxSize( 671 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 672 fScreenshotView->SetExplicitAlignment( 673 BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP)); 674 675 fEmailIconView = new BitmapView("email icon view"); 676 fEmailLinkView = new LinkView("email link view", "", 677 new BMessage(MSG_EMAIL_PUBLISHER)); 678 fEmailLinkView->SetFont(&smallFont); 679 680 fWebsiteIconView = new BitmapView("website icon view"); 681 fWebsiteLinkView = new LinkView("website link view", "", 682 new BMessage(MSG_VISIT_PUBLISHER_WEBSITE)); 683 fWebsiteLinkView->SetFont(&smallFont); 684 685 BGroupView* leftGroup = new BGroupView(B_VERTICAL, 686 B_USE_DEFAULT_SPACING); 687 688 fScreenshotView->SetViewUIColor(ViewUIColor(), kContentTint); 689 fEmailLinkView->SetViewUIColor(ViewUIColor(), kContentTint); 690 fWebsiteLinkView->SetViewUIColor(ViewUIColor(), kContentTint); 691 692 BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f) 693 .AddGroup(leftGroup, 1.0f) 694 .Add(fScreenshotView) 695 .AddGroup(B_HORIZONTAL) 696 .AddGrid(B_USE_HALF_ITEM_SPACING, B_USE_HALF_ITEM_SPACING) 697 .Add(fEmailIconView, 0, 0) 698 .Add(fEmailLinkView, 1, 0) 699 .Add(fWebsiteIconView, 0, 1) 700 .Add(fWebsiteLinkView, 1, 1) 701 .End() 702 .End() 703 .SetInsets(B_USE_DEFAULT_SPACING) 704 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)) 705 .End() 706 .Add(scrollView, 2.0f) 707 708 .SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED)) 709 .SetInsets(0.0f, -1.0f, -1.0f, -1.0f) 710 ; 711 } 712 713 virtual ~AboutView() 714 { 715 Clear(); 716 } 717 718 virtual void AttachedToWindow() 719 { 720 fScreenshotView->SetTarget(this); 721 fEmailLinkView->SetTarget(this); 722 fWebsiteLinkView->SetTarget(this); 723 } 724 725 virtual void AllAttached() 726 { 727 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 728 729 for (int32 index = 0; index < CountChildren(); ++index) 730 ChildAt(index)->AdoptParentColors(); 731 } 732 733 virtual void MessageReceived(BMessage* message) 734 { 735 switch (message->what) { 736 case MSG_SHOW_SCREENSHOT: 737 { 738 // Forward to window for now 739 Window()->PostMessage(message, Window()); 740 break; 741 } 742 743 case MSG_EMAIL_PUBLISHER: 744 { 745 // TODO: Implement. If memory serves, there is a 746 // standard command line interface which mail apps should 747 // support, i.e. to open a compose window with the TO: field 748 // already set. 749 break; 750 } 751 752 case MSG_VISIT_PUBLISHER_WEBSITE: 753 { 754 BUrl url(fWebsiteLinkView->Text()); 755 url.OpenWithPreferredApplication(); 756 break; 757 } 758 759 default: 760 BView::MessageReceived(message); 761 break; 762 } 763 } 764 765 void SetScreenshotThumbnail(const BitmapRef& bitmapRef) 766 { 767 if (bitmapRef.IsSet()) { 768 fScreenshotView->SetBitmap(bitmapRef); 769 fScreenshotView->SetEnabled(true); 770 } else { 771 fScreenshotView->UnsetBitmap(); 772 fScreenshotView->SetEnabled(false); 773 } 774 } 775 776 void SetPackage(const PackageInfoRef package) 777 { 778 fDescriptionView->SetText(package->ShortDescription(), package->FullDescription()); 779 fEmailIconView->SetBitmap(&fEmailIcon, BITMAP_SIZE_16); 780 _SetContactInfo(fEmailLinkView, package->Publisher().Email()); 781 fWebsiteIconView->SetBitmap(&fWebsiteIcon, BITMAP_SIZE_16); 782 _SetContactInfo(fWebsiteLinkView, package->Publisher().Website()); 783 } 784 785 void Clear() 786 { 787 fDescriptionView->SetText(""); 788 fEmailIconView->UnsetBitmap(); 789 fEmailLinkView->SetText(""); 790 fWebsiteIconView->UnsetBitmap(); 791 fWebsiteLinkView->SetText(""); 792 fScreenshotView->UnsetBitmap(); 793 fScreenshotView->SetEnabled(false); 794 } 795 796 private: 797 void _SetContactInfo(LinkView* view, const BString& string) 798 { 799 if (string.Length() > 0) { 800 view->SetText(string); 801 view->SetEnabled(true); 802 } else { 803 view->SetText(B_TRANSLATE("<no info>")); 804 view->SetEnabled(false); 805 } 806 } 807 808 private: 809 MarkupTextView* fDescriptionView; 810 811 LinkedBitmapView* fScreenshotView; 812 813 SharedBitmap fEmailIcon; 814 BitmapView* fEmailIconView; 815 LinkView* fEmailLinkView; 816 817 SharedBitmap fWebsiteIcon; 818 BitmapView* fWebsiteIconView; 819 LinkView* fWebsiteLinkView; 820 }; 821 822 823 // #pragma mark - UserRatingsView 824 825 826 class RatingItemView : public BGroupView { 827 public: 828 RatingItemView(const UserRatingRef rating) 829 : 830 BGroupView(B_HORIZONTAL, 0.0f) 831 { 832 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 833 834 BGroupLayout* verticalGroup = new BGroupLayout(B_VERTICAL, 0.0f); 835 GroupLayout()->AddItem(verticalGroup); 836 837 { 838 BStringView* userNicknameView = new BStringView("user-nickname", 839 rating->User().NickName()); 840 userNicknameView->SetFont(be_bold_font); 841 verticalGroup->AddView(userNicknameView); 842 } 843 844 BGroupLayout* ratingGroup = 845 new BGroupLayout(B_HORIZONTAL, B_USE_DEFAULT_SPACING); 846 verticalGroup->AddItem(ratingGroup); 847 848 if (rating->Rating() >= 0) { 849 RatingView* ratingView = new RatingView("package rating view"); 850 ratingView->SetRating(rating->Rating()); 851 ratingGroup->AddView(ratingView); 852 } 853 854 { 855 BString createTimestampPresentation = 856 LocaleUtils::TimestampToDateTimeString( 857 rating->CreateTimestamp()); 858 859 BString ratingContextDescription( 860 B_TRANSLATE("%hd.timestamp% (version %hd.version%)")); 861 ratingContextDescription.ReplaceAll("%hd.timestamp%", 862 createTimestampPresentation); 863 ratingContextDescription.ReplaceAll("%hd.version%", 864 rating->PackageVersion()); 865 866 BStringView* ratingContextView = new BStringView("rating-context", 867 ratingContextDescription); 868 BFont versionFont(be_plain_font); 869 ratingContextView->SetFont(&versionFont); 870 ratingGroup->AddView(ratingContextView); 871 } 872 873 ratingGroup->AddItem(BSpaceLayoutItem::CreateGlue()); 874 875 if (rating->Comment() > 0) { 876 TextView* textView = new TextView("rating-text"); 877 ParagraphStyle paragraphStyle(textView->ParagraphStyle()); 878 paragraphStyle.SetJustify(true); 879 textView->SetParagraphStyle(paragraphStyle); 880 textView->SetText(rating->Comment()); 881 verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f)); 882 verticalGroup->AddView(textView); 883 verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f)); 884 } 885 886 verticalGroup->SetInsets(B_USE_DEFAULT_SPACING); 887 888 SetFlags(Flags() | B_WILL_DRAW); 889 } 890 891 void AllAttached() 892 { 893 for (int32 index = 0; index < CountChildren(); ++index) 894 ChildAt(index)->AdoptParentColors(); 895 } 896 897 void Draw(BRect rect) 898 { 899 rgb_color color = mix_color(ViewColor(), ui_color(B_PANEL_TEXT_COLOR), 64); 900 SetHighColor(color); 901 StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom()); 902 } 903 904 }; 905 906 907 class RatingSummaryView : public BGridView { 908 public: 909 RatingSummaryView() 910 : 911 BGridView("rating summary view", B_USE_HALF_ITEM_SPACING, 0.0f) 912 { 913 float tint = kContentTint - 0.1; 914 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, tint); 915 916 BLayoutBuilder::Grid<> layoutBuilder(this); 917 918 BFont smallFont; 919 GetFont(&smallFont); 920 smallFont.SetSize(std::max(9.0f, floorf(smallFont.Size() * 0.85f))); 921 922 for (int32 i = 0; i < 5; i++) { 923 BString label; 924 label.SetToFormat("%" B_PRId32, 5 - i); 925 fLabelViews[i] = new BStringView("", label); 926 fLabelViews[i]->SetFont(&smallFont); 927 fLabelViews[i]->SetViewUIColor(ViewUIColor(), tint); 928 layoutBuilder.Add(fLabelViews[i], 0, i); 929 930 fDiagramBarViews[i] = new DiagramBarView(); 931 layoutBuilder.Add(fDiagramBarViews[i], 1, i); 932 933 fCountViews[i] = new BStringView("", ""); 934 fCountViews[i]->SetFont(&smallFont); 935 fCountViews[i]->SetViewUIColor(ViewUIColor(), tint); 936 fCountViews[i]->SetAlignment(B_ALIGN_RIGHT); 937 layoutBuilder.Add(fCountViews[i], 2, i); 938 } 939 940 layoutBuilder.SetInsets(5); 941 } 942 943 void SetToSummary(const RatingSummary& summary) { 944 for (int32 i = 0; i < 5; i++) { 945 int32 count = summary.ratingCountByStar[4 - i]; 946 947 BString label; 948 label.SetToFormat("%" B_PRId32, count); 949 fCountViews[i]->SetText(label); 950 951 if (summary.ratingCount > 0) { 952 fDiagramBarViews[i]->SetValue( 953 (float)count / summary.ratingCount); 954 } else 955 fDiagramBarViews[i]->SetValue(0.0f); 956 } 957 } 958 959 void Clear() { 960 for (int32 i = 0; i < 5; i++) { 961 fCountViews[i]->SetText(""); 962 fDiagramBarViews[i]->SetValue(0.0f); 963 } 964 } 965 966 private: 967 BStringView* fLabelViews[5]; 968 DiagramBarView* fDiagramBarViews[5]; 969 BStringView* fCountViews[5]; 970 }; 971 972 973 class UserRatingsView : public BGroupView { 974 public: 975 UserRatingsView() 976 : 977 BGroupView("package ratings view", B_HORIZONTAL) 978 { 979 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 980 981 fRatingSummaryView = new RatingSummaryView(); 982 983 ScrollableGroupView* ratingsContainerView = new ScrollableGroupView(); 984 ratingsContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR, 985 kContentTint); 986 fRatingContainerLayout = ratingsContainerView->GroupLayout(); 987 988 BScrollView* scrollView = new RatingsScrollView( 989 "ratings scroll view", ratingsContainerView); 990 scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 991 B_SIZE_UNLIMITED)); 992 993 BLayoutBuilder::Group<>(this) 994 .AddGroup(B_VERTICAL) 995 .Add(fRatingSummaryView, 0.0f) 996 .AddGlue() 997 .SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f) 998 .End() 999 .AddStrut(64.0) 1000 .Add(scrollView, 1.0f) 1001 .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f) 1002 ; 1003 } 1004 1005 virtual ~UserRatingsView() 1006 { 1007 Clear(); 1008 } 1009 1010 void SetPackage(const PackageInfoRef package) 1011 { 1012 ClearRatings(); 1013 1014 // TODO: Re-use rating summary already used for TitleView... 1015 fRatingSummaryView->SetToSummary(package->CalculateRatingSummary()); 1016 1017 int count = package->CountUserRatings(); 1018 if (count == 0) { 1019 BStringView* noRatingsView = new BStringView("no ratings", 1020 B_TRANSLATE("No user ratings available.")); 1021 noRatingsView->SetViewUIColor(ViewUIColor(), kContentTint); 1022 noRatingsView->SetAlignment(B_ALIGN_CENTER); 1023 noRatingsView->SetHighColor(disable_color(ui_color(B_PANEL_TEXT_COLOR), 1024 ViewColor())); 1025 noRatingsView->SetExplicitMaxSize( 1026 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 1027 fRatingContainerLayout->AddView(0, noRatingsView); 1028 return; 1029 } 1030 1031 for (int i = count - 1; i >= 0; i--) { 1032 UserRatingRef rating = package->UserRatingAtIndex(i); 1033 // was previously filtering comments just for the current 1034 // user's language, but as there are not so many comments at 1035 // the moment, just show all of them for now. 1036 RatingItemView* view = new RatingItemView(rating); 1037 fRatingContainerLayout->AddView(0, view); 1038 } 1039 } 1040 1041 void Clear() 1042 { 1043 fRatingSummaryView->Clear(); 1044 ClearRatings(); 1045 } 1046 1047 void ClearRatings() 1048 { 1049 for (int32 i = fRatingContainerLayout->CountItems() - 1; 1050 BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) { 1051 BView* view = dynamic_cast<RatingItemView*>(item->View()); 1052 if (view == NULL) 1053 view = dynamic_cast<BStringView*>(item->View()); 1054 if (view != NULL) { 1055 view->RemoveSelf(); 1056 delete view; 1057 } 1058 } 1059 } 1060 1061 private: 1062 BGroupLayout* fRatingContainerLayout; 1063 RatingSummaryView* fRatingSummaryView; 1064 }; 1065 1066 1067 // #pragma mark - ContentsView 1068 1069 1070 class ContentsView : public BGroupView { 1071 public: 1072 ContentsView() 1073 : 1074 BGroupView("package contents view", B_HORIZONTAL) 1075 { 1076 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 1077 1078 fPackageContents = new PackageContentsView("contents_list"); 1079 AddChild(fPackageContents); 1080 1081 } 1082 1083 virtual ~ContentsView() 1084 { 1085 } 1086 1087 virtual void Draw(BRect updateRect) 1088 { 1089 } 1090 1091 void SetPackage(const PackageInfoRef package) 1092 { 1093 fPackageContents->SetPackage(package); 1094 } 1095 1096 void Clear() 1097 { 1098 fPackageContents->Clear(); 1099 } 1100 1101 private: 1102 PackageContentsView* fPackageContents; 1103 }; 1104 1105 1106 // #pragma mark - ChangelogView 1107 1108 1109 class ChangelogView : public BGroupView { 1110 public: 1111 ChangelogView() 1112 : 1113 BGroupView("package changelog view", B_HORIZONTAL) 1114 { 1115 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); 1116 1117 fTextView = new MarkupTextView("changelog view"); 1118 fTextView->SetLowUIColor(ViewUIColor()); 1119 fTextView->SetInsets(be_plain_font->Size()); 1120 1121 BScrollView* scrollView = new GeneralContentScrollView( 1122 "changelog scroll view", fTextView); 1123 1124 BLayoutBuilder::Group<>(this) 1125 .Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f)) 1126 .Add(scrollView, 1.0f) 1127 .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f) 1128 ; 1129 } 1130 1131 virtual ~ChangelogView() 1132 { 1133 } 1134 1135 virtual void Draw(BRect updateRect) 1136 { 1137 } 1138 1139 void SetPackage(const PackageInfoRef package) 1140 { 1141 const BString& changelog = package->Changelog(); 1142 if (changelog.Length() > 0) 1143 fTextView->SetText(changelog); 1144 else 1145 fTextView->SetDisabledText(B_TRANSLATE("No changelog available.")); 1146 } 1147 1148 void Clear() 1149 { 1150 fTextView->SetText(""); 1151 } 1152 1153 private: 1154 MarkupTextView* fTextView; 1155 }; 1156 1157 1158 // #pragma mark - PagesView 1159 1160 1161 class PagesView : public BTabView { 1162 public: 1163 PagesView() 1164 : 1165 BTabView("pages view", B_WIDTH_FROM_WIDEST) 1166 { 1167 SetBorder(B_NO_BORDER); 1168 1169 fAboutView = new AboutView(); 1170 fUserRatingsView = new UserRatingsView(); 1171 fChangelogView = new ChangelogView(); 1172 fContentsView = new ContentsView(); 1173 1174 AddTab(fAboutView); 1175 AddTab(fUserRatingsView); 1176 AddTab(fChangelogView); 1177 AddTab(fContentsView); 1178 1179 TabAt(TAB_ABOUT)->SetLabel(B_TRANSLATE("About")); 1180 TabAt(TAB_RATINGS)->SetLabel(B_TRANSLATE("Ratings")); 1181 TabAt(TAB_CHANGELOG)->SetLabel(B_TRANSLATE("Changelog")); 1182 TabAt(TAB_CONTENTS)->SetLabel(B_TRANSLATE("Contents")); 1183 1184 Select(TAB_ABOUT); 1185 } 1186 1187 virtual ~PagesView() 1188 { 1189 Clear(); 1190 } 1191 1192 void SetScreenshotThumbnail(const BitmapRef& bitmap) 1193 { 1194 fAboutView->SetScreenshotThumbnail(bitmap); 1195 } 1196 1197 void SetPackage(const PackageInfoRef package, bool switchToDefaultTab) 1198 { 1199 if (switchToDefaultTab) 1200 Select(TAB_ABOUT); 1201 1202 TabAt(TAB_CHANGELOG)->SetEnabled( 1203 package.IsSet() && package->HasChangelog()); 1204 TabAt(TAB_CONTENTS)->SetEnabled( 1205 package.IsSet() 1206 && (package->State() == ACTIVATED || package->IsLocalFile())); 1207 Invalidate(TabFrame(TAB_CHANGELOG)); 1208 Invalidate(TabFrame(TAB_CONTENTS)); 1209 1210 fAboutView->SetPackage(package); 1211 fUserRatingsView->SetPackage(package); 1212 fChangelogView->SetPackage(package); 1213 fContentsView->SetPackage(package); 1214 } 1215 1216 void Clear() 1217 { 1218 fAboutView->Clear(); 1219 fUserRatingsView->Clear(); 1220 fChangelogView->Clear(); 1221 fContentsView->Clear(); 1222 } 1223 1224 private: 1225 AboutView* fAboutView; 1226 UserRatingsView* fUserRatingsView; 1227 ChangelogView* fChangelogView; 1228 ContentsView* fContentsView; 1229 }; 1230 1231 1232 // #pragma mark - PackageInfoView 1233 1234 1235 PackageInfoView::PackageInfoView(Model* model, 1236 ProcessCoordinatorConsumer* processCoordinatorConsumer) 1237 : 1238 BView("package info view", 0), 1239 fModel(model), 1240 fPackageListener(new(std::nothrow) OnePackageMessagePackageListener(this)), 1241 fProcessCoordinatorConsumer(processCoordinatorConsumer) 1242 { 1243 fCardLayout = new BCardLayout(); 1244 SetLayout(fCardLayout); 1245 1246 BGroupView* noPackageCard = new BGroupView("no package card", B_VERTICAL); 1247 AddChild(noPackageCard); 1248 1249 BStringView* noPackageView = new BStringView("no package view", 1250 B_TRANSLATE("Click a package to view information")); 1251 noPackageView->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT); 1252 noPackageView->SetExplicitAlignment(BAlignment( 1253 B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER)); 1254 1255 BLayoutBuilder::Group<>(noPackageCard) 1256 .Add(noPackageView) 1257 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)) 1258 ; 1259 1260 BGroupView* packageCard = new BGroupView("package card", B_VERTICAL); 1261 AddChild(packageCard); 1262 1263 fCardLayout->SetVisibleItem((int32)0); 1264 1265 fTitleView = new TitleView(fModel->GetPackageIconRepository()); 1266 fPackageActionView = new PackageActionView(processCoordinatorConsumer, 1267 model); 1268 fPackageActionView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 1269 B_SIZE_UNSET)); 1270 fPagesView = new PagesView(); 1271 1272 BLayoutBuilder::Group<>(packageCard) 1273 .AddGroup(B_HORIZONTAL, 0.0f) 1274 .Add(fTitleView, 6.0f) 1275 .Add(fPackageActionView, 1.0f) 1276 .SetInsets( 1277 B_USE_DEFAULT_SPACING, 0.0f, 1278 B_USE_DEFAULT_SPACING, 0.0f) 1279 .End() 1280 .Add(fPagesView) 1281 ; 1282 1283 Clear(); 1284 } 1285 1286 1287 PackageInfoView::~PackageInfoView() 1288 { 1289 fPackageListener->SetPackage(PackageInfoRef(NULL)); 1290 delete fPackageListener; 1291 } 1292 1293 1294 void 1295 PackageInfoView::AttachedToWindow() 1296 { 1297 } 1298 1299 1300 void 1301 PackageInfoView::MessageReceived(BMessage* message) 1302 { 1303 switch (message->what) { 1304 case MSG_UPDATE_PACKAGE: 1305 { 1306 if (!fPackageListener->Package().IsSet()) 1307 break; 1308 1309 BString name; 1310 uint32 changes; 1311 if (message->FindString("name", &name) != B_OK 1312 || message->FindUInt32("changes", &changes) != B_OK) { 1313 break; 1314 } 1315 1316 const PackageInfoRef& package = fPackageListener->Package(); 1317 if (package->Name() != name) 1318 break; 1319 1320 BAutolock _(fModel->Lock()); 1321 1322 if ((changes & PKG_CHANGED_SUMMARY) != 0 1323 || (changes & PKG_CHANGED_DESCRIPTION) != 0 1324 || (changes & PKG_CHANGED_SCREENSHOTS) != 0 1325 || (changes & PKG_CHANGED_TITLE) != 0 1326 || (changes & PKG_CHANGED_RATINGS) != 0 1327 || (changes & PKG_CHANGED_STATE) != 0 1328 || (changes & PKG_CHANGED_CHANGELOG) != 0) { 1329 fPagesView->SetPackage(package, false); 1330 } 1331 1332 if ((changes & PKG_CHANGED_TITLE) != 0 1333 || (changes & PKG_CHANGED_RATINGS) != 0) { 1334 fTitleView->SetPackage(package); 1335 } 1336 1337 if ((changes & PKG_CHANGED_STATE) != 0) 1338 fPackageActionView->SetPackage(package); 1339 1340 break; 1341 } 1342 default: 1343 BView::MessageReceived(message); 1344 break; 1345 } 1346 } 1347 1348 1349 void 1350 PackageInfoView::SetPackage(const PackageInfoRef& packageRef) 1351 { 1352 BAutolock _(fModel->Lock()); 1353 1354 if (!packageRef.IsSet()) { 1355 Clear(); 1356 return; 1357 } 1358 1359 bool switchToDefaultTab = true; 1360 if (fPackage == packageRef) { 1361 // When asked to display the already showing package ref, 1362 // don't switch to the default tab. 1363 switchToDefaultTab = false; 1364 } else if (fPackage.IsSet() && packageRef.IsSet() 1365 && fPackage->Name() == packageRef->Name()) { 1366 // When asked to display a different PackageInfo instance, 1367 // but it has the same package title as the already showing 1368 // instance, this probably means there was a repository 1369 // refresh and we are in fact still requested to show the 1370 // same package as before the refresh. 1371 switchToDefaultTab = false; 1372 } 1373 1374 fTitleView->SetPackage(packageRef); 1375 fPackageActionView->SetPackage(packageRef); 1376 fPagesView->SetPackage(packageRef, switchToDefaultTab); 1377 1378 _SetPackageScreenshotThumb(packageRef); 1379 1380 fCardLayout->SetVisibleItem(1); 1381 1382 fPackageListener->SetPackage(packageRef); 1383 1384 // Set the fPackage reference last, so we keep a reference to the 1385 // previous package before switching all the views to the new package. 1386 // Otherwise the PackageInfo instance may go away because we had the 1387 // last reference. And some of the views, the PackageActionView for 1388 // example, keeps references to stuff from the previous package and 1389 // access it while switching to the new package. 1390 fPackage = packageRef; 1391 } 1392 1393 1394 /*! See if the screenshot is already cached; if it is then load it 1395 immediately. If it is not then trigger a process to start it in 1396 the background. A message will come through later once it is 1397 cached and ready to load. 1398 */ 1399 1400 void 1401 PackageInfoView::_SetPackageScreenshotThumb(const PackageInfoRef& package) 1402 { 1403 ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package); 1404 bool hasCachedBitmap = false; 1405 1406 if (desiredCoordinate.IsValid()) { 1407 bool present = false; 1408 if (fModel->GetPackageScreenshotRepository()->HasCachedScreenshot( 1409 desiredCoordinate, &present) != B_OK) { 1410 HDERROR("unable to ascertain if screenshot is present for pkg [%s]", package->Name().String()); 1411 } else { 1412 if (present) { 1413 HDDEBUG("screenshot is already cached for [%s] -- will load it", package->Name().String()); 1414 _HandleScreenshotCached(package, desiredCoordinate); 1415 hasCachedBitmap = true; 1416 } else { 1417 HDDEBUG("screenshot is not cached [%s] -- will cache it", package->Name().String()); 1418 ProcessCoordinator *processCoordinator = 1419 ProcessCoordinatorFactory::CacheScreenshotCoordinator( 1420 fModel, desiredCoordinate); 1421 fProcessCoordinatorConsumer->Consume(processCoordinator); 1422 } 1423 } 1424 } else 1425 HDDEBUG("no screenshot for pkg [%s]", package->Name().String()); 1426 1427 if (!hasCachedBitmap) 1428 fPagesView->SetScreenshotThumbnail(BitmapRef()); 1429 } 1430 1431 1432 /*static*/ const ScreenshotCoordinate 1433 PackageInfoView::_ScreenshotThumbCoordinate(const PackageInfoRef& package) 1434 { 1435 if (!package.IsSet()) 1436 return ScreenshotCoordinate(); 1437 if (package->CountScreenshotInfos() == 0) 1438 return ScreenshotCoordinate(); 1439 return ScreenshotCoordinate(package->ScreenshotInfoAtIndex(0)->Code(), kScreenshotSize, kScreenshotSize); 1440 } 1441 1442 1443 /*! This message will arrive when the data in the screenshot cache 1444 contains a new screenshot. This logic can check to see if the 1445 screenshot arriving is the one that is being waited for and 1446 would then load the screenshot from the cache and display it. 1447 */ 1448 1449 void 1450 PackageInfoView::HandleScreenshotCached(const ScreenshotCoordinate& coordinate) 1451 { 1452 _HandleScreenshotCached(fPackage, coordinate); 1453 } 1454 1455 1456 void 1457 PackageInfoView::_HandleScreenshotCached(const PackageInfoRef& package, 1458 const ScreenshotCoordinate& coordinate) 1459 { 1460 ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package); 1461 bool hasBitmap = false; 1462 1463 if (desiredCoordinate.IsValid() && desiredCoordinate == coordinate) { 1464 HDDEBUG("screenshot [%s] has been cached and matched; will load", 1465 coordinate.Code().String()); 1466 BitmapRef bitmapRef; 1467 if (fModel->GetPackageScreenshotRepository()->CacheAndLoadScreenshot( 1468 coordinate, &bitmapRef) != B_OK) { 1469 HDERROR("unable to load the screenshot [%s]", coordinate.Code().String()); 1470 } else { 1471 fPagesView->SetScreenshotThumbnail(bitmapRef); 1472 hasBitmap = true; 1473 } 1474 } 1475 1476 if (!hasBitmap) 1477 fPagesView->SetScreenshotThumbnail(BitmapRef()); 1478 } 1479 1480 1481 void 1482 PackageInfoView::Clear() 1483 { 1484 BAutolock _(fModel->Lock()); 1485 1486 fTitleView->Clear(); 1487 fPackageActionView->Clear(); 1488 fPagesView->Clear(); 1489 1490 fCardLayout->SetVisibleItem((int32)0); 1491 1492 fPackageListener->SetPackage(PackageInfoRef(NULL)); 1493 1494 fPackage.Unset(); 1495 } 1496