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