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