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