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