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