1 /* 2 * Copyright 2018-2022, 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 - SharedBitmapStringField 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 BitmapRef bitmapRef; 422 status_t bitmapResult; 423 424 bitmapResult = fModel->GetPackageIconRepository().GetIcon( 425 packageIconAndTitleField->PackageName(), BITMAP_SIZE_16, 426 bitmapRef); 427 428 if (bitmapResult == B_OK) { 429 if (bitmapRef.IsSet()) { 430 const BBitmap* bitmap = bitmapRef->Bitmap(BITMAP_SIZE_16); 431 if (bitmap != NULL && bitmap->IsValid()) { 432 parent->SetDrawingMode(B_OP_ALPHA); 433 BRect viewRect(x, y, x + 15, y + 15); 434 parent->DrawBitmap(bitmap, bitmap->Bounds(), viewRect); 435 parent->SetDrawingMode(B_OP_OVER); 436 } 437 } 438 } 439 440 // draw the string 441 DrawString(packageIconAndTitleField->ClippedString(), parent, r); 442 443 } else if (stringField != NULL) { 444 445 float width = rect.Width() - (2 * sTextMargin); 446 447 if (width != stringField->Width()) { 448 BString truncatedString(stringField->String()); 449 450 parent->TruncateString(&truncatedString, fTruncateMode, width + 2); 451 stringField->SetClippedString(truncatedString.String()); 452 stringField->SetWidth(width); 453 } 454 455 DrawString(stringField->ClippedString(), parent, rect); 456 457 } else if (ratingField != NULL) { 458 459 const float kDefaultTextMargin = 8; 460 461 float width = rect.Width() - (2 * kDefaultTextMargin); 462 463 BString string = "★★★★★"; 464 float stringWidth = parent->StringWidth(string); 465 bool drawOverlay = true; 466 467 if (width < stringWidth) { 468 string.SetToFormat("%.1f", ratingField->Rating()); 469 drawOverlay = false; 470 stringWidth = parent->StringWidth(string); 471 } 472 473 switch (Alignment()) { 474 default: 475 case B_ALIGN_LEFT: 476 rect.left += kDefaultTextMargin; 477 break; 478 case B_ALIGN_CENTER: 479 rect.left = rect.left + (width - stringWidth) / 2.0f; 480 break; 481 482 case B_ALIGN_RIGHT: 483 rect.left = rect.right - (stringWidth + kDefaultTextMargin); 484 break; 485 } 486 487 rect.left = floorf(rect.left); 488 rect.right = rect.left + stringWidth; 489 490 if (drawOverlay) 491 parent->SetHighColor(0, 170, 255); 492 493 font_height fontHeight; 494 parent->GetFontHeight(&fontHeight); 495 float y = rect.top + (rect.Height() 496 - (fontHeight.ascent + fontHeight.descent)) / 2 497 + fontHeight.ascent; 498 499 parent->DrawString(string, BPoint(rect.left, y)); 500 501 if (drawOverlay) { 502 rect.left = ceilf(rect.left 503 + (ratingField->Rating() / 5.0f) * rect.Width()); 504 505 rgb_color color = parent->LowColor(); 506 color.alpha = 190; 507 parent->SetHighColor(color); 508 509 parent->SetDrawingMode(B_OP_ALPHA); 510 parent->FillRect(rect, B_SOLID_HIGH); 511 512 } 513 } 514 } 515 516 517 int 518 PackageColumn::CompareFields(BField* field1, BField* field2) 519 { 520 DateField* dateField1 = dynamic_cast<DateField*>(field1); 521 DateField* dateField2 = dynamic_cast<DateField*>(field2); 522 if (dateField1 != NULL && dateField2 != NULL) { 523 if (dateField1->MillisSinceEpoc() > dateField2->MillisSinceEpoc()) 524 return -1; 525 else if (dateField1->MillisSinceEpoc() < dateField2->MillisSinceEpoc()) 526 return 1; 527 return 0; 528 } 529 530 SizeField* sizeField1 = dynamic_cast<SizeField*>(field1); 531 SizeField* sizeField2 = dynamic_cast<SizeField*>(field2); 532 if (sizeField1 != NULL && sizeField2 != NULL) { 533 if (sizeField1->Size() > sizeField2->Size()) 534 return -1; 535 else if (sizeField1->Size() < sizeField2->Size()) 536 return 1; 537 return 0; 538 } 539 540 BStringField* stringField1 = dynamic_cast<BStringField*>(field1); 541 BStringField* stringField2 = dynamic_cast<BStringField*>(field2); 542 if (stringField1 != NULL && stringField2 != NULL) { 543 // TODO: Locale aware string compare... not too important if 544 // package names are not translated. 545 return strcasecmp(stringField1->String(), stringField2->String()); 546 } 547 548 RatingField* ratingField1 = dynamic_cast<RatingField*>(field1); 549 RatingField* ratingField2 = dynamic_cast<RatingField*>(field2); 550 if (ratingField1 != NULL && ratingField2 != NULL) { 551 if (ratingField1->Rating() > ratingField2->Rating()) 552 return -1; 553 else if (ratingField1->Rating() < ratingField2->Rating()) 554 return 1; 555 return 0; 556 } 557 558 return Inherited::CompareFields(field1, field2); 559 } 560 561 562 float 563 PackageColumn::GetPreferredWidth(BField *_field, BView* parent) const 564 { 565 PackageIconAndTitleField* packageIconAndTitleField 566 = dynamic_cast<PackageIconAndTitleField*>(_field); 567 BStringField* stringField = dynamic_cast<BStringField*>(_field); 568 569 float parentWidth = Inherited::GetPreferredWidth(_field, parent); 570 float width = 0.0; 571 572 if (packageIconAndTitleField) { 573 BFont font; 574 parent->GetFont(&font); 575 width = font.StringWidth(packageIconAndTitleField->String()) 576 + 3 * sTextMargin; 577 width += 16; 578 // for the icon; always 16px 579 } else if (stringField) { 580 BFont font; 581 parent->GetFont(&font); 582 width = font.StringWidth(stringField->String()) + 2 * sTextMargin; 583 } 584 return max_c(width, parentWidth); 585 } 586 587 588 bool 589 PackageColumn::AcceptsField(const BField* field) const 590 { 591 return dynamic_cast<const BStringField*>(field) != NULL 592 || dynamic_cast<const RatingField*>(field) != NULL; 593 } 594 595 596 void 597 PackageColumn::InitTextMargin(BView* parent) 598 { 599 BFont font; 600 parent->GetFont(&font); 601 sTextMargin = ceilf(font.Size() * 0.8); 602 } 603 604 605 // #pragma mark - PackageRow 606 607 608 enum { 609 kTitleColumn, 610 kRatingColumn, 611 kDescriptionColumn, 612 kSizeColumn, 613 kStatusColumn, 614 kRepositoryColumn, 615 kVersionColumn, 616 kVersionCreateTimestampColumn, 617 }; 618 619 620 PackageRow::PackageRow(const PackageInfoRef& packageRef, 621 PackageListener* packageListener) 622 : 623 Inherited(ceilf(be_plain_font->Size() * 1.8f)), 624 fPackage(packageRef), 625 fPackageListener(packageListener), 626 fNextInHash(NULL) 627 { 628 if (!packageRef.IsSet()) 629 return; 630 631 PackageInfo& package = *packageRef.Get(); 632 633 // Package icon and title 634 // NOTE: The icon BBitmap is referenced by the fPackage member. 635 UpdateIconAndTitle(); 636 637 UpdateRating(); 638 UpdateSummary(); 639 UpdateSize(); 640 UpdateState(); 641 UpdateRepository(); 642 UpdateVersion(); 643 UpdateVersionCreateTimestamp(); 644 645 package.AddListener(fPackageListener); 646 } 647 648 649 PackageRow::~PackageRow() 650 { 651 if (fPackage.IsSet()) 652 fPackage->RemoveListener(fPackageListener); 653 } 654 655 656 void 657 PackageRow::UpdateIconAndTitle() 658 { 659 if (!fPackage.IsSet()) 660 return; 661 SetField(new PackageIconAndTitleField( 662 fPackage->Name(), fPackage->Title()), kTitleColumn); 663 } 664 665 666 void 667 PackageRow::UpdateState() 668 { 669 if (!fPackage.IsSet()) 670 return; 671 SetField(new BStringField(package_state_to_string(fPackage)), 672 kStatusColumn); 673 } 674 675 676 void 677 PackageRow::UpdateSummary() 678 { 679 if (!fPackage.IsSet()) 680 return; 681 SetField(new BStringField(fPackage->ShortDescription()), 682 kDescriptionColumn); 683 } 684 685 686 void 687 PackageRow::UpdateRating() 688 { 689 if (!fPackage.IsSet()) 690 return; 691 RatingSummary summary = fPackage->CalculateRatingSummary(); 692 SetField(new RatingField(summary.averageRating), kRatingColumn); 693 } 694 695 696 void 697 PackageRow::UpdateSize() 698 { 699 if (!fPackage.IsSet()) 700 return; 701 SetField(new SizeField(fPackage->Size()), kSizeColumn); 702 } 703 704 705 void 706 PackageRow::UpdateRepository() 707 { 708 if (!fPackage.IsSet()) 709 return; 710 SetField(new BStringField(fPackage->DepotName()), kRepositoryColumn); 711 } 712 713 714 void 715 PackageRow::UpdateVersion() 716 { 717 if (!fPackage.IsSet()) 718 return; 719 SetField(new BStringField(fPackage->Version().ToString()), kVersionColumn); 720 } 721 722 723 void 724 PackageRow::UpdateVersionCreateTimestamp() 725 { 726 if (!fPackage.IsSet()) 727 return; 728 SetField(new DateField(fPackage->VersionCreateTimestamp()), 729 kVersionCreateTimestampColumn); 730 } 731 732 733 // #pragma mark - ItemCountView 734 735 736 class PackageListView::ItemCountView : public BView { 737 public: 738 ItemCountView() 739 : 740 BView("item count view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 741 fItemCount(0) 742 { 743 BFont font(be_plain_font); 744 font.SetSize(font.Size() * 0.75f); 745 SetFont(&font); 746 747 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 748 SetLowUIColor(ViewUIColor()); 749 SetHighUIColor(LowUIColor(), B_DARKEN_4_TINT); 750 751 // constantly calculating the size is expensive so here a sensible 752 // upper limit on the number of packages is arbitrarily chosen. 753 fMinSize = BSize(StringWidth(_DeriveLabel(999999)) + 10, 754 be_control_look->GetScrollBarWidth()); 755 } 756 757 virtual BSize MinSize() 758 { 759 return fMinSize; 760 } 761 762 virtual BSize PreferredSize() 763 { 764 return MinSize(); 765 } 766 767 virtual BSize MaxSize() 768 { 769 return MinSize(); 770 } 771 772 virtual void Draw(BRect updateRect) 773 { 774 FillRect(updateRect, B_SOLID_LOW); 775 776 font_height fontHeight; 777 GetFontHeight(&fontHeight); 778 779 BRect bounds(Bounds()); 780 float width = StringWidth(fLabel); 781 782 BPoint offset; 783 offset.x = bounds.left + (bounds.Width() - width) / 2.0f; 784 offset.y = bounds.top + (bounds.Height() 785 - (fontHeight.ascent + fontHeight.descent)) / 2.0f 786 + fontHeight.ascent; 787 788 DrawString(fLabel, offset); 789 } 790 791 void SetItemCount(int32 count) 792 { 793 if (count == fItemCount) 794 return; 795 fItemCount = count; 796 fLabel = _DeriveLabel(fItemCount); 797 Invalidate(); 798 } 799 800 private: 801 802 /*! This method is hit quite often when the list of packages in the 803 table-view are updated. Derivation of the plural for some 804 languages such as Russian can be slow so this method should be 805 called sparingly. 806 */ 807 808 BString _DeriveLabel(int32 count) const 809 { 810 static BStringFormat format(B_TRANSLATE("{0, plural, " 811 "one{# item} other{# items}}")); 812 BString label; 813 format.Format(label, count); 814 return label; 815 } 816 817 int32 fItemCount; 818 BString fLabel; 819 BSize fMinSize; 820 }; 821 822 823 // #pragma mark - PackageListView::RowByNameHashDefinition 824 825 826 struct PackageListView::RowByNameHashDefinition { 827 typedef const char* KeyType; 828 typedef PackageRow ValueType; 829 830 size_t HashKey(const char* key) const 831 { 832 return BPackageKit::BHPKG::BPrivate::hash_string(key); 833 } 834 835 size_t Hash(PackageRow* value) const 836 { 837 return BPackageKit::BHPKG::BPrivate::hash_string( 838 value->Package()->Name().String()); 839 } 840 841 bool Compare(const char* key, PackageRow* value) const 842 { 843 return value->Package()->Name() == key; 844 } 845 846 ValueType*& GetLink(PackageRow* value) const 847 { 848 return value->NextInHash(); 849 } 850 }; 851 852 853 // #pragma mark - PackageListView 854 855 856 PackageListView::PackageListView(Model* model) 857 : 858 BColumnListView(B_TRANSLATE("All packages"), 0, B_FANCY_BORDER, true), 859 fModel(model), 860 fPackageListener(new(std::nothrow) PackageListener(this)), 861 fRowByNameTable(new RowByNameTable()), 862 fWorkStatusView(NULL), 863 fIgnoreSelectionChanged(false) 864 { 865 float scale = be_plain_font->Size() / 12.f; 866 float spacing = be_control_look->DefaultItemSpacing() * 2; 867 868 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Name"), 869 150 * scale, 50 * scale, 300 * scale, 870 B_TRUNCATE_MIDDLE), kTitleColumn); 871 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Rating"), 872 80 * scale, 50 * scale, 100 * scale, 873 B_TRUNCATE_MIDDLE), kRatingColumn); 874 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Description"), 875 300 * scale, 80 * scale, 1000 * scale, 876 B_TRUNCATE_MIDDLE), kDescriptionColumn); 877 PackageColumn* sizeColumn = new PackageColumn(fModel, B_TRANSLATE("Size"), 878 spacing + StringWidth("9999.99 KiB"), 50 * scale, 879 140 * scale, B_TRUNCATE_END); 880 sizeColumn->SetAlignment(B_ALIGN_RIGHT); 881 AddColumn(sizeColumn, kSizeColumn); 882 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Status"), 883 spacing + StringWidth(B_TRANSLATE("Available")), 60 * scale, 884 140 * scale, B_TRUNCATE_END), kStatusColumn); 885 886 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Repository"), 887 120 * scale, 50 * scale, 200 * scale, 888 B_TRUNCATE_MIDDLE), kRepositoryColumn); 889 SetColumnVisible(kRepositoryColumn, false); 890 // invisible by default 891 892 float widthWithPlacboVersion = spacing 893 + StringWidth("8.2.3176-2"); 894 // average sort of version length as model 895 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Version"), 896 widthWithPlacboVersion, widthWithPlacboVersion, 897 widthWithPlacboVersion + (50 * scale), 898 B_TRUNCATE_MIDDLE), kVersionColumn); 899 900 float widthWithPlaceboDate = spacing 901 + StringWidth(LocaleUtils::TimestampToDateString( 902 static_cast<uint64>(1000))); 903 AddColumn(new PackageColumn(fModel, B_TRANSLATE("Date"), 904 widthWithPlaceboDate, widthWithPlaceboDate, 905 widthWithPlaceboDate + (50 * scale), 906 B_TRUNCATE_END), kVersionCreateTimestampColumn); 907 908 fItemCountView = new ItemCountView(); 909 AddStatusView(fItemCountView); 910 } 911 912 913 PackageListView::~PackageListView() 914 { 915 Clear(); 916 delete fPackageListener; 917 } 918 919 920 void 921 PackageListView::AttachedToWindow() 922 { 923 BColumnListView::AttachedToWindow(); 924 925 PackageColumn::InitTextMargin(ScrollView()); 926 } 927 928 929 void 930 PackageListView::AllAttached() 931 { 932 BColumnListView::AllAttached(); 933 934 SetSortingEnabled(true); 935 SetSortColumn(ColumnAt(0), false, true); 936 } 937 938 939 void 940 PackageListView::MessageReceived(BMessage* message) 941 { 942 switch (message->what) { 943 case MSG_UPDATE_PACKAGE: 944 { 945 BString name; 946 uint32 changes; 947 if (message->FindString("name", &name) != B_OK 948 || message->FindUInt32("changes", &changes) != B_OK) { 949 break; 950 } 951 952 BAutolock _(fModel->Lock()); 953 PackageRow* row = _FindRow(name); 954 if (row != NULL) { 955 if ((changes & PKG_CHANGED_TITLE) != 0) 956 row->UpdateIconAndTitle(); 957 if ((changes & PKG_CHANGED_SUMMARY) != 0) 958 row->UpdateSummary(); 959 if ((changes & PKG_CHANGED_RATINGS) != 0) 960 row->UpdateRating(); 961 if ((changes & PKG_CHANGED_STATE) != 0) 962 row->UpdateState(); 963 if ((changes & PKG_CHANGED_SIZE) != 0) 964 row->UpdateSize(); 965 if ((changes & PKG_CHANGED_ICON) != 0) 966 row->UpdateIconAndTitle(); 967 if ((changes & PKG_CHANGED_DEPOT) != 0) 968 row->UpdateRepository(); 969 if ((changes & PKG_CHANGED_VERSION) != 0) 970 row->UpdateVersion(); 971 if ((changes & PKG_CHANGED_VERSION_CREATE_TIMESTAMP) != 0) 972 row->UpdateVersionCreateTimestamp(); 973 } 974 break; 975 } 976 977 default: 978 BColumnListView::MessageReceived(message); 979 break; 980 } 981 } 982 983 984 void 985 PackageListView::SelectionChanged() 986 { 987 BColumnListView::SelectionChanged(); 988 989 if (fIgnoreSelectionChanged) 990 return; 991 992 BMessage message(MSG_PACKAGE_SELECTED); 993 994 PackageRow* selected = dynamic_cast<PackageRow*>(CurrentSelection()); 995 if (selected != NULL) 996 message.AddString("name", selected->Package()->Name()); 997 998 Window()->PostMessage(&message); 999 } 1000 1001 1002 void 1003 PackageListView::Clear() 1004 { 1005 fItemCountView->SetItemCount(0); 1006 BColumnListView::Clear(); 1007 fRowByNameTable->Clear(); 1008 } 1009 1010 1011 void 1012 PackageListView::AddPackage(const PackageInfoRef& package) 1013 { 1014 PackageRow* packageRow = _FindRow(package); 1015 1016 // forget about it if this package is already in the listview 1017 if (packageRow != NULL) 1018 return; 1019 1020 BAutolock _(fModel->Lock()); 1021 1022 // create the row for this package 1023 packageRow = new PackageRow(package, fPackageListener); 1024 1025 // add the row, parent may be NULL (add at top level) 1026 AddRow(packageRow); 1027 1028 // add to hash table for quick lookup of row by package name 1029 fRowByNameTable->Insert(packageRow); 1030 1031 // make sure the row is initially expanded 1032 ExpandOrCollapse(packageRow, true); 1033 1034 fItemCountView->SetItemCount(CountRows()); 1035 } 1036 1037 1038 void 1039 PackageListView::RemovePackage(const PackageInfoRef& package) 1040 { 1041 PackageRow* packageRow = _FindRow(package); 1042 if (packageRow == NULL) 1043 return; 1044 1045 fRowByNameTable->Remove(packageRow); 1046 1047 RemoveRow(packageRow); 1048 delete packageRow; 1049 1050 fItemCountView->SetItemCount(CountRows()); 1051 } 1052 1053 1054 void 1055 PackageListView::SelectPackage(const PackageInfoRef& package) 1056 { 1057 fIgnoreSelectionChanged = true; 1058 1059 PackageRow* row = _FindRow(package); 1060 BRow* selected = CurrentSelection(); 1061 if (row != selected) 1062 DeselectAll(); 1063 if (row != NULL) { 1064 AddToSelection(row); 1065 SetFocusRow(row, false); 1066 ScrollTo(row); 1067 } 1068 1069 fIgnoreSelectionChanged = false; 1070 } 1071 1072 1073 void 1074 PackageListView::AttachWorkStatusView(WorkStatusView* view) 1075 { 1076 fWorkStatusView = view; 1077 } 1078 1079 1080 PackageRow* 1081 PackageListView::_FindRow(const PackageInfoRef& package) 1082 { 1083 if (!package.IsSet()) 1084 return NULL; 1085 return fRowByNameTable->Lookup(package->Name().String()); 1086 } 1087 1088 1089 PackageRow* 1090 PackageListView::_FindRow(const BString& packageName) 1091 { 1092 return fRowByNameTable->Lookup(packageName.String()); 1093 } 1094