xref: /haiku/src/apps/haikudepot/ui/FeaturedPackagesView.cpp (revision a267f17ca645ceae1728e10680c6d1377589eef1)
1 /*
2  * Copyright 2013-214, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
4  * Copyright 2020-2021, 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 <Font.h>
16 #include <LayoutBuilder.h>
17 #include <LayoutItem.h>
18 #include <Message.h>
19 #include <ScrollView.h>
20 #include <StringView.h>
21 #include <SpaceLayoutItem.h>
22 
23 #include "BitmapView.h"
24 #include "HaikuDepotConstants.h"
25 #include "Logger.h"
26 #include "MainWindow.h"
27 #include "MarkupTextView.h"
28 #include "MessagePackageListener.h"
29 #include "RatingUtils.h"
30 #include "RatingView.h"
31 #include "ScrollableGroupView.h"
32 #include "SharedBitmap.h"
33 
34 
35 #undef B_TRANSLATION_CONTEXT
36 #define B_TRANSLATION_CONTEXT "FeaturedPackagesView"
37 
38 
39 #define HEIGHT_PACKAGE 84.0f
40 #define SIZE_ICON 64.0f
41 #define X_POSITION_RATING 350.0f
42 #define X_POSITION_SUMMARY 500.0f
43 #define WIDTH_RATING 100.0f
44 #define Y_PROPORTION_TITLE 0.4f
45 #define Y_PROPORTION_PUBLISHER 0.7f
46 #define PADDING 8.0f
47 
48 
49 static BitmapRef sInstalledIcon(new(std::nothrow)
50 	SharedBitmap(RSRC_INSTALLED), true);
51 
52 
53 // #pragma mark - PackageView
54 
55 
56 class StackedFeaturedPackagesView : public BView {
57 public:
58 	StackedFeaturedPackagesView(Model& model)
59 		:
60 		BView("stacked featured packages view", B_WILL_DRAW | B_FRAME_EVENTS),
61 		fModel(model),
62 		fSelectedIndex(-1),
63 		fPackageListener(
64 			new(std::nothrow) OnePackageMessagePackageListener(this)),
65 		fLowestIndexAddedOrRemoved(-1)
66 	{
67 		SetEventMask(B_POINTER_EVENTS);
68 		Clear();
69 	}
70 
71 
72 	virtual ~StackedFeaturedPackagesView()
73 	{
74 		fPackageListener->SetPackage(PackageInfoRef(NULL));
75 		fPackageListener->ReleaseReference();
76 	}
77 
78 // #pragma mark - message handling and events
79 
80 	virtual void MessageReceived(BMessage* message)
81 	{
82 		switch (message->what) {
83 			case MSG_UPDATE_PACKAGE:
84 			{
85 				BString name;
86 				if (message->FindString("name", &name) != B_OK)
87 					HDINFO("expected 'name' key on package update message");
88 				else
89 					_HandleUpdatePackage(name);
90 				break;
91 			}
92 
93 			case B_COLORS_UPDATED:
94 			{
95 				Invalidate();
96 				break;
97 			}
98 
99 			default:
100 				BView::MessageReceived(message);
101 				break;
102 		}
103 	}
104 
105 
106 	virtual void MouseDown(BPoint where)
107 	{
108 		if (Window()->IsActive() && !IsHidden()) {
109 			BRect bounds = Bounds();
110 			BRect parentBounds = Parent()->Bounds();
111 			ConvertFromParent(&parentBounds);
112 			bounds = bounds & parentBounds;
113 			if (bounds.Contains(where)) {
114 				_MessageSelectIndex(_IndexOfY(where.y));
115 				MakeFocus();
116 			}
117 		}
118 	}
119 
120 
121 	virtual void KeyDown(const char* bytes, int32 numBytes)
122 	{
123 		char key = bytes[0];
124 
125 		switch (key) {
126 			case B_RIGHT_ARROW:
127 			case B_DOWN_ARROW:
128 			{
129 				int32 lastIndex = static_cast<int32>(fPackages.size()) - 1;
130 				if (!IsEmpty() && fSelectedIndex != -1
131 						&& fSelectedIndex < lastIndex) {
132 					_MessageSelectIndex(fSelectedIndex + 1);
133 				}
134 				break;
135 			}
136 			case B_LEFT_ARROW:
137 			case B_UP_ARROW:
138 				if (fSelectedIndex > 0)
139 					_MessageSelectIndex( fSelectedIndex - 1);
140 				break;
141 			case B_PAGE_UP:
142 			{
143 				BRect bounds = Bounds();
144 				ScrollTo(0, fmaxf(0, bounds.top - bounds.Height()));
145 				break;
146 			}
147 			case B_PAGE_DOWN:
148 			{
149 				BRect bounds = Bounds();
150 				float height = fPackages.size() * HEIGHT_PACKAGE;
151 				float maxScrollY = height - bounds.Height();
152 				float pageDownScrollY = bounds.top + bounds.Height();
153 				ScrollTo(0, fminf(maxScrollY, pageDownScrollY));
154 				break;
155 			}
156 			default:
157 				BView::KeyDown(bytes, numBytes);
158 				break;
159 		}
160 	}
161 
162 
163 	/*!	This method will send a message to the Window so that it can signal
164 		back to this and other views that a package has been selected.  This
165 		method won't actually change the state of this view directly.
166 	*/
167 
168 	void _MessageSelectIndex(int32 index) const
169 	{
170 		if (index != -1) {
171 			BMessage message(MSG_PACKAGE_SELECTED);
172 			BString packageName = fPackages[index]->Name();
173 			message.AddString("name", packageName);
174 			Window()->PostMessage(&message);
175 		}
176 	}
177 
178 
179 	virtual void FrameResized(float width, float height)
180 	{
181 		BView::FrameResized(width, height);
182 
183 		// because the summary text will wrap, a resize of the frame will
184 		// result in all of the summary area needing to be redrawn.
185 
186 		BRect rectToInvalidate = Bounds();
187 		rectToInvalidate.left = X_POSITION_SUMMARY;
188 		Invalidate(rectToInvalidate);
189 	}
190 
191 
192 // #pragma mark - update / add / remove / clear data
193 
194 
195 	void UpdatePackage(uint32 changeMask, const PackageInfoRef& package)
196 	{
197 		// TODO; could optimize the invalidation?
198 		int32 index = _IndexOfPackage(package);
199 		if (index >= 0) {
200 			fPackages[index] = package;
201 			Invalidate(_RectOfIndex(index));
202 		}
203 	}
204 
205 
206 	void Clear()
207 	{
208 		for (std::vector<PackageInfoRef>::iterator it = fPackages.begin();
209 				it != fPackages.end(); it++) {
210 			(*it)->RemoveListener(fPackageListener);
211 		}
212 		fPackages.clear();
213 		fSelectedIndex = -1;
214 		Invalidate();
215 	}
216 
217 
218 	bool IsEmpty() const
219 	{
220 		return fPackages.size() == 0;
221 	}
222 
223 
224 	void _HandleUpdatePackage(const BString& name)
225 	{
226 		int32 index = _IndexOfName(name);
227 		if (index != -1)
228 			Invalidate(_RectOfIndex(index));
229 	}
230 
231 
232 	static int _CmpProminences(int64 a, int64 b)
233 	{
234 		if (a <= 0)
235 			a = PROMINANCE_ORDERING_MAX;
236 		if (b <= 0)
237 			b = PROMINANCE_ORDERING_MAX;
238 		if (a == b)
239 			return 0;
240 		if (a > b)
241 			return 1;
242 		return -1;
243 	}
244 
245 
246 	/*! This method will return true if the packageA is ordered before
247 		packageB.
248 	*/
249 
250 	static bool _IsPackageBefore(const PackageInfoRef& packageA,
251 		const PackageInfoRef& packageB)
252 	{
253 		if (!packageA.IsSet() || !packageB.IsSet())
254 			HDFATAL("unexpected NULL reference in a referencable");
255 		int c = _CmpProminences(packageA->Prominence(), packageB->Prominence());
256 		if (c == 0)
257 			c = packageA->Title().ICompare(packageB->Title());
258 		if (c == 0)
259 			c = packageA->Name().Compare(packageB->Name());
260 		return c < 0;
261 	}
262 
263 
264 	void BeginAddRemove()
265 	{
266 		fLowestIndexAddedOrRemoved = INT32_MAX;
267 	}
268 
269 
270 	void EndAddRemove()
271 	{
272 		if (fLowestIndexAddedOrRemoved < INT32_MAX) {
273 			if (fPackages.empty())
274 				Invalidate();
275 			else {
276 				BRect invalidRect = Bounds();
277 				invalidRect.top = _YOfIndex(fLowestIndexAddedOrRemoved);
278 				Invalidate(invalidRect);
279 			}
280 		}
281 	}
282 
283 
284 	void AddPackage(const PackageInfoRef& package)
285 	{
286 		// fPackages is sorted and for this reason it is possible to find the
287 		// insertion point by identifying the first item in fPackages that does
288 		// not return true from the method '_IsPackageBefore'.
289 
290 		std::vector<PackageInfoRef>::iterator itInsertionPt
291 			= std::lower_bound(fPackages.begin(), fPackages.end(), package,
292 				&_IsPackageBefore);
293 
294 		if (itInsertionPt == fPackages.end()
295 				|| package->Name() != (*itInsertionPt)->Name()) {
296 			int32 insertionIndex =
297 				std::distance<std::vector<PackageInfoRef>::const_iterator>(
298 					fPackages.begin(), itInsertionPt);
299 			if (fSelectedIndex >= insertionIndex)
300 				fSelectedIndex++;
301 			fPackages.insert(itInsertionPt, package);
302 			package->AddListener(fPackageListener);
303 			if (insertionIndex < fLowestIndexAddedOrRemoved)
304 				fLowestIndexAddedOrRemoved = insertionIndex;
305 		}
306 	}
307 
308 
309 	void RemovePackage(const PackageInfoRef& package)
310 	{
311 		int32 index = _IndexOfPackage(package);
312 		if (index >= 0) {
313 			if (fSelectedIndex == index)
314 				fSelectedIndex = -1;
315 			if (fSelectedIndex > index)
316 				fSelectedIndex--;
317 			fPackages[index]->RemoveListener(fPackageListener);
318 			fPackages.erase(fPackages.begin() + index);
319 			if (index < fLowestIndexAddedOrRemoved)
320 				fLowestIndexAddedOrRemoved = index;
321 		}
322 	}
323 
324 
325 // #pragma mark - selection and index handling
326 
327 
328 	void SelectPackage(const PackageInfoRef& package)
329 	{
330 		_SelectIndex(_IndexOfPackage(package));
331 	}
332 
333 
334 	void _SelectIndex(int32 index)
335 	{
336 		if (index != fSelectedIndex) {
337 			int32 previousSelectedIndex = fSelectedIndex;
338 			fSelectedIndex = index;
339 			if (fSelectedIndex >= 0)
340 				Invalidate(_RectOfIndex(fSelectedIndex));
341 			if (previousSelectedIndex >= 0)
342 				Invalidate(_RectOfIndex(previousSelectedIndex));
343 			_EnsureIndexVisible(index);
344 		}
345 	}
346 
347 
348 	int32 _IndexOfPackage(PackageInfoRef package) const
349 	{
350 		std::vector<PackageInfoRef>::const_iterator it
351 			= std::lower_bound(fPackages.begin(), fPackages.end(), package,
352 				&_IsPackageBefore);
353 
354 		return (it == fPackages.end() || (*it)->Name() != package->Name())
355 			? -1 : it - fPackages.begin();
356 	}
357 
358 
359 	int32 _IndexOfName(const BString& name) const
360 	{
361 		// TODO; slow linear search.
362 		// the fPackages is not sorted on name and for this reason it is not
363 		// possible to do a binary search.
364 		for (uint32 i = 0; i < fPackages.size(); i++) {
365 			if (fPackages[i]->Name() == name)
366 				return i;
367 		}
368 		return -1;
369 	}
370 
371 
372 // #pragma mark - drawing and rendering
373 
374 
375 	virtual void Draw(BRect updateRect)
376 	{
377 		SetHighUIColor(B_LIST_BACKGROUND_COLOR);
378 		FillRect(updateRect);
379 
380 		int32 iStart = _IndexRoundedOfY(updateRect.top);
381 
382 		if (iStart != -1) {
383 			int32 iEnd = _IndexRoundedOfY(updateRect.bottom);
384 			for (int32 i = iStart; i <= iEnd; i++)
385 				_DrawPackageAtIndex(updateRect, i);
386 		}
387 	}
388 
389 
390 	void _DrawPackageAtIndex(BRect updateRect, int32 index)
391 	{
392 		_DrawPackage(updateRect, fPackages[index], index, _YOfIndex(index),
393 			index == fSelectedIndex);
394 	}
395 
396 
397 	void _DrawPackage(BRect updateRect, PackageInfoRef pkg, int index, float y,
398 		bool selected)
399 	{
400 		if (selected) {
401 			SetLowUIColor(B_LIST_SELECTED_BACKGROUND_COLOR);
402 			FillRect(_RectOfY(y), B_SOLID_LOW);
403 		} else {
404 			SetLowUIColor(B_LIST_BACKGROUND_COLOR);
405 		}
406 		// TODO; optimization; the updateRect may only cover some of this?
407 		_DrawPackageIcon(updateRect, pkg, y, selected);
408 		_DrawPackageTitle(updateRect, pkg, y, selected);
409 		_DrawPackagePublisher(updateRect, pkg, y, selected);
410 		_DrawPackageRating(updateRect, pkg, y, selected);
411 		_DrawPackageSummary(updateRect, pkg, y, selected);
412 	}
413 
414 
415 	void _DrawPackageIcon(BRect updateRect, PackageInfoRef pkg, float y,
416 		bool selected)
417 	{
418 		BitmapRef icon;
419 		status_t iconResult = fModel.GetPackageIconRepository().GetIcon(
420 			pkg->Name(), BITMAP_SIZE_64, icon);
421 
422 		if (iconResult == B_OK) {
423 			if (icon.IsSet()) {
424 				float inset = (HEIGHT_PACKAGE - SIZE_ICON) / 2.0;
425 				BRect targetRect = BRect(inset, y + inset, SIZE_ICON + inset,
426 					y + SIZE_ICON + inset);
427 				const BBitmap* bitmap = icon->Bitmap(BITMAP_SIZE_64);
428 				SetDrawingMode(B_OP_ALPHA);
429 				DrawBitmap(bitmap, bitmap->Bounds(), targetRect,
430 					B_FILTER_BITMAP_BILINEAR);
431 			}
432 		}
433 	}
434 
435 
436 	void _DrawPackageTitle(BRect updateRect, PackageInfoRef pkg, float y,
437 		bool selected)
438 	{
439 		static BFont* sFont = NULL;
440 
441 		if (sFont == NULL) {
442 			sFont = new BFont(be_plain_font);
443 			GetFont(sFont);
444   			font_family family;
445 			font_style style;
446 			sFont->SetSize(ceilf(sFont->Size() * 1.8f));
447 			sFont->GetFamilyAndStyle(&family, &style);
448 			sFont->SetFamilyAndStyle(family, "Bold");
449 		}
450 
451 		SetDrawingMode(B_OP_COPY);
452 		SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR
453 			: B_LIST_ITEM_TEXT_COLOR);
454 		SetFont(sFont);
455 		BPoint pt(HEIGHT_PACKAGE, y + (HEIGHT_PACKAGE * Y_PROPORTION_TITLE));
456 		DrawString(pkg->Title(), pt);
457 
458 		if (pkg->State() == ACTIVATED) {
459 			const BBitmap* bitmap = sInstalledIcon->Bitmap(
460 				BITMAP_SIZE_16);
461 			float stringWidth = StringWidth(pkg->Title());
462 			float offsetX = pt.x + stringWidth + PADDING;
463 			BRect targetRect(offsetX, pt.y - 16, offsetX + 16, pt.y);
464 			SetDrawingMode(B_OP_ALPHA);
465 			DrawBitmap(bitmap, bitmap->Bounds(), targetRect,
466 				B_FILTER_BITMAP_BILINEAR);
467 		}
468 	}
469 
470 
471 	void _DrawPackagePublisher(BRect updateRect, PackageInfoRef pkg, float y,
472 		bool selected)
473 	{
474 		static BFont* sFont = NULL;
475 
476 		if (sFont == NULL) {
477 			sFont = new BFont(be_plain_font);
478 			font_family family;
479 			font_style style;
480 			sFont->SetSize(std::max(9.0f, floorf(sFont->Size() * 0.92f)));
481 			sFont->GetFamilyAndStyle(&family, &style);
482 			sFont->SetFamilyAndStyle(family, "Italic");
483 		}
484 
485 		SetDrawingMode(B_OP_COPY);
486 		SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR
487 			: B_LIST_ITEM_TEXT_COLOR);
488 		SetFont(sFont);
489 
490 		float maxTextWidth = (X_POSITION_RATING - HEIGHT_PACKAGE) - PADDING;
491 		BString publisherName(pkg->Publisher().Name());
492 		TruncateString(&publisherName, B_TRUNCATE_END, maxTextWidth);
493 
494 		DrawString(publisherName, BPoint(HEIGHT_PACKAGE,
495 			y + (HEIGHT_PACKAGE * Y_PROPORTION_PUBLISHER)));
496 	}
497 
498 
499 	// TODO; show the sample size
500 	void _DrawPackageRating(BRect updateRect, PackageInfoRef pkg, float y,
501 		bool selected)
502 	{
503 		BPoint at(X_POSITION_RATING,
504 			y + (HEIGHT_PACKAGE - SIZE_RATING_STAR) / 2.0f);
505 		RatingUtils::Draw(this, at,
506 			pkg->CalculateRatingSummary().averageRating);
507 	}
508 
509 
510 	// TODO; handle multi-line rendering of the text
511 	void _DrawPackageSummary(BRect updateRect, PackageInfoRef pkg, float y,
512 		bool selected)
513 	{
514 		BRect bounds = Bounds();
515 
516 		SetDrawingMode(B_OP_COPY);
517 		SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR
518 			: B_LIST_ITEM_TEXT_COLOR);
519 		SetFont(be_plain_font);
520 
521 		float maxTextWidth = bounds.Width() - X_POSITION_SUMMARY - PADDING;
522 		BString summary(pkg->ShortDescription());
523 		TruncateString(&summary, B_TRUNCATE_END, maxTextWidth);
524 
525 		DrawString(summary, BPoint(X_POSITION_SUMMARY,
526 			y + (HEIGHT_PACKAGE * 0.5)));
527 	}
528 
529 
530 // #pragma mark - geometry and scrolling
531 
532 
533 	/*!	This method will make sure that the package at the given index is
534 		visible.  If the whole of the package can be seen already then it will
535 		do nothing.  If the package is located above the visible region then it
536 		will scroll up to it.  If the package is located below the visible
537 		region then it will scroll down to it.
538 	*/
539 
540 	void _EnsureIndexVisible(int32 index)
541 	{
542 		if (!_IsIndexEntirelyVisible(index)) {
543 			BRect bounds = Bounds();
544 			int32 indexOfCentreVisible = _IndexOfY(
545 				bounds.top + bounds.Height() / 2);
546 			if (index < indexOfCentreVisible)
547 				ScrollTo(0, _YOfIndex(index));
548 			else {
549 				float scrollPointY = (_YOfIndex(index) + HEIGHT_PACKAGE)
550 					- bounds.Height();
551 				ScrollTo(0, scrollPointY);
552 			}
553 		}
554 	}
555 
556 
557 	/*!	This method will return true if the package at the supplied index is
558 		entirely visible.
559 	*/
560 
561 	bool _IsIndexEntirelyVisible(int32 index)
562 	{
563 		BRect bounds = Bounds();
564 		return bounds == (bounds | _RectOfIndex(index));
565 	}
566 
567 
568 	BRect _RectOfIndex(int32 index) const
569 	{
570 		if (index < 0)
571 			return BRect(0, 0, 0, 0);
572 		return _RectOfY(_YOfIndex(index));
573 	}
574 
575 
576 	/*!	Provides the top coordinate (offset from the top of view) of the package
577 		supplied.  If the package does not exist in the view then the coordinate
578 		returned will be B_SIZE_UNSET.
579 	*/
580 
581 	float TopOfPackage(const PackageInfoRef& package)
582 	{
583 		if (package.IsSet()) {
584 			int index = _IndexOfPackage(package);
585 			if (-1 != index)
586 				return _YOfIndex(index);
587 		}
588 		return B_SIZE_UNSET;
589 	}
590 
591 
592 	BRect _RectOfY(float y) const
593 	{
594 		return BRect(0, y, Bounds().Width(), y + HEIGHT_PACKAGE);
595 	}
596 
597 
598 	float _YOfIndex(int32 i) const
599 	{
600 		return i * HEIGHT_PACKAGE;
601 	}
602 
603 
604 	/*! Finds the offset into the list of packages for the y-coord in the view's
605 		coordinate space.  If the y is above or below the list of packages then
606 		this will return -1 to signal this.
607 	*/
608 
609 	int32 _IndexOfY(float y) const
610 	{
611 		if (fPackages.empty())
612 			return -1;
613 		int32 i = static_cast<int32>(y / HEIGHT_PACKAGE);
614 		if (i < 0 || i >= static_cast<int32>(fPackages.size()))
615 			return -1;
616 		return i;
617 	}
618 
619 
620 	/*! Find the offset into the list of packages for the y-coord in the view's
621 		coordinate space.  If the y is above or below the list of packages then
622 		this will return the first or last package index respectively.  If there
623 		are no packages then this will return -1;
624 	*/
625 
626 	int32 _IndexRoundedOfY(float y) const
627 	{
628 		if (fPackages.empty())
629 			return -1;
630 		int32 i = static_cast<int32>(y / HEIGHT_PACKAGE);
631 		if (i < 0)
632 			return 0;
633 		return std::min(i, (int32) (fPackages.size() - 1));
634 	}
635 
636 
637 	virtual BSize PreferredSize()
638 	{
639 		return BSize(B_SIZE_UNLIMITED, HEIGHT_PACKAGE * fPackages.size());
640 	}
641 
642 
643 private:
644 			Model&				fModel;
645 			std::vector<PackageInfoRef>
646 								fPackages;
647 			int32				fSelectedIndex;
648 			OnePackageMessagePackageListener*
649 								fPackageListener;
650 			int32				fLowestIndexAddedOrRemoved;
651 };
652 
653 
654 // #pragma mark - FeaturedPackagesView
655 
656 
657 FeaturedPackagesView::FeaturedPackagesView(Model& model)
658 	:
659 	BView(B_TRANSLATE("Featured packages"), 0),
660 	fModel(model)
661 {
662 	fPackagesView = new StackedFeaturedPackagesView(fModel);
663 
664 	fScrollView = new BScrollView("featured packages scroll view",
665 		fPackagesView, 0, false, true, B_FANCY_BORDER);
666 
667 	BLayoutBuilder::Group<>(this)
668 		.Add(fScrollView, 1.0f);
669 }
670 
671 
672 FeaturedPackagesView::~FeaturedPackagesView()
673 {
674 }
675 
676 
677 void
678 FeaturedPackagesView::BeginAddRemove()
679 {
680 	fPackagesView->BeginAddRemove();
681 }
682 
683 
684 void
685 FeaturedPackagesView::EndAddRemove()
686 {
687 	fPackagesView->EndAddRemove();
688 	_AdjustViews();
689 }
690 
691 
692 /*! This method will add the package into the list to be displayed.  The
693     insertion will occur in alphabetical order.
694 */
695 
696 void
697 FeaturedPackagesView::AddPackage(const PackageInfoRef& package)
698 {
699 	fPackagesView->AddPackage(package);
700 }
701 
702 
703 void
704 FeaturedPackagesView::RemovePackage(const PackageInfoRef& package)
705 {
706 	fPackagesView->RemovePackage(package);
707 }
708 
709 
710 void
711 FeaturedPackagesView::Clear()
712 {
713 	HDINFO("did clear the featured packages view");
714 	fPackagesView->Clear();
715 	_AdjustViews();
716 }
717 
718 
719 void
720 FeaturedPackagesView::SelectPackage(const PackageInfoRef& package,
721 	bool scrollToEntry)
722 {
723 	fPackagesView->SelectPackage(package);
724 
725 	if (scrollToEntry) {
726 		float offset = fPackagesView->TopOfPackage(package);
727 		if (offset != B_SIZE_UNSET)
728 			fPackagesView->ScrollTo(0, offset);
729 	}
730 }
731 
732 
733 void
734 FeaturedPackagesView::DoLayout()
735 {
736 	BView::DoLayout();
737 	_AdjustViews();
738 }
739 
740 
741 void
742 FeaturedPackagesView::_AdjustViews()
743 {
744 	fScrollView->FrameResized(fScrollView->Frame().Width(),
745 		fScrollView->Frame().Height());
746 }
747 
748 
749 void
750 FeaturedPackagesView::CleanupIcons()
751 {
752 	sInstalledIcon.Unset();
753 }
754