xref: /haiku/src/apps/haikudepot/ui/FeaturedPackagesView.cpp (revision 6afa94a0896259ded7b5b5fc27180f44196c2eca)
1 /*
2  * Copyright 2013-214, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
4  * Copyright 2020-2024, Andrew Lindesay <apl@lindesay.co.nz>.
5  * All rights reserved. Distributed under the terms of the MIT License.
6  */
7 
8 #include "FeaturedPackagesView.h"
9 
10 #include <algorithm>
11 #include <vector>
12 
13 #include <Bitmap.h>
14 #include <Catalog.h>
15 #include <ControlLook.h>
16 #include <Font.h>
17 #include <LayoutBuilder.h>
18 #include <LayoutItem.h>
19 #include <Message.h>
20 #include <ScrollView.h>
21 #include <StringView.h>
22 
23 #include "BitmapView.h"
24 #include "HaikuDepotConstants.h"
25 #include "LocaleUtils.h"
26 #include "Logger.h"
27 #include "MainWindow.h"
28 #include "MarkupTextView.h"
29 #include "MessagePackageListener.h"
30 #include "PackageUtils.h"
31 #include "RatingUtils.h"
32 #include "RatingView.h"
33 #include "SharedIcons.h"
34 
35 
36 #undef B_TRANSLATION_CONTEXT
37 #define B_TRANSLATION_CONTEXT "FeaturedPackagesView"
38 
39 #define SIZE_ICON 64.0f
40 
41 // If the space for the summary has less than this many "M" characters then the summary will not be
42 // displayed.
43 #define MINIMUM_M_COUNT_SUMMARY 10.0f
44 
45 // The title area will be this many times the width of an "M".
46 #define M_COUNT_TITLE 10
47 
48 // The fraction of the icon width that is left of the trailing icon
49 #define TRAILING_ICON_PADDING_LEFT_FACTOR 0.1
50 
51 // The faction of an M-space that is left between the title and the first trailing icon.
52 #define TITLE_RIGHT_TRAILING_ICON_PADDING_M_FACTOR 0.1
53 
54 
55 // #pragma mark - PackageView
56 
57 
58 class StackedFeaturesPackageBandMetrics
59 {
60 public:
StackedFeaturesPackageBandMetrics(float width,BFont * titleFont,BFont * metadataFont)61 	StackedFeaturesPackageBandMetrics(float width, BFont* titleFont, BFont* metadataFont)
62 	{
63 		float padding = be_control_look->DefaultItemSpacing();
64 		BSize iconSize = BControlLook::ComposeIconSize(SIZE_ICON);
65 
66 		font_height titleFontHeight;
67 		font_height metadataFontHeight;
68 		titleFont->GetHeight(&titleFontHeight);
69 		metadataFont->GetHeight(&metadataFontHeight);
70 
71 		float totalTitleAndMetadataHeight = titleFontHeight.ascent + titleFontHeight.descent
72 			+ titleFontHeight.leading
73 			+ metadataFontHeight.leading
74 			+ 2.0 * (metadataFontHeight.ascent + metadataFontHeight.descent);
75 
76 		fHeight = fmaxf(totalTitleAndMetadataHeight, iconSize.Height()) + 2.0 * padding;
77 
78 		{
79 			float iconInset = (fHeight - iconSize.Width()) / 2.0;
80 			fIconRect = BRect(padding, iconInset, iconSize.Width() + padding,
81 				iconSize.Height() + iconInset);
82 		}
83 
84 		{
85 			float titleWidthM = titleFont->StringWidth("M");
86 
87 			float leftTitlePublisherAndChronologicalInfo = fIconRect.right + padding;
88 			float rightTitlePublisherAndChronologicalInfo = fminf(width, fIconRect.Size().Width()
89 				+ (2.0 * padding) + (titleWidthM * M_COUNT_TITLE));
90 
91 // left, top, right bottom
92 			fTitleRect = BRect(leftTitlePublisherAndChronologicalInfo,
93 				(fHeight - totalTitleAndMetadataHeight) / 2.0,
94 				rightTitlePublisherAndChronologicalInfo,
95 				((fHeight - totalTitleAndMetadataHeight) / 2.0)
96 					+ titleFontHeight.ascent + titleFontHeight.descent);
97 
98 			fPublisherRect = BRect(leftTitlePublisherAndChronologicalInfo,
99 				fTitleRect.bottom + titleFontHeight.leading,
100 				rightTitlePublisherAndChronologicalInfo,
101 				fTitleRect.bottom + titleFontHeight.leading
102 					+ metadataFontHeight.ascent + metadataFontHeight.descent);
103 
104 			fChronologicalInfoRect = BRect(leftTitlePublisherAndChronologicalInfo,
105 				fPublisherRect.bottom + metadataFontHeight.leading,
106 				rightTitlePublisherAndChronologicalInfo,
107 				fPublisherRect.bottom + metadataFontHeight.leading
108 					+ metadataFontHeight.ascent + metadataFontHeight.descent);
109         }
110 
111         // sort out the ratings display
112 
113 		{
114 			BSize ratingStarSize = SharedIcons::IconStarBlue16Scaled()->Bitmap()->Bounds().Size();
115 			RatingStarsMetrics ratingStarsMetrics(ratingStarSize);
116 
117 			fRatingStarsRect = BRect(BPoint(fTitleRect.right + padding,
118 				(fHeight - ratingStarsMetrics.Size().Height()) / 2), ratingStarsMetrics.Size());
119 
120 			if (fRatingStarsRect.right > width)
121 				fRatingStarsRect = BRect();
122 			else {
123 				// Now sort out the position for the summary. This is reckoned as a container
124 				// rect because it would be nice to layout the text with newlines and not just a
125 				// single line.
126 
127 				fSummaryContainerRect = BRect(fRatingStarsRect.right + (padding * 2.0), padding,
128 					width - padding, fHeight - (padding * 2.0));
129 
130 				float metadataWidthM = metadataFont->StringWidth("M");
131 
132 				if (fSummaryContainerRect.Size().Width() < MINIMUM_M_COUNT_SUMMARY * metadataWidthM)
133 					fSummaryContainerRect = BRect();
134 			}
135 		}
136 	}
137 
Height()138 	float Height()
139 	{
140 		return fHeight;
141 	}
142 
IconRect()143 	BRect IconRect()
144 	{
145 		return fIconRect;
146 	}
147 
TitleRect()148 	BRect TitleRect()
149 	{
150 		return fTitleRect;
151 	}
152 
PublisherRect()153 	BRect PublisherRect()
154 	{
155 		return fPublisherRect;
156 	}
157 
ChronologicalInfoRect()158 	BRect ChronologicalInfoRect()
159 	{
160 		return fChronologicalInfoRect;
161 	}
162 
RatingStarsRect()163 	BRect RatingStarsRect()
164 	{
165 		return fRatingStarsRect;
166 	}
167 
SummaryContainerRect()168 	BRect SummaryContainerRect()
169 	{
170 		return fSummaryContainerRect;
171 	}
172 
173 private:
174 			float 				fHeight;
175 
176 			BRect				fIconRect;
177 			BRect				fTitleRect;
178 			BRect				fPublisherRect;
179 			BRect				fChronologicalInfoRect;
180 
181 			BRect				fRatingStarsRect;
182 
183 			BRect				fSummaryContainerRect;
184 };
185 
186 
187 class StackedFeaturedPackagesView : public BView {
188 public:
StackedFeaturedPackagesView(Model & model)189 	StackedFeaturedPackagesView(Model& model)
190 		:
191 		BView("stacked featured packages view", B_WILL_DRAW | B_FRAME_EVENTS),
192 		fModel(model),
193 		fSelectedIndex(-1),
194 		fPackageListener(
195 			new(std::nothrow) OnePackageMessagePackageListener(this)),
196 		fLowestIndexAddedOrRemoved(-1)
197 	{
198 		SetEventMask(B_POINTER_EVENTS);
199 
200 		fTitleFont = StackedFeaturedPackagesView::CreateTitleFont();
201 		fMetadataFont = StackedFeaturedPackagesView::CreateMetadataFont();
202 		fSummaryFont = StackedFeaturedPackagesView::CreateSummaryFont();
203 		fBandMetrics = CreateBandMetrics();
204 
205 		Clear();
206 	}
207 
208 
~StackedFeaturedPackagesView()209 	virtual ~StackedFeaturedPackagesView()
210 	{
211 		fPackageListener->SetPackage(PackageInfoRef(NULL));
212 		fPackageListener->ReleaseReference();
213 		delete fBandMetrics;
214 		delete fTitleFont;
215 		delete fMetadataFont;
216 		delete fSummaryFont;
217 	}
218 
219 // #pragma mark - message handling and events
220 
MessageReceived(BMessage * message)221 	virtual void MessageReceived(BMessage* message)
222 	{
223 		switch (message->what) {
224 			case MSG_UPDATE_PACKAGE:
225 			{
226 				BString name;
227 				if (message->FindString("name", &name) != B_OK)
228 					HDINFO("expected 'name' key on package update message");
229 				else
230 					_HandleUpdatePackage(name);
231 				break;
232 			}
233 
234 			case B_COLORS_UPDATED:
235 			{
236 				Invalidate();
237 				break;
238 			}
239 
240 			default:
241 				BView::MessageReceived(message);
242 				break;
243 		}
244 	}
245 
246 
MouseDown(BPoint where)247 	virtual void MouseDown(BPoint where)
248 	{
249 		if (Window()->IsActive() && !IsHidden()) {
250 			BRect bounds = Bounds();
251 			BRect parentBounds = Parent()->Bounds();
252 			ConvertFromParent(&parentBounds);
253 			bounds = bounds & parentBounds;
254 			if (bounds.Contains(where)) {
255 				_MessageSelectIndex(_IndexOfY(where.y));
256 				MakeFocus();
257 			}
258 		}
259 	}
260 
261 
KeyDown(const char * bytes,int32 numBytes)262 	virtual void KeyDown(const char* bytes, int32 numBytes)
263 	{
264 		char key = bytes[0];
265 
266 		switch (key) {
267 			case B_RIGHT_ARROW:
268 			case B_DOWN_ARROW:
269 			{
270 				int32 lastIndex = static_cast<int32>(fPackages.size()) - 1;
271 				if (!IsEmpty() && fSelectedIndex != -1
272 						&& fSelectedIndex < lastIndex) {
273 					_MessageSelectIndex(fSelectedIndex + 1);
274 				}
275 				break;
276 			}
277 			case B_LEFT_ARROW:
278 			case B_UP_ARROW:
279 				if (fSelectedIndex > 0)
280 					_MessageSelectIndex( fSelectedIndex - 1);
281 				break;
282 			case B_PAGE_UP:
283 			{
284 				BRect bounds = Bounds();
285 				ScrollTo(0, fmaxf(0, bounds.top - bounds.Height()));
286 				break;
287 			}
288 			case B_PAGE_DOWN:
289 			{
290 				BRect bounds = Bounds();
291 				float height = fPackages.size() * fBandMetrics->Height();
292 				float maxScrollY = height - bounds.Height();
293 				float pageDownScrollY = bounds.top + bounds.Height();
294 				ScrollTo(0, fminf(maxScrollY, pageDownScrollY));
295 				break;
296 			}
297 			default:
298 				BView::KeyDown(bytes, numBytes);
299 				break;
300 		}
301 	}
302 
303 
304 	/*!	This method will send a message to the Window so that it can signal
305 		back to this and other views that a package has been selected.  This
306 		method won't actually change the state of this view directly.
307 	*/
308 
_MessageSelectIndex(int32 index) const309 	void _MessageSelectIndex(int32 index) const
310 	{
311 		if (index != -1) {
312 			BMessage message(MSG_PACKAGE_SELECTED);
313 			BString packageName = fPackages[index]->Name();
314 			message.AddString("name", packageName);
315 			Window()->PostMessage(&message);
316 		}
317 	}
318 
319 
FrameResized(float width,float height)320 	virtual void FrameResized(float width, float height)
321 	{
322 		BView::FrameResized(width, height);
323 
324 		delete fBandMetrics;
325 		fBandMetrics = CreateBandMetrics();
326 
327 		Invalidate();
328 	}
329 
330 
331 // #pragma mark - update / add / remove / clear data
332 
333 
UpdatePackage(uint32 changeMask,const PackageInfoRef & package)334 	void UpdatePackage(uint32 changeMask, const PackageInfoRef& package)
335 	{
336 		// TODO; could optimize the invalidation?
337 		int32 index = _IndexOfPackage(package);
338 		if (index >= 0) {
339 			fPackages[index] = package;
340 			Invalidate(_RectOfIndex(index));
341 		}
342 	}
343 
344 
Clear()345 	void Clear()
346 	{
347 		for (std::vector<PackageInfoRef>::iterator it = fPackages.begin();
348 				it != fPackages.end(); it++) {
349 			(*it)->RemoveListener(fPackageListener);
350 		}
351 		fPackages.clear();
352 		fSelectedIndex = -1;
353 
354 		Invalidate();
355 	}
356 
357 
CreateTitleFont()358 	static BFont* CreateTitleFont()
359 	{
360 		BFont* font = new BFont(be_plain_font);
361 		font_family family;
362 		font_style style;
363 		font->SetSize(ceilf(font->Size() * 1.8f));
364 		font->GetFamilyAndStyle(&family, &style);
365 		font->SetFamilyAndStyle(family, "Bold");
366 		return font;
367 	}
368 
369 
CreateMetadataFont()370     static BFont* CreateMetadataFont()
371     {
372 		BFont* font = new BFont(be_plain_font);
373 		font_family family;
374 		font_style style;
375 		font->GetFamilyAndStyle(&family, &style);
376 		font->SetFamilyAndStyle(family, "Italic");
377 		return font;
378 	}
379 
380 
CreateSummaryFont()381 	static BFont* CreateSummaryFont()
382 	{
383 		return new BFont(be_plain_font);
384 	}
385 
386 
CreateBandMetrics()387 	StackedFeaturesPackageBandMetrics* CreateBandMetrics()
388 	{
389 		return new StackedFeaturesPackageBandMetrics(Bounds().Width(), fTitleFont, fMetadataFont);
390 	}
391 
392 
IsEmpty() const393 	bool IsEmpty() const
394 	{
395 		return fPackages.size() == 0;
396 	}
397 
398 
_HandleUpdatePackage(const BString & name)399 	void _HandleUpdatePackage(const BString& name)
400 	{
401 		int32 index = _IndexOfName(name);
402 		if (index != -1)
403 			Invalidate(_RectOfIndex(index));
404 	}
405 
406 
_CmpProminences(int64 a,int64 b)407 	static int _CmpProminences(int64 a, int64 b)
408 	{
409 		if (a <= 0)
410 			a = PROMINANCE_ORDERING_MAX;
411 		if (b <= 0)
412 			b = PROMINANCE_ORDERING_MAX;
413 		if (a == b)
414 			return 0;
415 		if (a > b)
416 			return 1;
417 		return -1;
418 	}
419 
420 
421 	/*! This method will return true if the packageA is ordered before
422 		packageB.
423 	*/
424 
_IsPackageBefore(const PackageInfoRef & packageA,const PackageInfoRef & packageB)425 	static bool _IsPackageBefore(const PackageInfoRef& packageA, const PackageInfoRef& packageB)
426 	{
427 		if (!packageA.IsSet() || !packageB.IsSet())
428 			HDFATAL("unexpected NULL reference in a referencable");
429 
430 		uint32 prominenceA = 0;
431 		uint32 prominenceB = 0;
432 
433 		PackageClassificationInfoRef classificationInfoA = packageA->PackageClassificationInfo();
434 		PackageClassificationInfoRef classificationInfoB = packageB->PackageClassificationInfo();
435 
436 		if (classificationInfoA.IsSet())
437 			prominenceA = classificationInfoA->Prominence();
438 
439 		if (classificationInfoB.IsSet())
440 			prominenceB = classificationInfoB->Prominence();
441 
442 		int c = static_cast<int>(prominenceA) - static_cast<int>(prominenceB);
443 
444 		if (c == 0) {
445 			BString titleA;
446 			BString titleB;
447 			PackageUtils::Title(packageA, titleA);
448 			PackageUtils::Title(packageB, titleB);
449 			c = titleA.ICompare(titleB);
450 		}
451 
452 		if (c == 0)
453 			c = packageA->Name().Compare(packageB->Name());
454 
455 		return c < 0;
456 	}
457 
458 
BeginAddRemove()459 	void BeginAddRemove()
460 	{
461 		fLowestIndexAddedOrRemoved = INT32_MAX;
462 	}
463 
464 
EndAddRemove()465 	void EndAddRemove()
466 	{
467 		if (fLowestIndexAddedOrRemoved < INT32_MAX) {
468 			if (fPackages.empty())
469 				Invalidate();
470 			else {
471 				BRect invalidRect = Bounds();
472 				invalidRect.top = _YOfIndex(fLowestIndexAddedOrRemoved);
473 				Invalidate(invalidRect);
474 			}
475 		}
476 	}
477 
478 
AddPackage(const PackageInfoRef & package)479 	void AddPackage(const PackageInfoRef& package)
480 	{
481 		// fPackages is sorted and for this reason it is possible to find the
482 		// insertion point by identifying the first item in fPackages that does
483 		// not return true from the method '_IsPackageBefore'.
484 
485 		std::vector<PackageInfoRef>::iterator itInsertionPt
486 			= std::lower_bound(fPackages.begin(), fPackages.end(), package,
487 				&_IsPackageBefore);
488 
489 		if (itInsertionPt == fPackages.end()
490 				|| package->Name() != (*itInsertionPt)->Name()) {
491 			int32 insertionIndex =
492 				std::distance<std::vector<PackageInfoRef>::const_iterator>(
493 					fPackages.begin(), itInsertionPt);
494 			if (fSelectedIndex >= insertionIndex)
495 				fSelectedIndex++;
496 			fPackages.insert(itInsertionPt, package);
497 			package->AddListener(fPackageListener);
498 			if (insertionIndex < fLowestIndexAddedOrRemoved)
499 				fLowestIndexAddedOrRemoved = insertionIndex;
500 		}
501 	}
502 
503 
RemovePackage(const PackageInfoRef & package)504 	void RemovePackage(const PackageInfoRef& package)
505 	{
506 		int32 index = _IndexOfPackage(package);
507 		if (index >= 0) {
508 			if (fSelectedIndex == index)
509 				fSelectedIndex = -1;
510 			if (fSelectedIndex > index)
511 				fSelectedIndex--;
512 			fPackages[index]->RemoveListener(fPackageListener);
513 			fPackages.erase(fPackages.begin() + index);
514 			if (index < fLowestIndexAddedOrRemoved)
515 				fLowestIndexAddedOrRemoved = index;
516 		}
517 	}
518 
519 
520 // #pragma mark - selection and index handling
521 
522 
SelectPackage(const PackageInfoRef & package)523 	void SelectPackage(const PackageInfoRef& package)
524 	{
525 		_SelectIndex(_IndexOfPackage(package));
526 	}
527 
528 
_SelectIndex(int32 index)529 	void _SelectIndex(int32 index)
530 	{
531 		if (index != fSelectedIndex) {
532 			int32 previousSelectedIndex = fSelectedIndex;
533 			fSelectedIndex = index;
534 			if (fSelectedIndex >= 0)
535 				Invalidate(_RectOfIndex(fSelectedIndex));
536 			if (previousSelectedIndex >= 0)
537 				Invalidate(_RectOfIndex(previousSelectedIndex));
538 			_EnsureIndexVisible(index);
539 		}
540 	}
541 
542 
_IndexOfPackage(PackageInfoRef package) const543 	int32 _IndexOfPackage(PackageInfoRef package) const
544 	{
545 		std::vector<PackageInfoRef>::const_iterator it
546 			= std::lower_bound(fPackages.begin(), fPackages.end(), package,
547 				&_IsPackageBefore);
548 
549 		return (it == fPackages.end() || (*it)->Name() != package->Name())
550 			? -1 : it - fPackages.begin();
551 	}
552 
553 
_IndexOfName(const BString & name) const554 	int32 _IndexOfName(const BString& name) const
555 	{
556 		// TODO; slow linear search.
557 		// the fPackages is not sorted on name and for this reason it is not
558 		// possible to do a binary search.
559 		for (uint32 i = 0; i < fPackages.size(); i++) {
560 			if (fPackages[i]->Name() == name)
561 				return i;
562 		}
563 		return -1;
564 	}
565 
566 
567 // #pragma mark - drawing and rendering
568 
569 
Draw(BRect updateRect)570 	virtual void Draw(BRect updateRect)
571 	{
572 		SetHighUIColor(B_LIST_BACKGROUND_COLOR);
573 		FillRect(updateRect);
574 
575 		int32 iStart = _IndexRoundedOfY(updateRect.top);
576 
577 		if (iStart != -1) {
578 			int32 iEnd = _IndexRoundedOfY(updateRect.bottom);
579 			for (int32 i = iStart; i <= iEnd; i++)
580 				_DrawPackageAtIndex(updateRect, i);
581 		}
582 	}
583 
584 
_DrawPackageAtIndex(BRect updateRect,int32 index)585 	void _DrawPackageAtIndex(BRect updateRect, int32 index)
586 	{
587 		_DrawPackage(updateRect, fPackages[index], _YOfIndex(index), index == fSelectedIndex);
588 	}
589 
590 
_DrawPackage(BRect updateRect,PackageInfoRef pkg,float y,bool selected)591 	void _DrawPackage(BRect updateRect, PackageInfoRef pkg, float y, bool selected)
592 	{
593 		if (selected) {
594 			SetLowUIColor(B_LIST_SELECTED_BACKGROUND_COLOR);
595 			FillRect(_RectOfY(y), B_SOLID_LOW);
596 		} else {
597 			SetLowUIColor(B_LIST_BACKGROUND_COLOR);
598 		}
599 
600 		BRect iconRect = fBandMetrics->IconRect();
601 		BRect titleRect = fBandMetrics->TitleRect();
602 		BRect publisherRect = fBandMetrics->PublisherRect();
603 		BRect chronologicalInfoRect = fBandMetrics->ChronologicalInfoRect();
604 		BRect ratingStarsRect = fBandMetrics->RatingStarsRect();
605 		BRect summaryContainerRect = fBandMetrics->SummaryContainerRect();
606 
607 		iconRect.OffsetBy(0.0, y);
608 		titleRect.OffsetBy(0.0, y);
609 		publisherRect.OffsetBy(0.0, y);
610 		chronologicalInfoRect.OffsetBy(0.0, y);
611 		ratingStarsRect.OffsetBy(0.0, y);
612 		summaryContainerRect.OffsetBy(0.0, y);
613 
614 		// TODO; optimization; the updateRect may only cover some of this?
615 		_DrawPackageIcon(iconRect, pkg, selected);
616 		_DrawPackageTitle(titleRect, pkg, selected);
617 		_DrawPackagePublisher(publisherRect, pkg, selected);
618 		_DrawPackageChronologicalInfo(chronologicalInfoRect, pkg, selected);
619 		_DrawPackageRating(ratingStarsRect, pkg);
620 		_DrawPackageSummary(summaryContainerRect, pkg, selected);
621 	}
622 
623 
_DrawPackageIcon(BRect iconRect,PackageInfoRef pkg,bool selected)624 	void _DrawPackageIcon(BRect iconRect, PackageInfoRef pkg, bool selected)
625 	{
626 		if (!iconRect.IsValid())
627 			return;
628 
629 		BitmapHolderRef icon;
630 		status_t iconResult = fModel.GetPackageIconRepository().GetIcon(pkg->Name(),
631 			iconRect.Width(), icon);
632 
633 		if (iconResult == B_OK) {
634 			if (icon.IsSet()) {
635 				const BBitmap* bitmap = icon->Bitmap();
636 
637 				if (bitmap != NULL && bitmap->IsValid()) {
638 					SetDrawingMode(B_OP_ALPHA);
639 					DrawBitmap(bitmap, bitmap->Bounds(), iconRect, B_FILTER_BITMAP_BILINEAR);
640 				}
641 			}
642 		}
643 	}
644 
645 
_DrawPackageTitle(BRect textRect,PackageInfoRef pkg,bool selected)646 	void _DrawPackageTitle(BRect textRect, PackageInfoRef pkg, bool selected)
647 	{
648 		if (!textRect.IsValid())
649 			return;
650 
651 		std::vector<BitmapHolderRef> trailingIconBitmaps;
652 
653 		if (PackageUtils::State(pkg) == ACTIVATED)
654 			trailingIconBitmaps.push_back(SharedIcons::IconInstalled16Scaled());
655 
656 		if (PackageUtils::IsNativeDesktop(pkg))
657 			trailingIconBitmaps.push_back(SharedIcons::IconNative16Scaled());
658 
659 		float trailingIconsWidth = 0.0f;
660 
661 		for (std::vector<BitmapHolderRef>::iterator it = trailingIconBitmaps.begin();
662 				it != trailingIconBitmaps.end(); it++) {
663 			trailingIconsWidth += (*it)->Bitmap()->Bounds().Width()
664 				* (1.0 + TRAILING_ICON_PADDING_LEFT_FACTOR);
665 		}
666 
667 		SetDrawingMode(B_OP_COPY);
668 		SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR : B_LIST_ITEM_TEXT_COLOR);
669 		SetFont(fTitleFont);
670 
671 		float titleRightTrailingIconsPadding = 0.0f;
672 
673 		if (!trailingIconBitmaps.empty()) {
674 			titleRightTrailingIconsPadding = StringWidth("M")
675 				* TITLE_RIGHT_TRAILING_ICON_PADDING_M_FACTOR;
676 		}
677 
678 		font_height fontHeight;
679 		fTitleFont->GetHeight(&fontHeight);
680 		BPoint pt = textRect.LeftTop() + BPoint(0.0, + fontHeight.ascent);
681 
682 		BString title;
683 		PackageUtils::TitleOrName(pkg, title);
684 
685 		BString renderedText = title;
686 		TruncateString(&renderedText, B_TRUNCATE_END, textRect.Width()
687 			- (titleRightTrailingIconsPadding + trailingIconsWidth));
688 
689 		DrawString(renderedText, pt);
690 
691 		// now draw the trailing icons.
692 
693 		float stringWidth = StringWidth(title);
694 		float trailingIconX = textRect.left + stringWidth + titleRightTrailingIconsPadding;
695 		float trailingIconMidY = textRect.top + (textRect.Height() / 2.0);
696 
697 		SetDrawingMode(B_OP_ALPHA);
698 
699 		for (std::vector<BitmapHolderRef>::iterator it = trailingIconBitmaps.begin();
700 				it != trailingIconBitmaps.end(); it++) {
701 			const BBitmap* bitmap = (*it)->Bitmap();
702 			BRect bitmapBounds = bitmap->Bounds();
703 
704             float trailingIconTopLeftPtX = ceilf(
705             	trailingIconX + (bitmapBounds.Width() * TRAILING_ICON_PADDING_LEFT_FACTOR)) + 0.5;
706             float trailingIconTopLeftPtY = ceilf(
707             	trailingIconMidY - (bitmapBounds.Height() / 2.0)) + 0.5;
708 
709 			BRect trailingIconRect(BPoint(trailingIconTopLeftPtX, trailingIconTopLeftPtY),
710 				bitmap->Bounds().Size());
711 
712 			DrawBitmap(bitmap, bitmapBounds, trailingIconRect, B_FILTER_BITMAP_BILINEAR);
713 
714 			trailingIconX = trailingIconRect.right;
715 		}
716 	}
717 
718 
_DrawPackageGenericTextSlug(BRect textRect,const BString & text,bool selected)719 	void _DrawPackageGenericTextSlug(BRect textRect, const BString& text, bool selected)
720 	{
721 		if (!textRect.IsValid())
722 			return;
723 
724 		SetDrawingMode(B_OP_COPY);
725 		SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR : B_LIST_ITEM_TEXT_COLOR);
726 		SetFont(fMetadataFont);
727 
728 		font_height fontHeight;
729 		fMetadataFont->GetHeight(&fontHeight);
730 		BPoint pt = textRect.LeftTop() + BPoint(0.0, + fontHeight.ascent);
731 
732 		BString renderedText(text);
733 		TruncateString(&renderedText, B_TRUNCATE_END, textRect.Width());
734 
735 		DrawString(renderedText, pt);
736 	}
737 
738 
_DrawPackagePublisher(BRect textRect,PackageInfoRef pkg,bool selected)739 	void _DrawPackagePublisher(BRect textRect, PackageInfoRef pkg, bool selected)
740 	{
741 		_DrawPackageGenericTextSlug(textRect, pkg->Publisher().Name(), selected);
742 	}
743 
744 
_DrawPackageChronologicalInfo(BRect textRect,PackageInfoRef pkg,bool selected)745 	void _DrawPackageChronologicalInfo(BRect textRect, PackageInfoRef pkg, bool selected)
746 	{
747 		BString versionCreateTimestampPresentation
748 			= LocaleUtils::TimestampToDateString(pkg->VersionCreateTimestamp());
749 		_DrawPackageGenericTextSlug(textRect, versionCreateTimestampPresentation, selected);
750 	}
751 
752 
753 	// TODO; show the sample size
_DrawPackageRating(BRect ratingRect,PackageInfoRef pkg)754 	void _DrawPackageRating(BRect ratingRect, PackageInfoRef pkg)
755 	{
756 		if (!ratingRect.IsValid())
757 			return;
758 
759 		UserRatingInfoRef userRatingInfo = pkg->UserRatingInfo();
760 
761 		if (userRatingInfo.IsSet()) {
762 			UserRatingSummaryRef userRatingSummary = userRatingInfo->Summary();
763 
764 			if (userRatingSummary.IsSet())
765 				RatingUtils::Draw(this, ratingRect.LeftTop(), userRatingSummary->AverageRating());
766 		}
767 	}
768 
769 
770 	// TODO; handle multi-line rendering of the text
_DrawPackageSummary(BRect textRect,PackageInfoRef pkg,bool selected)771 	void _DrawPackageSummary(BRect textRect, PackageInfoRef pkg, bool selected)
772 	{
773 		if (!textRect.IsValid())
774 			return;
775 
776 		SetDrawingMode(B_OP_COPY);
777 		SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR : B_LIST_ITEM_TEXT_COLOR);
778 		SetFont(fSummaryFont);
779 
780 		font_height fontHeight;
781 		fSummaryFont->GetHeight(&fontHeight);
782 
783 		// The text rect is a container into which later text can be made to flow multi-line. For
784 		// now just draw one line of the summary.
785 
786 		BPoint pt = textRect.LeftTop() + BPoint(0.0,
787 			(textRect.Size().Height() / 2.0) - ((fontHeight.ascent + fontHeight.descent) / 2.0)
788 				+ fontHeight.ascent);
789 
790 		BString summary;
791 		PackageUtils::Summary(pkg, summary);
792 		TruncateString(&summary, B_TRUNCATE_END, textRect.Width());
793 
794 		DrawString(summary, pt);
795 	}
796 
797 
798 // #pragma mark - geometry and scrolling
799 
800 
801 	/*!	This method will make sure that the package at the given index is
802 		visible.  If the whole of the package can be seen already then it will
803 		do nothing.  If the package is located above the visible region then it
804 		will scroll up to it.  If the package is located below the visible
805 		region then it will scroll down to it.
806 	*/
807 
_EnsureIndexVisible(int32 index)808 	void _EnsureIndexVisible(int32 index)
809 	{
810 		if (!_IsIndexEntirelyVisible(index)) {
811 			BRect bounds = Bounds();
812 			int32 indexOfCentreVisible = _IndexOfY(bounds.top + bounds.Height() / 2);
813 			if (index < indexOfCentreVisible)
814 				ScrollTo(0, _YOfIndex(index));
815 			else {
816 				float scrollPointY = (_YOfIndex(index) + fBandMetrics->Height()) - bounds.Height();
817 				ScrollTo(0, scrollPointY);
818 			}
819 		}
820 	}
821 
822 
823 	/*!	This method will return true if the package at the supplied index is
824 		entirely visible.
825 	*/
826 
_IsIndexEntirelyVisible(int32 index)827 	bool _IsIndexEntirelyVisible(int32 index)
828 	{
829 		BRect bounds = Bounds();
830 		return bounds == (bounds | _RectOfIndex(index));
831 	}
832 
833 
_RectOfIndex(int32 index) const834 	BRect _RectOfIndex(int32 index) const
835 	{
836 		if (index < 0)
837 			return BRect(0, 0, 0, 0);
838 		return _RectOfY(_YOfIndex(index));
839 	}
840 
841 
842 	/*!	Provides the top coordinate (offset from the top of view) of the package
843 		supplied.  If the package does not exist in the view then the coordinate
844 		returned will be B_SIZE_UNSET.
845 	*/
846 
TopOfPackage(const PackageInfoRef & package)847 	float TopOfPackage(const PackageInfoRef& package)
848 	{
849 		if (package.IsSet()) {
850 			int index = _IndexOfPackage(package);
851 			if (-1 != index)
852 				return _YOfIndex(index);
853 		}
854 		return B_SIZE_UNSET;
855 	}
856 
857 
_RectOfY(float y) const858 	BRect _RectOfY(float y) const
859 	{
860 		return BRect(0, y, Bounds().Width(), y + fBandMetrics->Height());
861 	}
862 
863 
_YOfIndex(int32 i) const864 	float _YOfIndex(int32 i) const
865 	{
866 		return i * fBandMetrics->Height();
867 	}
868 
869 
870 	/*! Finds the offset into the list of packages for the y-coord in the view's
871 		coordinate space.  If the y is above or below the list of packages then
872 		this will return -1 to signal this.
873 	*/
874 
_IndexOfY(float y) const875 	int32 _IndexOfY(float y) const
876 	{
877 		if (fPackages.empty())
878 			return -1;
879 		int32 i = static_cast<int32>(y / fBandMetrics->Height());
880 		if (i < 0 || i >= static_cast<int32>(fPackages.size()))
881 			return -1;
882 		return i;
883 	}
884 
885 
886 	/*! Find the offset into the list of packages for the y-coord in the view's
887 		coordinate space.  If the y is above or below the list of packages then
888 		this will return the first or last package index respectively.  If there
889 		are no packages then this will return -1;
890 	*/
891 
_IndexRoundedOfY(float y) const892 	int32 _IndexRoundedOfY(float y) const
893 	{
894 		if (fPackages.empty())
895 			return -1;
896 		int32 i = static_cast<int32>(y / fBandMetrics->Height());
897 		if (i < 0)
898 			return 0;
899 		return std::min(i, (int32) (fPackages.size() - 1));
900 	}
901 
902 
PreferredSize()903 	virtual BSize PreferredSize()
904 	{
905 		return BSize(B_SIZE_UNLIMITED,
906 			fBandMetrics->Height() * static_cast<float>(fPackages.size()));
907 	}
908 
909 
910 private:
911 			Model&				fModel;
912 			std::vector<PackageInfoRef>
913 								fPackages;
914 			int32				fSelectedIndex;
915 			OnePackageMessagePackageListener*
916 								fPackageListener;
917 			int32				fLowestIndexAddedOrRemoved;
918 			StackedFeaturesPackageBandMetrics*
919 								fBandMetrics;
920 
921 			BFont*				fTitleFont;
922 			BFont*				fMetadataFont;
923 			BFont*				fSummaryFont;
924 };
925 
926 
927 // #pragma mark - FeaturedPackagesView
928 
929 
FeaturedPackagesView(Model & model)930 FeaturedPackagesView::FeaturedPackagesView(Model& model)
931 	:
932 	BView(B_TRANSLATE("Featured packages"), 0),
933 	fModel(model)
934 {
935 	fPackagesView = new StackedFeaturedPackagesView(fModel);
936 
937 	fScrollView = new BScrollView("featured packages scroll view",
938 		fPackagesView, 0, false, true, B_FANCY_BORDER);
939 
940 	BLayoutBuilder::Group<>(this)
941 		.Add(fScrollView, 1.0f);
942 }
943 
944 
~FeaturedPackagesView()945 FeaturedPackagesView::~FeaturedPackagesView()
946 {
947 }
948 
949 
950 void
BeginAddRemove()951 FeaturedPackagesView::BeginAddRemove()
952 {
953 	fPackagesView->BeginAddRemove();
954 }
955 
956 
957 void
EndAddRemove()958 FeaturedPackagesView::EndAddRemove()
959 {
960 	fPackagesView->EndAddRemove();
961 	_AdjustViews();
962 }
963 
964 
965 /*! This method will add the package into the list to be displayed.  The
966     insertion will occur in alphabetical order.
967 */
968 
969 void
AddPackage(const PackageInfoRef & package)970 FeaturedPackagesView::AddPackage(const PackageInfoRef& package)
971 {
972 	fPackagesView->AddPackage(package);
973 }
974 
975 
976 void
RemovePackage(const PackageInfoRef & package)977 FeaturedPackagesView::RemovePackage(const PackageInfoRef& package)
978 {
979 	fPackagesView->RemovePackage(package);
980 }
981 
982 
983 void
Clear()984 FeaturedPackagesView::Clear()
985 {
986 	HDINFO("did clear the featured packages view");
987 	fPackagesView->Clear();
988 	_AdjustViews();
989 }
990 
991 
992 void
SelectPackage(const PackageInfoRef & package,bool scrollToEntry)993 FeaturedPackagesView::SelectPackage(const PackageInfoRef& package,
994 	bool scrollToEntry)
995 {
996 	fPackagesView->SelectPackage(package);
997 
998 	if (scrollToEntry) {
999 		float offset = fPackagesView->TopOfPackage(package);
1000 		if (offset != B_SIZE_UNSET)
1001 			fPackagesView->ScrollTo(0, offset);
1002 	}
1003 }
1004 
1005 
1006 void
DoLayout()1007 FeaturedPackagesView::DoLayout()
1008 {
1009 	BView::DoLayout();
1010 	_AdjustViews();
1011 }
1012 
1013 
1014 void
_AdjustViews()1015 FeaturedPackagesView::_AdjustViews()
1016 {
1017 	fScrollView->FrameResized(fScrollView->Frame().Width(),
1018 		fScrollView->Frame().Height());
1019 }
1020