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