1 /* 2 * Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>. 3 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>. 4 * Copyright 2013, Rene Gollent, <rene@gollent.com>. 5 * All rights reserved. Distributed under the terms of the MIT License. 6 */ 7 8 #include "PackageListView.h" 9 10 #include <algorithm> 11 #include <stdio.h> 12 13 #include <Autolock.h> 14 #include <Catalog.h> 15 #include <ControlLook.h> 16 #include <MessageFormat.h> 17 #include <ScrollBar.h> 18 #include <StringForSize.h> 19 #include <Window.h> 20 21 #include "MainWindow.h" 22 23 24 #undef B_TRANSLATION_CONTEXT 25 #define B_TRANSLATION_CONTEXT "PackageListView" 26 27 28 static const char* skPackageStateAvailable = B_TRANSLATE_MARK("Available"); 29 static const char* skPackageStateUninstalled = B_TRANSLATE_MARK("Uninstalled"); 30 static const char* skPackageStateActive = B_TRANSLATE_MARK("Active"); 31 static const char* skPackageStateInactive = B_TRANSLATE_MARK("Inactive"); 32 static const char* skPackageStatePending = B_TRANSLATE_MARK( 33 "Pending" B_UTF8_ELLIPSIS); 34 35 36 inline BString 37 package_state_to_string(PackageInfoRef ref) 38 { 39 switch (ref->State()) { 40 case NONE: 41 return B_TRANSLATE(skPackageStateAvailable); 42 case INSTALLED: 43 return B_TRANSLATE(skPackageStateInactive); 44 case ACTIVATED: 45 return B_TRANSLATE(skPackageStateActive); 46 case UNINSTALLED: 47 return B_TRANSLATE(skPackageStateUninstalled); 48 case DOWNLOADING: 49 { 50 BString data; 51 data.SetToFormat("%3.2f%%", ref->DownloadProgress() * 100.0); 52 return data; 53 } 54 case PENDING: 55 return B_TRANSLATE(skPackageStatePending); 56 } 57 58 return B_TRANSLATE("Unknown"); 59 } 60 61 62 // A field type displaying both a bitmap and a string so that the 63 // tree display looks nicer (both text and bitmap are indented) 64 class SharedBitmapStringField : public BStringField { 65 typedef BStringField Inherited; 66 public: 67 SharedBitmapStringField(SharedBitmap* bitmap, 68 SharedBitmap::Size size, 69 const char* string); 70 virtual ~SharedBitmapStringField(); 71 72 void SetBitmap(SharedBitmap* bitmap, 73 SharedBitmap::Size size); 74 const BBitmap* Bitmap() const 75 { return fBitmap; } 76 77 private: 78 BitmapRef fReference; 79 const BBitmap* fBitmap; 80 }; 81 82 83 class RatingField : public BField { 84 public: 85 RatingField(float rating); 86 virtual ~RatingField(); 87 88 void SetRating(float rating); 89 float Rating() const 90 { return fRating; } 91 private: 92 float fRating; 93 }; 94 95 96 class SizeField : public BStringField { 97 public: 98 SizeField(double size); 99 virtual ~SizeField(); 100 101 void SetSize(double size); 102 double Size() const 103 { return fSize; } 104 private: 105 double fSize; 106 }; 107 108 109 // BColumn for PackageListView which knows how to render 110 // a SharedBitmapStringField 111 // TODO: Code-duplication with DriveSetup PartitionList.h 112 class PackageColumn : public BTitledColumn { 113 typedef BTitledColumn Inherited; 114 public: 115 PackageColumn(const char* title, 116 float width, float minWidth, 117 float maxWidth, uint32 truncateMode, 118 alignment align = B_ALIGN_LEFT); 119 120 virtual void DrawField(BField* field, BRect rect, 121 BView* parent); 122 virtual int CompareFields(BField* field1, BField* field2); 123 virtual float GetPreferredWidth(BField* field, 124 BView* parent) const; 125 126 virtual bool AcceptsField(const BField* field) const; 127 128 static void InitTextMargin(BView* parent); 129 130 private: 131 uint32 fTruncateMode; 132 static float sTextMargin; 133 }; 134 135 136 // BRow for the PartitionListView 137 class PackageRow : public BRow { 138 typedef BRow Inherited; 139 public: 140 PackageRow(const PackageInfoRef& package, 141 PackageListener* listener); 142 virtual ~PackageRow(); 143 144 const PackageInfoRef& Package() const 145 { return fPackage; } 146 147 void UpdateTitle(); 148 void UpdateSummary(); 149 void UpdateState(); 150 void UpdateRating(); 151 void UpdateSize(); 152 153 private: 154 PackageInfoRef fPackage; 155 PackageInfoListenerRef fPackageListener; 156 }; 157 158 159 enum { 160 MSG_UPDATE_PACKAGE = 'updp' 161 }; 162 163 164 class PackageListener : public PackageInfoListener { 165 public: 166 PackageListener(PackageListView* view) 167 : 168 fView(view) 169 { 170 } 171 172 virtual ~PackageListener() 173 { 174 } 175 176 virtual void PackageChanged(const PackageInfoEvent& event) 177 { 178 BMessenger messenger(fView); 179 if (!messenger.IsValid()) 180 return; 181 182 const PackageInfo& package = *event.Package().Get(); 183 184 BMessage message(MSG_UPDATE_PACKAGE); 185 message.AddString("name", package.Name()); 186 message.AddUInt32("changes", event.Changes()); 187 188 messenger.SendMessage(&message); 189 } 190 191 private: 192 PackageListView* fView; 193 }; 194 195 196 // #pragma mark - SharedBitmapStringField 197 198 199 SharedBitmapStringField::SharedBitmapStringField(SharedBitmap* bitmap, 200 SharedBitmap::Size size, const char* string) 201 : 202 Inherited(string), 203 fBitmap(NULL) 204 { 205 SetBitmap(bitmap, size); 206 } 207 208 209 SharedBitmapStringField::~SharedBitmapStringField() 210 { 211 } 212 213 214 void 215 SharedBitmapStringField::SetBitmap(SharedBitmap* bitmap, 216 SharedBitmap::Size size) 217 { 218 fReference = bitmap; 219 fBitmap = bitmap != NULL ? bitmap->Bitmap(size) : NULL; 220 // TODO: cause a redraw? 221 } 222 223 224 // #pragma mark - RatingField 225 226 227 RatingField::RatingField(float rating) 228 : 229 fRating(0.0f) 230 { 231 SetRating(rating); 232 } 233 234 235 RatingField::~RatingField() 236 { 237 } 238 239 240 void 241 RatingField::SetRating(float rating) 242 { 243 if (rating < 0.0f) 244 rating = 0.0f; 245 if (rating > 5.0f) 246 rating = 5.0f; 247 248 if (rating == fRating) 249 return; 250 251 fRating = rating; 252 } 253 254 255 // #pragma mark - SizeField 256 257 258 SizeField::SizeField(double size) 259 : 260 BStringField(""), 261 fSize(-1.0) 262 { 263 SetSize(size); 264 } 265 266 267 SizeField::~SizeField() 268 { 269 } 270 271 272 void 273 SizeField::SetSize(double size) 274 { 275 if (size < 0.0) 276 size = 0.0; 277 278 if (size == fSize) 279 return; 280 281 BString sizeString; 282 if (size == 0) { 283 sizeString = B_TRANSLATE_CONTEXT("-", "no package size"); 284 } else { 285 char buffer[256]; 286 sizeString = string_for_size(size, buffer, sizeof(buffer)); 287 } 288 289 fSize = size; 290 SetString(sizeString.String()); 291 } 292 293 294 // #pragma mark - PackageColumn 295 296 297 // TODO: Code-duplication with DriveSetup PartitionList.cpp 298 299 300 float PackageColumn::sTextMargin = 0.0; 301 302 303 PackageColumn::PackageColumn(const char* title, float width, float minWidth, 304 float maxWidth, uint32 truncateMode, alignment align) 305 : 306 Inherited(title, width, minWidth, maxWidth, align), 307 fTruncateMode(truncateMode) 308 { 309 SetWantsEvents(true); 310 } 311 312 313 void 314 PackageColumn::DrawField(BField* field, BRect rect, BView* parent) 315 { 316 SharedBitmapStringField* bitmapField 317 = dynamic_cast<SharedBitmapStringField*>(field); 318 BStringField* stringField = dynamic_cast<BStringField*>(field); 319 RatingField* ratingField = dynamic_cast<RatingField*>(field); 320 321 if (bitmapField != NULL) { 322 const BBitmap* bitmap = bitmapField->Bitmap(); 323 324 // figure out the placement 325 float x = 0.0; 326 BRect r = bitmap ? bitmap->Bounds() : BRect(0, 0, 15, 15); 327 float y = rect.top + ((rect.Height() - r.Height()) / 2); 328 float width = 0.0; 329 330 switch (Alignment()) { 331 default: 332 case B_ALIGN_LEFT: 333 case B_ALIGN_CENTER: 334 x = rect.left + sTextMargin; 335 width = rect.right - (x + r.Width()) - (2 * sTextMargin); 336 r.Set(x + r.Width(), rect.top, rect.right - width, rect.bottom); 337 break; 338 339 case B_ALIGN_RIGHT: 340 x = rect.right - sTextMargin - r.Width(); 341 width = (x - rect.left - (2 * sTextMargin)); 342 r.Set(rect.left, rect.top, rect.left + width, rect.bottom); 343 break; 344 } 345 346 if (width != bitmapField->Width()) { 347 BString truncatedString(bitmapField->String()); 348 parent->TruncateString(&truncatedString, fTruncateMode, width + 2); 349 bitmapField->SetClippedString(truncatedString.String()); 350 bitmapField->SetWidth(width); 351 } 352 353 // draw the bitmap 354 if (bitmap != NULL) { 355 parent->SetDrawingMode(B_OP_ALPHA); 356 parent->DrawBitmap(bitmap, BPoint(x, y)); 357 parent->SetDrawingMode(B_OP_OVER); 358 } 359 360 // draw the string 361 DrawString(bitmapField->ClippedString(), parent, r); 362 363 } else if (stringField != NULL) { 364 365 float width = rect.Width() - (2 * sTextMargin); 366 367 if (width != stringField->Width()) { 368 BString truncatedString(stringField->String()); 369 370 parent->TruncateString(&truncatedString, fTruncateMode, width + 2); 371 stringField->SetClippedString(truncatedString.String()); 372 stringField->SetWidth(width); 373 } 374 375 DrawString(stringField->ClippedString(), parent, rect); 376 377 } else if (ratingField != NULL) { 378 379 const float kDefaultTextMargin = 8; 380 381 float width = rect.Width() - (2 * kDefaultTextMargin); 382 383 BString string = "★★★★★"; 384 float stringWidth = parent->StringWidth(string); 385 bool drawOverlay = true; 386 387 if (width < stringWidth) { 388 string.SetToFormat("%.1f", ratingField->Rating()); 389 drawOverlay = false; 390 stringWidth = parent->StringWidth(string); 391 } 392 393 switch (Alignment()) { 394 default: 395 case B_ALIGN_LEFT: 396 rect.left += kDefaultTextMargin; 397 break; 398 case B_ALIGN_CENTER: 399 rect.left = rect.left + (width - stringWidth) / 2.0f; 400 break; 401 402 case B_ALIGN_RIGHT: 403 rect.left = rect.right - (stringWidth + kDefaultTextMargin); 404 break; 405 } 406 407 rect.left = floorf(rect.left); 408 rect.right = rect.left + stringWidth; 409 410 if (drawOverlay) 411 parent->SetHighColor(0, 170, 255); 412 413 font_height fontHeight; 414 parent->GetFontHeight(&fontHeight); 415 float y = rect.top + (rect.Height() 416 - (fontHeight.ascent + fontHeight.descent)) / 2 417 + fontHeight.ascent; 418 419 parent->DrawString(string, BPoint(rect.left, y)); 420 421 if (drawOverlay) { 422 rect.left = ceilf(rect.left 423 + (ratingField->Rating() / 5.0f) * rect.Width()); 424 425 rgb_color color = parent->LowColor(); 426 color.alpha = 190; 427 parent->SetHighColor(color); 428 429 parent->SetDrawingMode(B_OP_ALPHA); 430 parent->FillRect(rect, B_SOLID_HIGH); 431 432 } 433 } 434 } 435 436 437 int 438 PackageColumn::CompareFields(BField* field1, BField* field2) 439 { 440 SizeField* sizeField1 = dynamic_cast<SizeField*>(field1); 441 SizeField* sizeField2 = dynamic_cast<SizeField*>(field2); 442 if (sizeField1 != NULL && sizeField2 != NULL) { 443 if (sizeField1->Size() > sizeField2->Size()) 444 return -1; 445 else if (sizeField1->Size() < sizeField2->Size()) 446 return 1; 447 return 0; 448 } 449 450 BStringField* stringField1 = dynamic_cast<BStringField*>(field1); 451 BStringField* stringField2 = dynamic_cast<BStringField*>(field2); 452 if (stringField1 != NULL && stringField2 != NULL) { 453 // TODO: Locale aware string compare... not too important if 454 // package names are not translated. 455 return strcasecmp(stringField1->String(), stringField2->String()); 456 } 457 458 RatingField* ratingField1 = dynamic_cast<RatingField*>(field1); 459 RatingField* ratingField2 = dynamic_cast<RatingField*>(field2); 460 if (ratingField1 != NULL && ratingField2 != NULL) { 461 if (ratingField1->Rating() > ratingField2->Rating()) 462 return -1; 463 else if (ratingField1->Rating() < ratingField2->Rating()) 464 return 1; 465 return 0; 466 } 467 468 return Inherited::CompareFields(field1, field2); 469 } 470 471 472 float 473 PackageColumn::GetPreferredWidth(BField *_field, BView* parent) const 474 { 475 SharedBitmapStringField* bitmapField 476 = dynamic_cast<SharedBitmapStringField*>(_field); 477 BStringField* stringField = dynamic_cast<BStringField*>(_field); 478 479 float parentWidth = Inherited::GetPreferredWidth(_field, parent); 480 float width = 0.0; 481 482 if (bitmapField) { 483 const BBitmap* bitmap = bitmapField->Bitmap(); 484 BFont font; 485 parent->GetFont(&font); 486 width = font.StringWidth(bitmapField->String()) + 3 * sTextMargin; 487 if (bitmap) 488 width += bitmap->Bounds().Width(); 489 else 490 width += 16; 491 } else if (stringField) { 492 BFont font; 493 parent->GetFont(&font); 494 width = font.StringWidth(stringField->String()) + 2 * sTextMargin; 495 } 496 return max_c(width, parentWidth); 497 } 498 499 500 bool 501 PackageColumn::AcceptsField(const BField* field) const 502 { 503 return dynamic_cast<const BStringField*>(field) != NULL 504 || dynamic_cast<const RatingField*>(field) != NULL; 505 } 506 507 508 void 509 PackageColumn::InitTextMargin(BView* parent) 510 { 511 BFont font; 512 parent->GetFont(&font); 513 sTextMargin = ceilf(font.Size() * 0.8); 514 } 515 516 517 // #pragma mark - PackageRow 518 519 520 enum { 521 kTitleColumn, 522 kRatingColumn, 523 kDescriptionColumn, 524 kSizeColumn, 525 kStatusColumn, 526 }; 527 528 529 PackageRow::PackageRow(const PackageInfoRef& packageRef, 530 PackageListener* packageListener) 531 : 532 Inherited(ceilf(be_plain_font->Size() * 1.8f)), 533 fPackage(packageRef), 534 fPackageListener(packageListener) 535 { 536 if (packageRef.Get() == NULL) 537 return; 538 539 PackageInfo& package = *packageRef.Get(); 540 541 // Package icon and title 542 // NOTE: The icon BBitmap is referenced by the fPackage member. 543 UpdateTitle(); 544 545 // Rating 546 UpdateRating(); 547 548 // Summary 549 UpdateSummary(); 550 551 // Size 552 UpdateSize(); 553 554 // Status 555 UpdateState(); 556 557 package.AddListener(fPackageListener); 558 } 559 560 561 PackageRow::~PackageRow() 562 { 563 if (fPackage.Get() != NULL) 564 fPackage->RemoveListener(fPackageListener); 565 } 566 567 568 void 569 PackageRow::UpdateTitle() 570 { 571 if (fPackage.Get() == NULL) 572 return; 573 574 SetField(new SharedBitmapStringField(fPackage->Icon(), 575 SharedBitmap::SIZE_16, fPackage->Title()), kTitleColumn); 576 } 577 578 579 void 580 PackageRow::UpdateState() 581 { 582 if (fPackage.Get() == NULL) 583 return; 584 585 SetField(new BStringField(package_state_to_string(fPackage)), 586 kStatusColumn); 587 } 588 589 590 void 591 PackageRow::UpdateSummary() 592 { 593 if (fPackage.Get() == NULL) 594 return; 595 596 SetField(new BStringField(fPackage->ShortDescription()), 597 kDescriptionColumn); 598 } 599 600 601 void 602 PackageRow::UpdateRating() 603 { 604 if (fPackage.Get() == NULL) 605 return; 606 RatingSummary summary = fPackage->CalculateRatingSummary(); 607 SetField(new RatingField(summary.averageRating), kRatingColumn); 608 } 609 610 611 void 612 PackageRow::UpdateSize() 613 { 614 if (fPackage.Get() == NULL) 615 return; 616 617 SetField(new SizeField(fPackage->Size()), kSizeColumn); 618 } 619 620 621 // #pragma mark - ItemCountView 622 623 624 class PackageListView::ItemCountView : public BView { 625 public: 626 ItemCountView() 627 : 628 BView("item count view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 629 fItemCount(0) 630 { 631 BFont font(be_plain_font); 632 font.SetSize(9.0f); 633 SetFont(&font); 634 635 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 636 SetLowUIColor(ViewUIColor()); 637 SetHighUIColor(LowUIColor(), B_DARKEN_4_TINT); 638 } 639 640 virtual BSize MinSize() 641 { 642 BString label(_GetLabel()); 643 return BSize(StringWidth(label) + 10, B_H_SCROLL_BAR_HEIGHT); 644 } 645 646 virtual BSize PreferredSize() 647 { 648 return MinSize(); 649 } 650 651 virtual BSize MaxSize() 652 { 653 return MinSize(); 654 } 655 656 virtual void Draw(BRect updateRect) 657 { 658 FillRect(updateRect, B_SOLID_LOW); 659 660 BString label(_GetLabel()); 661 662 font_height fontHeight; 663 GetFontHeight(&fontHeight); 664 665 BRect bounds(Bounds()); 666 float width = StringWidth(label); 667 668 BPoint offset; 669 offset.x = bounds.left + (bounds.Width() - width) / 2.0f; 670 offset.y = bounds.top + (bounds.Height() 671 - (fontHeight.ascent + fontHeight.descent)) / 2.0f 672 + fontHeight.ascent; 673 674 DrawString(label, offset); 675 } 676 677 void SetItemCount(int32 count) 678 { 679 if (count == fItemCount) 680 return; 681 BSize minSize = MinSize(); 682 fItemCount = count; 683 if (minSize != MinSize()) 684 InvalidateLayout(); 685 Invalidate(); 686 } 687 688 private: 689 BString _GetLabel() const 690 { 691 static BMessageFormat format(B_TRANSLATE("{0, plural, " 692 "one{# item} other{# items}}")); 693 694 BString label; 695 format.Format(label, fItemCount); 696 return label; 697 } 698 699 int32 fItemCount; 700 }; 701 702 703 // #pragma mark - PackageListView 704 705 706 PackageListView::PackageListView(BLocker* modelLock) 707 : 708 BColumnListView("package list view", 0, B_FANCY_BORDER, true), 709 fModelLock(modelLock), 710 fPackageListener(new(std::nothrow) PackageListener(this)) 711 { 712 float scale = be_plain_font->Size() / 12.f; 713 float spacing = be_control_look->DefaultItemSpacing() * 2; 714 715 AddColumn(new PackageColumn(B_TRANSLATE("Name"), 150 * scale, 50 * scale, 716 300 * scale, B_TRUNCATE_MIDDLE), kTitleColumn); 717 AddColumn(new PackageColumn(B_TRANSLATE("Rating"), 80 * scale, 50 * scale, 718 100 * scale, B_TRUNCATE_MIDDLE), kRatingColumn); 719 AddColumn(new PackageColumn(B_TRANSLATE("Description"), 300 * scale, 720 80 * scale, 1000 * scale, 721 B_TRUNCATE_MIDDLE), kDescriptionColumn); 722 PackageColumn* sizeColumn = new PackageColumn(B_TRANSLATE("Size"), 723 spacing + StringWidth(B_TRANSLATE("9999.99 KiB")), 50 * scale, 724 140 * scale, B_TRUNCATE_END); 725 sizeColumn->SetAlignment(B_ALIGN_RIGHT); 726 AddColumn(sizeColumn, kSizeColumn); 727 AddColumn(new PackageColumn(B_TRANSLATE("Status"), 728 spacing + StringWidth(B_TRANSLATE("Available")), 60 * scale, 729 140 * scale, B_TRUNCATE_END), kStatusColumn); 730 731 fItemCountView = new ItemCountView(); 732 AddStatusView(fItemCountView); 733 } 734 735 736 PackageListView::~PackageListView() 737 { 738 Clear(); 739 delete fPackageListener; 740 } 741 742 743 void 744 PackageListView::AttachedToWindow() 745 { 746 BColumnListView::AttachedToWindow(); 747 748 PackageColumn::InitTextMargin(ScrollView()); 749 } 750 751 752 void 753 PackageListView::AllAttached() 754 { 755 BColumnListView::AllAttached(); 756 757 SetSortingEnabled(true); 758 SetSortColumn(ColumnAt(0), false, true); 759 } 760 761 762 void 763 PackageListView::MessageReceived(BMessage* message) 764 { 765 switch (message->what) { 766 case MSG_UPDATE_PACKAGE: 767 { 768 BString name; 769 uint32 changes; 770 if (message->FindString("name", &name) != B_OK 771 || message->FindUInt32("changes", &changes) != B_OK) { 772 break; 773 } 774 775 BAutolock _(fModelLock); 776 PackageRow* row = _FindRow(name); 777 if (row != NULL) { 778 if ((changes & PKG_CHANGED_TITLE) != 0) 779 row->UpdateTitle(); 780 if ((changes & PKG_CHANGED_SUMMARY) != 0) 781 row->UpdateSummary(); 782 if ((changes & PKG_CHANGED_RATINGS) != 0) 783 row->UpdateRating(); 784 if ((changes & PKG_CHANGED_STATE) != 0) 785 row->UpdateState(); 786 if ((changes & PKG_CHANGED_SIZE) != 0) 787 row->UpdateSize(); 788 if ((changes & PKG_CHANGED_ICON) != 0) 789 row->UpdateTitle(); 790 } 791 break; 792 } 793 794 default: 795 BColumnListView::MessageReceived(message); 796 break; 797 } 798 } 799 800 801 void 802 PackageListView::SelectionChanged() 803 { 804 BColumnListView::SelectionChanged(); 805 806 BMessage message(MSG_PACKAGE_SELECTED); 807 808 PackageRow* selected = dynamic_cast<PackageRow*>(CurrentSelection()); 809 if (selected != NULL) 810 message.AddString("name", selected->Package()->Name()); 811 812 Window()->PostMessage(&message); 813 } 814 815 816 void 817 PackageListView::Clear() 818 { 819 fItemCountView->SetItemCount(0); 820 BColumnListView::Clear(); 821 } 822 823 824 void 825 PackageListView::AddPackage(const PackageInfoRef& package) 826 { 827 PackageRow* packageRow = _FindRow(package); 828 829 // forget about it if this package is already in the listview 830 if (packageRow != NULL) 831 return; 832 833 BAutolock _(fModelLock); 834 835 // create the row for this package 836 packageRow = new PackageRow(package, fPackageListener); 837 838 // add the row, parent may be NULL (add at top level) 839 AddRow(packageRow); 840 841 // make sure the row is initially expanded 842 ExpandOrCollapse(packageRow, true); 843 844 fItemCountView->SetItemCount(CountRows()); 845 } 846 847 848 void 849 PackageListView::RemovePackage(const PackageInfoRef& package) 850 { 851 PackageRow* packageRow = _FindRow(package); 852 if (packageRow == NULL) 853 return; 854 855 RemoveRow(packageRow); 856 delete packageRow; 857 858 fItemCountView->SetItemCount(CountRows()); 859 } 860 861 862 void 863 PackageListView::SelectPackage(const PackageInfoRef& package) 864 { 865 PackageRow* row = _FindRow(package); 866 BRow* selected = CurrentSelection(); 867 if (row != selected) 868 DeselectAll(); 869 if (row != NULL) { 870 AddToSelection(row); 871 SetFocusRow(row, false); 872 ScrollTo(row); 873 } 874 } 875 876 877 PackageRow* 878 PackageListView::_FindRow(const PackageInfoRef& package, PackageRow* parent) 879 { 880 for (int32 i = CountRows(parent) - 1; i >= 0; i--) { 881 PackageRow* row = dynamic_cast<PackageRow*>(RowAt(i, parent)); 882 if (row != NULL && row->Package() == package) 883 return row; 884 if (CountRows(row) > 0) { 885 // recurse into child rows 886 row = _FindRow(package, row); 887 if (row != NULL) 888 return row; 889 } 890 } 891 892 return NULL; 893 } 894 895 896 PackageRow* 897 PackageListView::_FindRow(const BString& packageName, PackageRow* parent) 898 { 899 for (int32 i = CountRows(parent) - 1; i >= 0; i--) { 900 PackageRow* row = dynamic_cast<PackageRow*>(RowAt(i, parent)); 901 if (row != NULL && row->Package().Get() != NULL 902 && row->Package()->Name() == packageName) { 903 return row; 904 } 905 if (CountRows(row) > 0) { 906 // recurse into child rows 907 row = _FindRow(packageName, row); 908 if (row != NULL) 909 return row; 910 } 911 } 912 913 return NULL; 914 } 915 916