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