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