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