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