xref: /haiku/src/apps/haikudepot/ui/FeaturedPackagesView.cpp (revision 7a617f59fd64449167bb190666bd44fae7efbe0b)
1 /*
2  * Copyright 2013-214, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
4  * Copyright 2020, 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 <stdio.h>
11 
12 #include <Catalog.h>
13 #include <Font.h>
14 #include <LayoutBuilder.h>
15 #include <Message.h>
16 #include <ScrollView.h>
17 #include <StringView.h>
18 #include <SpaceLayoutItem.h>
19 
20 #include "BitmapView.h"
21 #include "HaikuDepotConstants.h"
22 #include "MainWindow.h"
23 #include "MarkupTextView.h"
24 #include "MessagePackageListener.h"
25 #include "RatingView.h"
26 #include "ScrollableGroupView.h"
27 
28 
29 #undef B_TRANSLATION_CONTEXT
30 #define B_TRANSLATION_CONTEXT "FeaturedPackagesView"
31 
32 
33 static BitmapRef sInstalledIcon(new(std::nothrow)
34 	SharedBitmap(RSRC_INSTALLED), true);
35 
36 
37 // #pragma mark - PackageView
38 
39 
40 class PackageView : public BGroupView {
41 public:
42 	PackageView()
43 		:
44 		BGroupView("package view", B_HORIZONTAL),
45 		fPackageListener(
46 			new(std::nothrow) OnePackageMessagePackageListener(this)),
47 		fSelected(false)
48 	{
49 		SetViewUIColor(B_LIST_BACKGROUND_COLOR);
50 		SetHighUIColor(B_LIST_ITEM_TEXT_COLOR);
51 		SetEventMask(B_POINTER_EVENTS);
52 
53 		// Featured icon package should be scaled to 64x64
54 		fIconView = new BitmapView("package icon view");
55 		fIconView->SetExplicitMinSize(BSize(64, 64));
56 
57 		fInstalledIconView = new BitmapView("installed icon view");
58 		fTitleView = new BStringView("package title view", "");
59 		fPublisherView = new BStringView("package publisher view", "");
60 
61 		// Title font
62 		BFont font;
63 		GetFont(&font);
64 		font_family family;
65 		font_style style;
66 		font.SetSize(ceilf(font.Size() * 1.8f));
67 		font.GetFamilyAndStyle(&family, &style);
68 		font.SetFamilyAndStyle(family, "Bold");
69 		fTitleView->SetFont(&font);
70 
71 		// Publisher font
72 		GetFont(&font);
73 		font.SetSize(std::max(9.0f, floorf(font.Size() * 0.92f)));
74 		font.SetFamilyAndStyle(family, "Italic");
75 		fPublisherView->SetFont(&font);
76 
77 		// Summary text view
78 		fSummaryView = new BTextView("package summary");
79 		fSummaryView->MakeSelectable(false);
80 		fSummaryView->MakeEditable(false);
81 		font = BFont(be_plain_font);
82 		rgb_color color = HighColor();
83 		fSummaryView->SetFontAndColor(&font, B_FONT_ALL, &color);
84 
85 		// Rating view
86 		fRatingView = new RatingView("package rating view");
87 
88 		fAvgRating = new BStringView("package average rating", "");
89 		fAvgRating->SetFont(&font);
90 
91 		fVoteInfo = new BStringView("package vote info", "");
92 		// small font
93 		GetFont(&font);
94 		font.SetSize(std::max(9.0f, floorf(font.Size() * 0.85f)));
95 		fVoteInfo->SetFont(&font);
96 		fVoteInfo->SetHighUIColor(HighUIColor());
97 
98 		BLayoutBuilder::Group<>(this)
99 			.Add(fIconView)
100 			.AddGroup(B_VERTICAL, 1.0f, 2.2f)
101 				.AddGroup(B_HORIZONTAL)
102 					.Add(fTitleView)
103 					.Add(fInstalledIconView)
104 					.AddGlue()
105 				.End()
106 				.Add(fPublisherView)
107 				.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
108 			.End()
109 			.AddGlue(0.1f)
110 			.AddGroup(B_HORIZONTAL, 0.8f)
111 				.Add(fRatingView)
112 				.Add(fAvgRating)
113 				.Add(fVoteInfo)
114 			.End()
115 			.AddGlue(0.2f)
116 			.Add(fSummaryView, 2.0f)
117 
118 			.SetInsets(B_USE_WINDOW_INSETS)
119 		;
120 
121 		Clear();
122 	}
123 
124 	virtual ~PackageView()
125 	{
126 		fPackageListener->SetPackage(PackageInfoRef(NULL));
127 		fPackageListener->ReleaseReference();
128 	}
129 
130 	virtual void AllAttached()
131 	{
132 		for (int32 index = 0; index < CountChildren(); ++index) {
133 			ChildAt(index)->SetViewUIColor(ViewUIColor());
134 			ChildAt(index)->SetLowUIColor(ViewUIColor());
135 			ChildAt(index)->SetHighUIColor(HighUIColor());
136 		}
137 	}
138 
139 	virtual void MessageReceived(BMessage* message)
140 	{
141 		switch (message->what) {
142 			case B_MOUSE_WHEEL_CHANGED:
143 				Window()->PostMessage(message, Parent());
144 				break;
145 
146 			case MSG_UPDATE_PACKAGE:
147 			{
148 				uint32 changes = 0;
149 				if (message->FindUInt32("changes", &changes) != B_OK)
150 					break;
151 				UpdatePackage(changes, fPackageListener->Package());
152 				break;
153 			}
154 
155 			case B_COLORS_UPDATED:
156 			{
157 				if (message->HasColor(ui_color_name(B_LIST_ITEM_TEXT_COLOR)))
158 					_UpdateColors();
159 				break;
160 			}
161 
162 			default:
163 				BView::MessageReceived(message);
164 				break;
165 		}
166 	}
167 
168 	virtual void MouseDown(BPoint where)
169 	{
170 		BRect bounds = Bounds();
171 		BRect parentBounds = Parent()->Bounds();
172 		ConvertFromParent(&parentBounds);
173 		bounds = bounds & parentBounds;
174 
175 		if (bounds.Contains(where) && Window()->IsActive() && !IsHidden()) {
176 			BMessage message(MSG_PACKAGE_SELECTED);
177 			message.AddString("name", PackageName());
178 			Window()->PostMessage(&message);
179 		}
180 	}
181 
182 	void SetPackage(const PackageInfoRef& package)
183 	{
184 		fPackageListener->SetPackage(package);
185 
186 		_SetIcon(package->Icon());
187 		_SetInstalled(package->State() == ACTIVATED);
188 
189 		fTitleView->SetText(package->Title());
190 
191 		BString publisher = package->Publisher().Name();
192 		fPublisherView->SetText(publisher);
193 
194 		BString summary = package->ShortDescription();
195 		fSummaryView->SetText(summary);
196 
197 		_SetRating(package->CalculateRatingSummary());
198 
199 		InvalidateLayout();
200 		Invalidate();
201 	}
202 
203 	void UpdatePackage(uint32 changeMask, const PackageInfoRef& package)
204 	{
205 		if ((changeMask & PKG_CHANGED_TITLE) != 0)
206 			fTitleView->SetText(package->Title());
207 		if ((changeMask & PKG_CHANGED_SUMMARY) != 0)
208 			fSummaryView->SetText(package->ShortDescription());
209 		if ((changeMask & PKG_CHANGED_RATINGS) != 0)
210 			_SetRating(package->CalculateRatingSummary());
211 		if ((changeMask & PKG_CHANGED_STATE) != 0)
212 			_SetInstalled(package->State() == ACTIVATED);
213 		if ((changeMask & PKG_CHANGED_ICON) != 0)
214 			_SetIcon(package->Icon());
215 	}
216 
217 	void Clear()
218 	{
219 		fPackageListener->SetPackage(PackageInfoRef(NULL));
220 
221 		fIconView->UnsetBitmap();
222 		fInstalledIconView->UnsetBitmap();
223 		fTitleView->SetText("");
224 		fPublisherView->SetText("");
225 		fSummaryView->SetText("");
226 		fRatingView->SetRating(-1.0f);
227 		fAvgRating->SetText("");
228 		fVoteInfo->SetText("");
229 	}
230 
231 	const char* PackageTitle() const
232 	{
233 		return fTitleView->Text();
234 	}
235 
236 	const char* PackageName() const
237 	{
238 		if (fPackageListener->Package().Get() != NULL)
239 			return fPackageListener->Package()->Name();
240 		else
241 			return "";
242 	}
243 
244 	void SetSelected(bool selected)
245 	{
246 		if (fSelected == selected)
247 			return;
248 		fSelected = selected;
249 
250 		_UpdateColors();
251 	}
252 
253 	void _UpdateColors()
254 	{
255 		color_which bgColor = B_LIST_BACKGROUND_COLOR;
256 		color_which textColor = B_LIST_ITEM_TEXT_COLOR;
257 
258 		if (fSelected) {
259 			bgColor = B_LIST_SELECTED_BACKGROUND_COLOR;
260 			textColor = B_LIST_SELECTED_ITEM_TEXT_COLOR;
261 		}
262 
263 		List<BView*, true> views;
264 
265 		views.Add(this);
266 		views.Add(fIconView);
267 		views.Add(fInstalledIconView);
268 		views.Add(fTitleView);
269 		views.Add(fPublisherView);
270 		views.Add(fSummaryView);
271 		views.Add(fRatingView);
272 		views.Add(fAvgRating);
273 		views.Add(fVoteInfo);
274 
275 		for (int32 i = 0; i < views.CountItems(); i++) {
276 			BView* view = views.ItemAtFast(i);
277 
278 			view->SetViewUIColor(bgColor);
279 			view->SetLowUIColor(bgColor);
280 			view->SetHighUIColor(textColor);
281 			view->Invalidate();
282 		}
283 
284 		BFont font(be_plain_font);
285 		rgb_color color = HighColor();
286 		fSummaryView->SetFontAndColor(&font, B_FONT_ALL, &color);
287 	}
288 
289 	void _SetRating(const RatingSummary& ratingSummary)
290 	{
291 		fRatingView->SetRating(ratingSummary.averageRating);
292 
293 		if (ratingSummary.ratingCount > 0) {
294 			BString avgRating;
295 			avgRating.SetToFormat("%.1f", ratingSummary.averageRating);
296 			fAvgRating->SetText(avgRating);
297 
298 			BString votes;
299 			votes.SetToFormat("%d", ratingSummary.ratingCount);
300 
301 			BString voteInfo(B_TRANSLATE("(%Votes%)"));
302 			voteInfo.ReplaceAll("%Votes%", votes);
303 
304 			fVoteInfo->SetText(voteInfo);
305 		} else {
306 			fAvgRating->SetText("");
307 			fVoteInfo->SetText("");
308 		}
309 	}
310 
311 	void _SetInstalled(bool installed)
312 	{
313 		if (installed) {
314 			fInstalledIconView->SetBitmap(sInstalledIcon,
315 				SharedBitmap::SIZE_16);
316 		} else
317 			fInstalledIconView->UnsetBitmap();
318 	}
319 
320 	void _SetIcon(const BitmapRef& icon)
321 	{
322 		if (icon.Get() != NULL) {
323 			fIconView->SetBitmap(icon, SharedBitmap::SIZE_64);
324 		} else
325 			fIconView->UnsetBitmap();
326 	}
327 
328 private:
329 	OnePackageMessagePackageListener* fPackageListener;
330 
331 	BitmapView*						fIconView;
332 	BitmapView*						fInstalledIconView;
333 
334 	BStringView*					fTitleView;
335 	BStringView*					fPublisherView;
336 
337 	BTextView*						fSummaryView;
338 
339 	RatingView*						fRatingView;
340 	BStringView*					fAvgRating;
341 	BStringView*					fVoteInfo;
342 
343 	bool							fSelected;
344 
345 	BString							fPackageName;
346 };
347 
348 
349 // #pragma mark - FeaturedPackagesView
350 
351 
352 FeaturedPackagesView::FeaturedPackagesView()
353 	:
354 	BView(B_TRANSLATE("Featured packages"), 0)
355 {
356 	BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
357 	SetLayout(layout);
358 
359 	fContainerView = new ScrollableGroupView();
360 	fContainerView->SetViewUIColor(B_LIST_BACKGROUND_COLOR);
361 	fPackageListLayout = fContainerView->GroupLayout();
362 
363 	BScrollView* scrollView = new BScrollView(
364 		"featured packages scroll view", fContainerView,
365 		0, false, true, B_FANCY_BORDER);
366 
367 	BScrollBar* scrollBar = scrollView->ScrollBar(B_VERTICAL);
368 	if (scrollBar != NULL)
369 		scrollBar->SetSteps(10.0f, 20.0f);
370 
371 	BLayoutBuilder::Group<>(this)
372 		.Add(scrollView, 1.0f)
373 	;
374 }
375 
376 
377 FeaturedPackagesView::~FeaturedPackagesView()
378 {
379 }
380 
381 
382 /*! This method will add the package into the list to be displayed.  The
383     insertion will occur in alphabetical order.
384 */
385 
386 void
387 FeaturedPackagesView::AddPackage(const PackageInfoRef& package)
388 {
389 	int32 index = _InsertionIndex(package->Name());
390 	if (index != -1) {
391 		PackageView* view = new PackageView();
392 		view->SetPackage(package);
393 		fPackageListLayout->AddView(index, view);
394 	}
395 }
396 
397 
398 const char*
399 FeaturedPackagesView::_PackageNameAtIndex(int32 index) const
400 {
401 	BLayoutItem* item = fPackageListLayout->ItemAt(index);
402 	PackageView* view = dynamic_cast<PackageView*>(item->View());
403 	return (view != NULL ? view->PackageName() : NULL);
404 		// some of the items in the GroupLayout instance are not of type
405 		// PackageView* and it is not immediately clear where they are
406 		// coming from.
407 
408 }
409 
410 
411 int32
412 FeaturedPackagesView::_InsertionIndex(const BString& packageName) const
413 {
414 	int32 count = fPackageListLayout->CountItems();
415 	return _InsertionIndexBinary(packageName, 0, count - 1);
416 }
417 
418 int32
419 FeaturedPackagesView::_InsertionIndexLinear(const BString& packageName,
420 	int32 startIndex, int32 endIndex) const
421 {
422 	for (int32 i = startIndex; i <= endIndex; i++) {
423 		const char* iPackageName = _PackageNameAtIndex(i);
424 		if (NULL != iPackageName) {
425 			int compare = packageName.Compare(iPackageName);
426 			if (compare == 0)
427 				return -1;
428 			if (compare < 0)
429 				return i;
430 		}
431 	}
432 	return endIndex;
433 }
434 
435 /*! This performs a binary search to find the location at which to insert
436     the item.
437 */
438 
439 int32
440 FeaturedPackagesView::_InsertionIndexBinary(const BString& packageName,
441 	int32 startIndex, int32 endIndex) const
442 {
443 	if (startIndex == endIndex)
444 		return startIndex;
445 
446 	int32 endStartSpan = endIndex - startIndex;
447 
448 	if (endStartSpan < 5)
449 		return _InsertionIndexLinear(packageName, startIndex, endIndex);
450 
451 	int midIndex = startIndex + (endStartSpan / 2);
452 	const char *midPackageName = _PackageNameAtIndex(midIndex);
453 
454 	if (midPackageName == NULL)
455 		return _InsertionIndexLinear(packageName, startIndex, endIndex);
456 
457 	int compare = packageName.Compare(midPackageName);
458 
459 	if (compare == 0)
460 		return -1;
461 			// don't want to insert the same package twice.
462 	if (compare < 0)
463 		return _InsertionIndexBinary(packageName, startIndex, midIndex);
464 	return _InsertionIndexBinary(packageName, midIndex, endIndex);
465 }
466 
467 
468 void
469 FeaturedPackagesView::RemovePackage(const PackageInfoRef& package)
470 {
471 	// Find the package
472 	for (int32 i = 0; BLayoutItem* item = fPackageListLayout->ItemAt(i); i++) {
473 		PackageView* view = dynamic_cast<PackageView*>(item->View());
474 		if (view == NULL)
475 			break;
476 
477 		BString name = view->PackageName();
478 		if (name == package->Name()) {
479 			view->RemoveSelf();
480 			delete view;
481 			break;
482 		}
483 	}
484 }
485 
486 
487 void
488 FeaturedPackagesView::Clear()
489 {
490 	for (int32 i = fPackageListLayout->CountItems() - 1;
491 			BLayoutItem* item = fPackageListLayout->ItemAt(i); i--) {
492 		BView* view = dynamic_cast<PackageView*>(item->View());
493 		if (view != NULL) {
494 			view->RemoveSelf();
495 			delete view;
496 		}
497 	}
498 }
499 
500 
501 void
502 FeaturedPackagesView::SelectPackage(const PackageInfoRef& package,
503 	bool scrollToEntry)
504 {
505 	BString selectedName;
506 	if (package.Get() != NULL)
507 		selectedName = package->Name();
508 
509 	for (int32 i = 0; BLayoutItem* item = fPackageListLayout->ItemAt(i); i++) {
510 		PackageView* view = dynamic_cast<PackageView*>(item->View());
511 		if (view == NULL)
512 			break;
513 
514 		BString name = view->PackageName();
515 		bool match = (name == selectedName);
516 		view->SetSelected(match);
517 
518 		if (match && scrollToEntry) {
519 			// Scroll the view so that the package entry shows up in the middle
520 			fContainerView->ScrollTo(0,
521 				view->Frame().top
522 				- fContainerView->Bounds().Height() / 2
523 				+ view->Bounds().Height() / 2);
524 		}
525 	}
526 }
527 
528 
529 void
530 FeaturedPackagesView::CleanupIcons()
531 {
532 	sInstalledIcon.Unset();
533 }
534