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