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