xref: /haiku/src/apps/haikudepot/ui/FeaturedPackagesView.cpp (revision b8a45b3a2df2379b4301bf3bd5949b9a105be4ba)
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 <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 "SharedIcons.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.35f
45 #define Y_PROPORTION_PUBLISHER 0.60f
46 #define Y_PROPORTION_CHRONOLOGICAL_DATA 0.75f
47 #define PADDING 8.0f
48 
49 
50 // #pragma mark - PackageView
51 
52 
53 class StackedFeaturedPackagesView : public BView {
54 public:
55 	StackedFeaturedPackagesView(Model& model)
56 		:
57 		BView("stacked featured packages view", B_WILL_DRAW | B_FRAME_EVENTS),
58 		fModel(model),
59 		fSelectedIndex(-1),
60 		fPackageListener(
61 			new(std::nothrow) OnePackageMessagePackageListener(this)),
62 		fLowestIndexAddedOrRemoved(-1)
63 	{
64 		SetEventMask(B_POINTER_EVENTS);
65 		Clear();
66 	}
67 
68 
69 	virtual ~StackedFeaturedPackagesView()
70 	{
71 		fPackageListener->SetPackage(PackageInfoRef(NULL));
72 		fPackageListener->ReleaseReference();
73 	}
74 
75 // #pragma mark - message handling and events
76 
77 	virtual void MessageReceived(BMessage* message)
78 	{
79 		switch (message->what) {
80 			case MSG_UPDATE_PACKAGE:
81 			{
82 				BString name;
83 				if (message->FindString("name", &name) != B_OK)
84 					HDINFO("expected 'name' key on package update message");
85 				else
86 					_HandleUpdatePackage(name);
87 				break;
88 			}
89 
90 			case B_COLORS_UPDATED:
91 			{
92 				Invalidate();
93 				break;
94 			}
95 
96 			default:
97 				BView::MessageReceived(message);
98 				break;
99 		}
100 	}
101 
102 
103 	virtual void MouseDown(BPoint where)
104 	{
105 		if (Window()->IsActive() && !IsHidden()) {
106 			BRect bounds = Bounds();
107 			BRect parentBounds = Parent()->Bounds();
108 			ConvertFromParent(&parentBounds);
109 			bounds = bounds & parentBounds;
110 			if (bounds.Contains(where)) {
111 				_MessageSelectIndex(_IndexOfY(where.y));
112 				MakeFocus();
113 			}
114 		}
115 	}
116 
117 
118 	virtual void KeyDown(const char* bytes, int32 numBytes)
119 	{
120 		char key = bytes[0];
121 
122 		switch (key) {
123 			case B_RIGHT_ARROW:
124 			case B_DOWN_ARROW:
125 			{
126 				int32 lastIndex = static_cast<int32>(fPackages.size()) - 1;
127 				if (!IsEmpty() && fSelectedIndex != -1
128 						&& fSelectedIndex < lastIndex) {
129 					_MessageSelectIndex(fSelectedIndex + 1);
130 				}
131 				break;
132 			}
133 			case B_LEFT_ARROW:
134 			case B_UP_ARROW:
135 				if (fSelectedIndex > 0)
136 					_MessageSelectIndex( fSelectedIndex - 1);
137 				break;
138 			case B_PAGE_UP:
139 			{
140 				BRect bounds = Bounds();
141 				ScrollTo(0, fmaxf(0, bounds.top - bounds.Height()));
142 				break;
143 			}
144 			case B_PAGE_DOWN:
145 			{
146 				BRect bounds = Bounds();
147 				float height = fPackages.size() * HEIGHT_PACKAGE;
148 				float maxScrollY = height - bounds.Height();
149 				float pageDownScrollY = bounds.top + bounds.Height();
150 				ScrollTo(0, fminf(maxScrollY, pageDownScrollY));
151 				break;
152 			}
153 			default:
154 				BView::KeyDown(bytes, numBytes);
155 				break;
156 		}
157 	}
158 
159 
160 	/*!	This method will send a message to the Window so that it can signal
161 		back to this and other views that a package has been selected.  This
162 		method won't actually change the state of this view directly.
163 	*/
164 
165 	void _MessageSelectIndex(int32 index) const
166 	{
167 		if (index != -1) {
168 			BMessage message(MSG_PACKAGE_SELECTED);
169 			BString packageName = fPackages[index]->Name();
170 			message.AddString("name", packageName);
171 			Window()->PostMessage(&message);
172 		}
173 	}
174 
175 
176 	virtual void FrameResized(float width, float height)
177 	{
178 		BView::FrameResized(width, height);
179 
180 		// because the summary text will wrap, a resize of the frame will
181 		// result in all of the summary area needing to be redrawn.
182 
183 		BRect rectToInvalidate = Bounds();
184 		rectToInvalidate.left = X_POSITION_SUMMARY;
185 		Invalidate(rectToInvalidate);
186 	}
187 
188 
189 // #pragma mark - update / add / remove / clear data
190 
191 
192 	void UpdatePackage(uint32 changeMask, const PackageInfoRef& package)
193 	{
194 		// TODO; could optimize the invalidation?
195 		int32 index = _IndexOfPackage(package);
196 		if (index >= 0) {
197 			fPackages[index] = package;
198 			Invalidate(_RectOfIndex(index));
199 		}
200 	}
201 
202 
203 	void Clear()
204 	{
205 		for (std::vector<PackageInfoRef>::iterator it = fPackages.begin();
206 				it != fPackages.end(); it++) {
207 			(*it)->RemoveListener(fPackageListener);
208 		}
209 		fPackages.clear();
210 		fSelectedIndex = -1;
211 		Invalidate();
212 	}
213 
214 
215 	bool IsEmpty() const
216 	{
217 		return fPackages.size() == 0;
218 	}
219 
220 
221 	void _HandleUpdatePackage(const BString& name)
222 	{
223 		int32 index = _IndexOfName(name);
224 		if (index != -1)
225 			Invalidate(_RectOfIndex(index));
226 	}
227 
228 
229 	static int _CmpProminences(int64 a, int64 b)
230 	{
231 		if (a <= 0)
232 			a = PROMINANCE_ORDERING_MAX;
233 		if (b <= 0)
234 			b = PROMINANCE_ORDERING_MAX;
235 		if (a == b)
236 			return 0;
237 		if (a > b)
238 			return 1;
239 		return -1;
240 	}
241 
242 
243 	/*! This method will return true if the packageA is ordered before
244 		packageB.
245 	*/
246 
247 	static bool _IsPackageBefore(const PackageInfoRef& packageA,
248 		const PackageInfoRef& packageB)
249 	{
250 		if (!packageA.IsSet() || !packageB.IsSet())
251 			HDFATAL("unexpected NULL reference in a referencable");
252 		int c = _CmpProminences(packageA->Prominence(), packageB->Prominence());
253 		if (c == 0)
254 			c = packageA->Title().ICompare(packageB->Title());
255 		if (c == 0)
256 			c = packageA->Name().Compare(packageB->Name());
257 		return c < 0;
258 	}
259 
260 
261 	void BeginAddRemove()
262 	{
263 		fLowestIndexAddedOrRemoved = INT32_MAX;
264 	}
265 
266 
267 	void EndAddRemove()
268 	{
269 		if (fLowestIndexAddedOrRemoved < INT32_MAX) {
270 			if (fPackages.empty())
271 				Invalidate();
272 			else {
273 				BRect invalidRect = Bounds();
274 				invalidRect.top = _YOfIndex(fLowestIndexAddedOrRemoved);
275 				Invalidate(invalidRect);
276 			}
277 		}
278 	}
279 
280 
281 	void AddPackage(const PackageInfoRef& package)
282 	{
283 		// fPackages is sorted and for this reason it is possible to find the
284 		// insertion point by identifying the first item in fPackages that does
285 		// not return true from the method '_IsPackageBefore'.
286 
287 		std::vector<PackageInfoRef>::iterator itInsertionPt
288 			= std::lower_bound(fPackages.begin(), fPackages.end(), package,
289 				&_IsPackageBefore);
290 
291 		if (itInsertionPt == fPackages.end()
292 				|| package->Name() != (*itInsertionPt)->Name()) {
293 			int32 insertionIndex =
294 				std::distance<std::vector<PackageInfoRef>::const_iterator>(
295 					fPackages.begin(), itInsertionPt);
296 			if (fSelectedIndex >= insertionIndex)
297 				fSelectedIndex++;
298 			fPackages.insert(itInsertionPt, package);
299 			package->AddListener(fPackageListener);
300 			if (insertionIndex < fLowestIndexAddedOrRemoved)
301 				fLowestIndexAddedOrRemoved = insertionIndex;
302 		}
303 	}
304 
305 
306 	void RemovePackage(const PackageInfoRef& package)
307 	{
308 		int32 index = _IndexOfPackage(package);
309 		if (index >= 0) {
310 			if (fSelectedIndex == index)
311 				fSelectedIndex = -1;
312 			if (fSelectedIndex > index)
313 				fSelectedIndex--;
314 			fPackages[index]->RemoveListener(fPackageListener);
315 			fPackages.erase(fPackages.begin() + index);
316 			if (index < fLowestIndexAddedOrRemoved)
317 				fLowestIndexAddedOrRemoved = index;
318 		}
319 	}
320 
321 
322 // #pragma mark - selection and index handling
323 
324 
325 	void SelectPackage(const PackageInfoRef& package)
326 	{
327 		_SelectIndex(_IndexOfPackage(package));
328 	}
329 
330 
331 	void _SelectIndex(int32 index)
332 	{
333 		if (index != fSelectedIndex) {
334 			int32 previousSelectedIndex = fSelectedIndex;
335 			fSelectedIndex = index;
336 			if (fSelectedIndex >= 0)
337 				Invalidate(_RectOfIndex(fSelectedIndex));
338 			if (previousSelectedIndex >= 0)
339 				Invalidate(_RectOfIndex(previousSelectedIndex));
340 			_EnsureIndexVisible(index);
341 		}
342 	}
343 
344 
345 	int32 _IndexOfPackage(PackageInfoRef package) const
346 	{
347 		std::vector<PackageInfoRef>::const_iterator it
348 			= std::lower_bound(fPackages.begin(), fPackages.end(), package,
349 				&_IsPackageBefore);
350 
351 		return (it == fPackages.end() || (*it)->Name() != package->Name())
352 			? -1 : it - fPackages.begin();
353 	}
354 
355 
356 	int32 _IndexOfName(const BString& name) const
357 	{
358 		// TODO; slow linear search.
359 		// the fPackages is not sorted on name and for this reason it is not
360 		// possible to do a binary search.
361 		for (uint32 i = 0; i < fPackages.size(); i++) {
362 			if (fPackages[i]->Name() == name)
363 				return i;
364 		}
365 		return -1;
366 	}
367 
368 
369 // #pragma mark - drawing and rendering
370 
371 
372 	virtual void Draw(BRect updateRect)
373 	{
374 		SetHighUIColor(B_LIST_BACKGROUND_COLOR);
375 		FillRect(updateRect);
376 
377 		int32 iStart = _IndexRoundedOfY(updateRect.top);
378 
379 		if (iStart != -1) {
380 			int32 iEnd = _IndexRoundedOfY(updateRect.bottom);
381 			for (int32 i = iStart; i <= iEnd; i++)
382 				_DrawPackageAtIndex(updateRect, i);
383 		}
384 	}
385 
386 
387 	void _DrawPackageAtIndex(BRect updateRect, int32 index)
388 	{
389 		_DrawPackage(updateRect, fPackages[index], index, _YOfIndex(index),
390 			index == fSelectedIndex);
391 	}
392 
393 
394 	void _DrawPackage(BRect updateRect, PackageInfoRef pkg, int index, float y,
395 		bool selected)
396 	{
397 		if (selected) {
398 			SetLowUIColor(B_LIST_SELECTED_BACKGROUND_COLOR);
399 			FillRect(_RectOfY(y), B_SOLID_LOW);
400 		} else {
401 			SetLowUIColor(B_LIST_BACKGROUND_COLOR);
402 		}
403 		// TODO; optimization; the updateRect may only cover some of this?
404 		_DrawPackageIcon(updateRect, pkg, y, selected);
405 		_DrawPackageTitle(updateRect, pkg, y, selected);
406 		_DrawPackagePublisher(updateRect, pkg, y, selected);
407 		_DrawPackageCronologicalInfo(updateRect, pkg, y, selected);
408 		_DrawPackageRating(updateRect, pkg, y, selected);
409 		_DrawPackageSummary(updateRect, pkg, y, selected);
410 	}
411 
412 
413 	void _DrawPackageIcon(BRect updateRect, PackageInfoRef pkg, float y,
414 		bool selected)
415 	{
416 		BitmapHolderRef icon;
417 		status_t iconResult = fModel.GetPackageIconRepository().GetIcon(
418 			pkg->Name(), 64, icon);
419 
420 		if (iconResult == B_OK) {
421 			if (icon.IsSet()) {
422 				float inset = (HEIGHT_PACKAGE - SIZE_ICON) / 2.0;
423 				BRect targetRect = BRect(inset, y + inset, SIZE_ICON + inset,
424 					y + SIZE_ICON + inset);
425 				const BBitmap* bitmap = icon->Bitmap();
426 
427 				if (bitmap != NULL && bitmap->IsValid()) {
428 					SetDrawingMode(B_OP_ALPHA);
429 					DrawBitmap(bitmap, bitmap->Bounds(), targetRect,
430 						B_FILTER_BITMAP_BILINEAR);
431 				}
432 			}
433 		}
434 	}
435 
436 
437 	void _DrawPackageTitle(BRect updateRect, PackageInfoRef pkg, float y,
438 		bool selected)
439 	{
440 		static BFont* sFont = NULL;
441 
442 		if (sFont == NULL) {
443 			sFont = new BFont(be_plain_font);
444 			GetFont(sFont);
445   			font_family family;
446 			font_style style;
447 			sFont->SetSize(ceilf(sFont->Size() * 1.8f));
448 			sFont->GetFamilyAndStyle(&family, &style);
449 			sFont->SetFamilyAndStyle(family, "Bold");
450 		}
451 
452 		SetDrawingMode(B_OP_COPY);
453 		SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR
454 			: B_LIST_ITEM_TEXT_COLOR);
455 		SetFont(sFont);
456 		BPoint pt(HEIGHT_PACKAGE, y + (HEIGHT_PACKAGE * Y_PROPORTION_TITLE));
457 		DrawString(pkg->Title(), pt);
458 
459 		if (pkg->State() == ACTIVATED) {
460 			const BBitmap* bitmap = SharedIcons::IconInstalled16Scaled()->Bitmap();
461 			if (bitmap != NULL && bitmap->IsValid()) {
462 				float stringWidth = StringWidth(pkg->Title());
463 				float offsetX = pt.x + stringWidth + PADDING;
464 				BRect targetRect(offsetX, pt.y - 16,
465 					offsetX + 16, pt.y);
466 				SetDrawingMode(B_OP_ALPHA);
467 				DrawBitmap(bitmap, bitmap->Bounds(), targetRect,
468 					B_FILTER_BITMAP_BILINEAR);
469 			}
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