1 /* 2 * Copyright 2018-2020, Andrew Lindesay, <apl@lindesay.co.nz>. 3 * Copyright 2017, Julian Harnath, <julian.harnath@rwth-aachen.de>. 4 * Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>. 5 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. 6 * Copyright 2013, Rene Gollent, <rene@gollent.com>. 7 * All rights reserved. Distributed under the terms of the MIT License. 8 */ 9 10 #include "PackageListView.h" 11 12 #include <algorithm> 13 #include <stdio.h> 14 15 #include <Autolock.h> 16 #include <Catalog.h> 17 #include <ControlLook.h> 18 #include <ScrollBar.h> 19 #include <StringFormat.h> 20 #include <StringForSize.h> 21 #include <package/hpkg/Strings.h> 22 #include <Window.h> 23 24 #include "MainWindow.h" 25 #include "WorkStatusView.h" 26 27 28 #undef B_TRANSLATION_CONTEXT 29 #define B_TRANSLATION_CONTEXT "PackageListView" 30 31 32 static const char* skPackageStateAvailable = B_TRANSLATE_MARK("Available"); 33 static const char* skPackageStateUninstalled = B_TRANSLATE_MARK("Uninstalled"); 34 static const char* skPackageStateActive = B_TRANSLATE_MARK("Active"); 35 static const char* skPackageStateInactive = B_TRANSLATE_MARK("Inactive"); 36 static const char* skPackageStatePending = B_TRANSLATE_MARK( 37 "Pending" B_UTF8_ELLIPSIS); 38 39 40 inline BString 41 package_state_to_string(PackageInfoRef ref) 42 { 43 switch (ref->State()) { 44 case NONE: 45 return B_TRANSLATE(skPackageStateAvailable); 46 case INSTALLED: 47 return B_TRANSLATE(skPackageStateInactive); 48 case ACTIVATED: 49 return B_TRANSLATE(skPackageStateActive); 50 case UNINSTALLED: 51 return B_TRANSLATE(skPackageStateUninstalled); 52 case DOWNLOADING: 53 { 54 BString data; 55 data.SetToFormat("%3.2f%%", ref->DownloadProgress() * 100.0); 56 return data; 57 } 58 case PENDING: 59 return B_TRANSLATE(skPackageStatePending); 60 } 61 62 return B_TRANSLATE("Unknown"); 63 } 64 65 66 class PackageIconAndTitleField : public BStringField { 67 typedef BStringField Inherited; 68 public: 69 PackageIconAndTitleField( 70 const char* packageName, 71 const char* string); 72 virtual ~PackageIconAndTitleField(); 73 74 const BString PackageName() const 75 { return fPackageName; } 76 private: 77 const BString fPackageName; 78 }; 79 80 81 class RatingField : public BField { 82 public: 83 RatingField(float rating); 84 virtual ~RatingField(); 85 86 void SetRating(float rating); 87 float Rating() const 88 { return fRating; } 89 private: 90 float fRating; 91 }; 92 93 94 class SizeField : public BStringField { 95 public: 96 SizeField(double size); 97 virtual ~SizeField(); 98 99 void SetSize(double size); 100 double Size() const 101 { return fSize; } 102 private: 103 double fSize; 104 }; 105 106 107 // BColumn for PackageListView which knows how to render 108 // a PackageIconAndTitleField 109 class PackageColumn : public BTitledColumn { 110 typedef BTitledColumn Inherited; 111 public: 112 PackageColumn(Model* model, 113 const char* title, 114 float width, float minWidth, 115 float maxWidth, uint32 truncateMode, 116 alignment align = B_ALIGN_LEFT); 117 118 virtual void DrawField(BField* field, BRect rect, 119 BView* parent); 120 virtual int CompareFields(BField* field1, BField* field2); 121 virtual float GetPreferredWidth(BField* field, 122 BView* parent) const; 123 124 virtual bool AcceptsField(const BField* field) const; 125 126 static void InitTextMargin(BView* parent); 127 128 private: 129 Model* fModel; 130 uint32 fTruncateMode; 131 static float sTextMargin; 132 }; 133 134 135 // BRow for the PartitionListView 136 class PackageRow : public BRow { 137 typedef BRow Inherited; 138 public: 139 PackageRow( 140 const PackageInfoRef& package, 141 PackageListener* listener); 142 virtual ~PackageRow(); 143 144 const PackageInfoRef& Package() const 145 { return fPackage; } 146 147 void UpdateIconAndTitle(); 148 void UpdateSummary(); 149 void UpdateState(); 150 void UpdateRating(); 151 void UpdateSize(); 152 void UpdateRepository(); 153 void UpdateVersion(); 154 155 PackageRow*& NextInHash() 156 { return fNextInHash; } 157 158 private: 159 PackageInfoRef fPackage; 160 PackageInfoListenerRef 161 fPackageListener; 162 163 PackageRow* fNextInHash; 164 // link for BOpenHashTable 165 }; 166 167 168 enum { 169 MSG_UPDATE_PACKAGE = 'updp' 170 }; 171 172 173 class PackageListener : public PackageInfoListener { 174 public: 175 PackageListener(PackageListView* view) 176 : 177 fView(view) 178 { 179 } 180 181 virtual ~PackageListener() 182 { 183 } 184 185 virtual void PackageChanged(const PackageInfoEvent& event) 186 { 187 BMessenger messenger(fView); 188 if (!messenger.IsValid()) 189 return; 190 191 const PackageInfo& package = *event.Package().Get(); 192 193 BMessage message(MSG_UPDATE_PACKAGE); 194 message.AddString("name", package.Name()); 195 message.AddUInt32("changes", event.Changes()); 196 197 messenger.SendMessage(&message); 198 } 199 200 private: 201 PackageListView* fView; 202 }; 203 204 205 // #pragma mark - SharedBitmapStringField 206 207 208 PackageIconAndTitleField::PackageIconAndTitleField(const char* packageName, 209 const char* string) 210 : 211 Inherited(string), 212 fPackageName(packageName) 213 { 214 } 215 216 217 PackageIconAndTitleField::~PackageIconAndTitleField() 218 { 219 } 220 221 222 // #pragma mark - RatingField 223 224 225 RatingField::RatingField(float rating) 226 : 227 fRating(0.0f) 228 { 229 SetRating(rating); 230 } 231 232 233 RatingField::~RatingField() 234 { 235 } 236 237 238 void 239 RatingField::SetRating(float rating) 240 { 241 if (rating < 0.0f) 242 rating = 0.0f; 243 if (rating > 5.0f) 244 rating = 5.0f; 245 246 if (rating == fRating) 247 return; 248 249 fRating = rating; 250 } 251 252 253 // #pragma mark - SizeField 254 255 256 SizeField::SizeField(double size) 257 : 258 BStringField(""), 259 fSize(-1.0) 260 { 261 SetSize(size); 262 } 263 264 265 SizeField::~SizeField() 266 { 267 } 268 269 270 void 271 SizeField::SetSize(double size) 272 { 273 if (size < 0.0) 274 size = 0.0; 275 276 if (size == fSize) 277 return; 278 279 BString sizeString; 280 if (size == 0) { 281 sizeString = B_TRANSLATE_CONTEXT("-", "no package size"); 282 } else { 283 char buffer[256]; 284 sizeString = string_for_size(size, buffer, sizeof(buffer)); 285 } 286 287 fSize = size; 288 SetString(sizeString.String()); 289 } 290 291 292 // #pragma mark - PackageColumn 293 294 295 // TODO: Code-duplication with DriveSetup PartitionList.cpp 296 297 298 float PackageColumn::sTextMargin = 0.0; 299 300 301 PackageColumn::PackageColumn(Model* model, const char* title, float width, 302 float minWidth, float maxWidth, uint32 truncateMode, alignment align) 303 : 304 Inherited(title, width, minWidth, maxWidth, align), 305 fModel(model), 306 fTruncateMode(truncateMode) 307 { 308 SetWantsEvents(true); 309 } 310 311 312 void 313 PackageColumn::DrawField(BField* field, BRect rect, BView* parent) 314 { 315 PackageIconAndTitleField* packageIconAndTitleField 316 = dynamic_cast<PackageIconAndTitleField*>(field); 317 BStringField* stringField = dynamic_cast<BStringField*>(field); 318 RatingField* ratingField = dynamic_cast<RatingField*>(field); 319 320 if (packageIconAndTitleField != NULL) { 321 // Scale the bitmap to 16x16 322 BRect r = BRect(0, 0, 15, 15); 323 324 // figure out the placement 325 float x = 0.0; 326 float y = rect.top + ((rect.Height() - r.Height()) / 2) - 1; 327 float width = 0.0; 328 329 switch (Alignment()) { 330 default: 331 case B_ALIGN_LEFT: 332 case B_ALIGN_CENTER: 333 x = rect.left + sTextMargin; 334 width = rect.right - (x + r.Width()) - (2 * sTextMargin); 335 r.Set(x + r.Width(), rect.top, rect.right - width, rect.bottom); 336 break; 337 338 case B_ALIGN_RIGHT: 339 x = rect.right - sTextMargin - r.Width(); 340 width = (x - rect.left - (2 * sTextMargin)); 341 r.Set(rect.left, rect.top, rect.left + width, rect.bottom); 342 break; 343 } 344 345 if (width != packageIconAndTitleField->Width()) { 346 BString truncatedString(packageIconAndTitleField->String()); 347 parent->TruncateString(&truncatedString, fTruncateMode, width + 2); 348 packageIconAndTitleField->SetClippedString(truncatedString.String()); 349 packageIconAndTitleField->SetWidth(width); 350 } 351 352 // draw the bitmap 353 BitmapRef bitmapRef; 354 status_t bitmapResult; 355 356 bitmapResult = fModel->GetPackageIconRepository().GetIcon( 357 packageIconAndTitleField->PackageName(), BITMAP_SIZE_16, 358 bitmapRef); 359 360 if (bitmapResult == B_OK) { 361 if (bitmapRef.IsSet()) { 362 const BBitmap* bitmap = bitmapRef->Bitmap(BITMAP_SIZE_16); 363 parent->SetDrawingMode(B_OP_ALPHA); 364 BRect viewRect(x, y, x + 15, y + 15); 365 parent->DrawBitmap(bitmap, bitmap->Bounds(), viewRect); 366 parent->SetDrawingMode(B_OP_OVER); 367 } 368 } 369 370 // draw the string 371 DrawString(packageIconAndTitleField->ClippedString(), parent, r); 372 373 } else if (stringField != NULL) { 374 375 float width = rect.Width() - (2 * sTextMargin); 376 377 if (width != stringField->Width()) { 378 BString truncatedString(stringField->String()); 379 380 parent->TruncateString(&truncatedString, fTruncateMode, width + 2); 381 stringField->SetClippedString(truncatedString.String()); 382 stringField->SetWidth(width); 383 } 384 385 DrawString(stringField->ClippedString(), parent, rect); 386 387 } else if (ratingField != NULL) { 388 389 const float kDefaultTextMargin = 8; 390 391 float width = rect.Width() - (2 * kDefaultTextMargin); 392 393 BString string = "★★★★★"; 394 float stringWidth = parent->StringWidth(string); 395 bool drawOverlay = true; 396 397 if (width < stringWidth) { 398 string.SetToFormat("%.1f", ratingField->Rating()); 399 drawOverlay = false; 400 stringWidth = parent->StringWidth(string); 401 } 402 403 switch (Alignment()) { 404 default: 405 case B_ALIGN_LEFT: 406 rect.left += kDefaultTextMargin; 407 break; 408 case B_ALIGN_CENTER: 409 rect.left = rect.left + (width - stringWidth) / 2.0f; 410 break; 411 412 case B_ALIGN_RIGHT: 413 rect.left = rect.right - (stringWidth + kDefaultTextMargin); 414 break; 415 } 416 417 rect.left = floorf(rect.left); 418 rect.right = rect.left + stringWidth; 419 420 if (drawOverlay) 421 parent->SetHighColor(0, 170, 255); 422 423 font_height fontHeight; 424 parent->GetFontHeight(&fontHeight); 425 float y = rect.top + (rect.Height() 426 - (fontHeight.ascent + fontHeight.descent)) / 2 427 + fontHeight.ascent; 428 429 parent->DrawString(string, BPoint(rect.left, y)); 430 431 if (drawOverlay) { 432 rect.left = ceilf(rect.left 433 + (ratingField->Rating() / 5.0f) * rect.Width()); 434 435 rgb_color color = parent->LowColor(); 436 color.alpha = 190; 437 parent->SetHighColor(color); 438 439 parent->SetDrawingMode(B_OP_ALPHA); 440 parent->FillRect(rect, B_SOLID_HIGH); 441 442 } 443 } 444 } 445 446 447 int 448 PackageColumn::CompareFields(BField* field1, BField* field2) 449 { 450 SizeField* sizeField1 = dynamic_cast<SizeField*>(field1); 451 SizeField* sizeField2 = dynamic_cast<SizeField*>(field2); 452 if (sizeField1 != NULL && sizeField2 != NULL) { 453 if (sizeField1->Size() > sizeField2->Size()) 454 return -1; 455 else if (sizeField1->Size() < sizeField2->Size()) 456 return 1; 457 return 0; 458 } 459 460 BStringField* stringField1 = dynamic_cast<BStringField*>(field1); 461 BStringField* stringField2 = dynamic_cast<BStringField*>(field2); 462 if (stringField1 != NULL && stringField2 != NULL) { 463 // TODO: Locale aware string compare... not too important if 464 // package names are not translated. 465 return strcasecmp(stringField1->String(), stringField2->String()); 466 } 467 468 RatingField* ratingField1 = dynamic_cast<RatingField*>(field1); 469 RatingField* ratingField2 = dynamic_cast<RatingField*>(field2); 470 if (ratingField1 != NULL && ratingField2 != NULL) { 471 if (ratingField1->Rating() > ratingField2->Rating()) 472 return -1; 473 else if (ratingField1->Rating() < ratingField2->Rating()) 474 return 1; 475 return 0; 476 } 477 478 return Inherited::CompareFields(field1, field2); 479 } 480 481 482 float 483 PackageColumn::GetPreferredWidth(BField *_field, BView* parent) const 484 { 485 PackageIconAndTitleField* packageIconAndTitleField 486 = dynamic_cast<PackageIconAndTitleField*>(_field); 487 BStringField* stringField = dynamic_cast<BStringField*>(_field); 488 489 float parentWidth = Inherited::GetPreferredWidth(_field, parent); 490 float width = 0.0; 491 492 if (packageIconAndTitleField) { 493 BFont font; 494 parent->GetFont(&font); 495 width = font.StringWidth(packageIconAndTitleField->String()) 496 + 3 * sTextMargin; 497 width += 16; 498 // for the icon; always 16px 499 } else if (stringField) { 500 BFont font; 501 parent->GetFont(&font); 502 width = font.StringWidth(stringField->String()) + 2 * sTextMargin; 503 } 504 return max_c(width, parentWidth); 505 } 506 507 508 bool 509 PackageColumn::AcceptsField(const BField* field) const 510 { 511 return dynamic_cast<const BStringField*>(field) != NULL 512 || dynamic_cast<const RatingField*>(field) != NULL; 513 } 514 515 516 void 517 PackageColumn::InitTextMargin(BView* parent) 518 { 519 BFont font; 520 parent->GetFont(&font); 521 sTextMargin = ceilf(font.Size() * 0.8); 522 } 523 524 525 // #pragma mark - PackageRow 526 527 528 enum { 529 kTitleColumn, 530 kRatingColumn, 531 kDescriptionColumn, 532 kSizeColumn, 533 kStatusColumn, 534 kRepositoryColumn, 535 kVersionColumn, 536 }; 537 538 539 PackageRow::PackageRow(const PackageInfoRef& packageRef, 540 PackageListener* packageListener) 541 : 542 Inherited(ceilf(be_plain_font->Size() * 1.8f)), 543 fPackage(packageRef), 544 fPackageListener(packageListener), 545 fNextInHash(NULL) 546 { 547 if (!packageRef.IsSet()) 548 return; 549 550 PackageInfo& package = *packageRef.Get(); 551 552 // Package icon and title 553 // NOTE: The icon BBitmap is referenced by the fPackage member. 554 UpdateIconAndTitle(); 555 556 // Rating 557 UpdateRating(); 558 559 // Summary 560 UpdateSummary(); 561 562 // Size 563 UpdateSize(); 564 565 // Status 566 UpdateState(); 567 568 // Repository 569 UpdateRepository(); 570 571 // Version 572 UpdateVersion(); 573 574 package.AddListener(fPackageListener); 575 } 576 577 578 PackageRow::~PackageRow() 579 { 580 if (fPackage.IsSet()) 581 fPackage->RemoveListener(fPackageListener); 582 } 583 584 585 void 586 PackageRow::UpdateIconAndTitle() 587 { 588 if (!fPackage.IsSet()) 589 return; 590 591 SetField(new PackageIconAndTitleField( 592 fPackage->Name(), fPackage->Title()), kTitleColumn); 593 } 594 595 596 void 597 PackageRow::UpdateState() 598 { 599 if (!fPackage.IsSet()) 600 return; 601 602 SetField(new BStringField(package_state_to_string(fPackage)), 603 kStatusColumn); 604 } 605 606 607 void 608 PackageRow::UpdateSummary() 609 { 610 if (!fPackage.IsSet()) 611 return; 612 613 SetField(new BStringField(fPackage->ShortDescription()), 614 kDescriptionColumn); 615 } 616 617 618 void 619 PackageRow::UpdateRating() 620 { 621 if (!fPackage.IsSet()) 622 return; 623 RatingSummary summary = fPackage->CalculateRatingSummary(); 624 SetField(new RatingField(summary.averageRating), kRatingColumn); 625 } 626 627 628 void 629 PackageRow::UpdateSize() 630 { 631 if (!fPackage.IsSet()) 632 return; 633 634 SetField(new SizeField(fPackage->Size()), kSizeColumn); 635 } 636 637 638 void 639 PackageRow::UpdateRepository() 640 { 641 if (!fPackage.IsSet()) 642 return; 643 644 SetField(new BStringField(fPackage->DepotName()), kRepositoryColumn); 645 } 646 647 648 void 649 PackageRow::UpdateVersion() 650 { 651 if (!fPackage.IsSet()) 652 return; 653 654 SetField(new BStringField(fPackage->Version().ToString()), kVersionColumn); 655 } 656 657 658 // #pragma mark - ItemCountView 659 660 661 class PackageListView::ItemCountView : public BView { 662 public: 663 ItemCountView() 664 : 665 BView("item count view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 666 fItemCount(0) 667 { 668 BFont font(be_plain_font); 669 font.SetSize(9.0f); 670 SetFont(&font); 671 672 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 673 SetLowUIColor(ViewUIColor()); 674 SetHighUIColor(LowUIColor(), B_DARKEN_4_TINT); 675 676 // constantly calculating the size is expensive so here a sensible 677 // upper limit on the number of packages is arbitrarily chosen. 678 fMinSize = BSize(StringWidth(_DeriveLabel(999999)) + 10, 679 B_H_SCROLL_BAR_HEIGHT); 680 } 681 682 virtual BSize MinSize() 683 { 684 return fMinSize; 685 } 686 687 virtual BSize PreferredSize() 688 { 689 return MinSize(); 690 } 691 692 virtual BSize MaxSize() 693 { 694 return MinSize(); 695 } 696 697 virtual void Draw(BRect updateRect) 698 { 699 FillRect(updateRect, B_SOLID_LOW); 700 701 font_height fontHeight; 702 GetFontHeight(&fontHeight); 703 704 BRect bounds(Bounds()); 705 float width = StringWidth(fLabel); 706 707 BPoint offset; 708 offset.x = bounds.left + (bounds.Width() - width) / 2.0f; 709 offset.y = bounds.top + (bounds.Height() 710 - (fontHeight.ascent + fontHeight.descent)) / 2.0f 711 + fontHeight.ascent; 712 713 DrawString(fLabel, offset); 714 } 715 716 void SetItemCount(int32 count) 717 { 718 if (count == fItemCount) 719 return; 720 fItemCount = count; 721 fLabel = _DeriveLabel(fItemCount); 722 Invalidate(); 723 } 724 725 private: 726 727 /*! This method is hit quite often when the list of packages in the 728 table-view are updated. Derivation of the plural for some 729 languages such as Russian can be slow so this method should be 730 called sparingly. 731 */ 732 733 BString _DeriveLabel(int32 count) const 734 { 735 static BStringFormat format(B_TRANSLATE("{0, plural, " 736 "one{# item} other{# items}}")); 737 BString label; 738 format.Format(label, count); 739 return label; 740 } 741 742 int32 fItemCount; 743 BString fLabel; 744 BSize fMinSize; 745 }; 746 747 748 // #pragma mark - PackageListView::RowByNameHashDefinition 749 750 751 struct PackageListView::RowByNameHashDefinition { 752 typedef const char* KeyType; 753 typedef PackageRow ValueType; 754 755 size_t HashKey(const char* key) const 756 { 757 return BPackageKit::BHPKG::BPrivate::hash_string(key); 758 } 759 760 size_t Hash(PackageRow* value) const 761 { 762 return BPackageKit::BHPKG::BPrivate::hash_string( 763 value->Package()->Name().String()); 764 } 765 766 bool Compare(const char* key, PackageRow* value) const 767 { 768 return value->Package()->Name() == key; 769 } 770 771 ValueType*& GetLink(PackageRow* value) const 772 { 773 return value->NextInHash(); 774 } 775 }; 776 777 778 // #pragma mark - PackageListView 779 780 781 PackageListView::PackageListView(Model* model) 782 : 783 BColumnListView(B_TRANSLATE("All packages"), 0, B_FANCY_BORDER, true), 784 fModel(model), 785 fPackageListener(new(std::nothrow) PackageListener(this)), 786 fRowByNameTable(new RowByNameTable()), 787 fWorkStatusView(NULL) 788 { 789 float scale = be_plain_font->Size() / 12.f; 790 float spacing = be_control_look->DefaultItemSpacing() * 2; 791 792 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Name"), 793 150 * scale, 50 * scale, 300 * scale, 794 B_TRUNCATE_MIDDLE), kTitleColumn); 795 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Rating"), 796 80 * scale, 50 * scale, 100 * scale, 797 B_TRUNCATE_MIDDLE), kRatingColumn); 798 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Description"), 799 300 * scale, 80 * scale, 1000 * scale, 800 B_TRUNCATE_MIDDLE), kDescriptionColumn); 801 PackageColumn* sizeColumn = new PackageColumn(fModel, B_TRANSLATE("Size"), 802 spacing + StringWidth(B_TRANSLATE("9999.99 KiB")), 50 * scale, 803 140 * scale, B_TRUNCATE_END); 804 sizeColumn->SetAlignment(B_ALIGN_RIGHT); 805 AddColumn(sizeColumn, kSizeColumn); 806 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Status"), 807 spacing + StringWidth(B_TRANSLATE("Available")), 60 * scale, 808 140 * scale, B_TRUNCATE_END), kStatusColumn); 809 810 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Repository"), 811 120 * scale, 50 * scale, 200 * scale, 812 B_TRUNCATE_MIDDLE), kRepositoryColumn); 813 SetColumnVisible(kRepositoryColumn, false); 814 // invisible by default 815 816 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Version"), 817 50 * scale, 50 * scale, 200 * scale, 818 B_TRUNCATE_MIDDLE), kVersionColumn); 819 820 fItemCountView = new ItemCountView(); 821 AddStatusView(fItemCountView); 822 } 823 824 825 PackageListView::~PackageListView() 826 { 827 Clear(); 828 delete fPackageListener; 829 } 830 831 832 void 833 PackageListView::AttachedToWindow() 834 { 835 BColumnListView::AttachedToWindow(); 836 837 PackageColumn::InitTextMargin(ScrollView()); 838 } 839 840 841 void 842 PackageListView::AllAttached() 843 { 844 BColumnListView::AllAttached(); 845 846 SetSortingEnabled(true); 847 SetSortColumn(ColumnAt(0), false, true); 848 } 849 850 851 void 852 PackageListView::MessageReceived(BMessage* message) 853 { 854 switch (message->what) { 855 case MSG_UPDATE_PACKAGE: 856 { 857 BString name; 858 uint32 changes; 859 if (message->FindString("name", &name) != B_OK 860 || message->FindUInt32("changes", &changes) != B_OK) { 861 break; 862 } 863 864 BAutolock _(fModel->Lock()); 865 PackageRow* row = _FindRow(name); 866 if (row != NULL) { 867 if ((changes & PKG_CHANGED_TITLE) != 0) 868 row->UpdateIconAndTitle(); 869 if ((changes & PKG_CHANGED_SUMMARY) != 0) 870 row->UpdateSummary(); 871 if ((changes & PKG_CHANGED_RATINGS) != 0) 872 row->UpdateRating(); 873 if ((changes & PKG_CHANGED_STATE) != 0) 874 row->UpdateState(); 875 if ((changes & PKG_CHANGED_SIZE) != 0) 876 row->UpdateSize(); 877 if ((changes & PKG_CHANGED_ICON) != 0) 878 row->UpdateIconAndTitle(); 879 if ((changes & PKG_CHANGED_DEPOT) != 0) 880 row->UpdateRepository(); 881 if ((changes & PKG_CHANGED_VERSION) != 0) 882 row->UpdateVersion(); 883 } 884 break; 885 } 886 887 default: 888 BColumnListView::MessageReceived(message); 889 break; 890 } 891 } 892 893 894 void 895 PackageListView::SelectionChanged() 896 { 897 BColumnListView::SelectionChanged(); 898 899 BMessage message(MSG_PACKAGE_SELECTED); 900 901 PackageRow* selected = dynamic_cast<PackageRow*>(CurrentSelection()); 902 if (selected != NULL) 903 message.AddString("name", selected->Package()->Name()); 904 905 Window()->PostMessage(&message); 906 } 907 908 909 void 910 PackageListView::Clear() 911 { 912 fItemCountView->SetItemCount(0); 913 BColumnListView::Clear(); 914 fRowByNameTable->Clear(); 915 } 916 917 918 void 919 PackageListView::AddPackage(const PackageInfoRef& package) 920 { 921 PackageRow* packageRow = _FindRow(package); 922 923 // forget about it if this package is already in the listview 924 if (packageRow != NULL) 925 return; 926 927 BAutolock _(fModel->Lock()); 928 929 // create the row for this package 930 packageRow = new PackageRow(package, fPackageListener); 931 932 // add the row, parent may be NULL (add at top level) 933 AddRow(packageRow); 934 935 // add to hash table for quick lookup of row by package name 936 fRowByNameTable->Insert(packageRow); 937 938 // make sure the row is initially expanded 939 ExpandOrCollapse(packageRow, true); 940 941 fItemCountView->SetItemCount(CountRows()); 942 } 943 944 945 void 946 PackageListView::RemovePackage(const PackageInfoRef& package) 947 { 948 PackageRow* packageRow = _FindRow(package); 949 if (packageRow == NULL) 950 return; 951 952 fRowByNameTable->Remove(packageRow); 953 954 RemoveRow(packageRow); 955 delete packageRow; 956 957 fItemCountView->SetItemCount(CountRows()); 958 } 959 960 961 void 962 PackageListView::SelectPackage(const PackageInfoRef& package) 963 { 964 PackageRow* row = _FindRow(package); 965 BRow* selected = CurrentSelection(); 966 if (row != selected) 967 DeselectAll(); 968 if (row != NULL) { 969 AddToSelection(row); 970 SetFocusRow(row, false); 971 ScrollTo(row); 972 } 973 } 974 975 976 void 977 PackageListView::AttachWorkStatusView(WorkStatusView* view) 978 { 979 fWorkStatusView = view; 980 } 981 982 983 PackageRow* 984 PackageListView::_FindRow(const PackageInfoRef& package) 985 { 986 if (!package.IsSet()) 987 return NULL; 988 return fRowByNameTable->Lookup(package->Name().String()); 989 } 990 991 992 PackageRow* 993 PackageListView::_FindRow(const BString& packageName) 994 { 995 return fRowByNameTable->Lookup(packageName.String()); 996 } 997