xref: /haiku/src/apps/haikudepot/ui/PackageListView.cpp (revision 6d2f2ec177bf615a117a7428d71be4330545b320)
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