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