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