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