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