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