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 <ControlLook.h>
16 #include <Font.h>
17 #include <LayoutBuilder.h>
18 #include <LayoutItem.h>
19 #include <Message.h>
20 #include <ScrollView.h>
21 #include <StringView.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 "PackageUtils.h"
31 #include "RatingUtils.h"
32 #include "RatingView.h"
33 #include "SharedIcons.h"
34
35
36 #undef B_TRANSLATION_CONTEXT
37 #define B_TRANSLATION_CONTEXT "FeaturedPackagesView"
38
39 #define SIZE_ICON 64.0f
40
41 // If the space for the summary has less than this many "M" characters then the summary will not be
42 // displayed.
43 #define MINIMUM_M_COUNT_SUMMARY 10.0f
44
45 // The title area will be this many times the width of an "M".
46 #define M_COUNT_TITLE 10
47
48 // The fraction of the icon width that is left of the trailing icon
49 #define TRAILING_ICON_PADDING_LEFT_FACTOR 0.1
50
51 // The faction of an M-space that is left between the title and the first trailing icon.
52 #define TITLE_RIGHT_TRAILING_ICON_PADDING_M_FACTOR 0.1
53
54
55 // #pragma mark - PackageView
56
57
58 class StackedFeaturesPackageBandMetrics
59 {
60 public:
StackedFeaturesPackageBandMetrics(float width,BFont * titleFont,BFont * metadataFont)61 StackedFeaturesPackageBandMetrics(float width, BFont* titleFont, BFont* metadataFont)
62 {
63 float padding = be_control_look->DefaultItemSpacing();
64 BSize iconSize = BControlLook::ComposeIconSize(SIZE_ICON);
65
66 font_height titleFontHeight;
67 font_height metadataFontHeight;
68 titleFont->GetHeight(&titleFontHeight);
69 metadataFont->GetHeight(&metadataFontHeight);
70
71 float totalTitleAndMetadataHeight = titleFontHeight.ascent + titleFontHeight.descent
72 + titleFontHeight.leading
73 + metadataFontHeight.leading
74 + 2.0 * (metadataFontHeight.ascent + metadataFontHeight.descent);
75
76 fHeight = fmaxf(totalTitleAndMetadataHeight, iconSize.Height()) + 2.0 * padding;
77
78 {
79 float iconInset = (fHeight - iconSize.Width()) / 2.0;
80 fIconRect = BRect(padding, iconInset, iconSize.Width() + padding,
81 iconSize.Height() + iconInset);
82 }
83
84 {
85 float titleWidthM = titleFont->StringWidth("M");
86
87 float leftTitlePublisherAndChronologicalInfo = fIconRect.right + padding;
88 float rightTitlePublisherAndChronologicalInfo = fminf(width, fIconRect.Size().Width()
89 + (2.0 * padding) + (titleWidthM * M_COUNT_TITLE));
90
91 // left, top, right bottom
92 fTitleRect = BRect(leftTitlePublisherAndChronologicalInfo,
93 (fHeight - totalTitleAndMetadataHeight) / 2.0,
94 rightTitlePublisherAndChronologicalInfo,
95 ((fHeight - totalTitleAndMetadataHeight) / 2.0)
96 + titleFontHeight.ascent + titleFontHeight.descent);
97
98 fPublisherRect = BRect(leftTitlePublisherAndChronologicalInfo,
99 fTitleRect.bottom + titleFontHeight.leading,
100 rightTitlePublisherAndChronologicalInfo,
101 fTitleRect.bottom + titleFontHeight.leading
102 + metadataFontHeight.ascent + metadataFontHeight.descent);
103
104 fChronologicalInfoRect = BRect(leftTitlePublisherAndChronologicalInfo,
105 fPublisherRect.bottom + metadataFontHeight.leading,
106 rightTitlePublisherAndChronologicalInfo,
107 fPublisherRect.bottom + metadataFontHeight.leading
108 + metadataFontHeight.ascent + metadataFontHeight.descent);
109 }
110
111 // sort out the ratings display
112
113 {
114 BSize ratingStarSize = SharedIcons::IconStarBlue16Scaled()->Bitmap()->Bounds().Size();
115 RatingStarsMetrics ratingStarsMetrics(ratingStarSize);
116
117 fRatingStarsRect = BRect(BPoint(fTitleRect.right + padding,
118 (fHeight - ratingStarsMetrics.Size().Height()) / 2), ratingStarsMetrics.Size());
119
120 if (fRatingStarsRect.right > width)
121 fRatingStarsRect = BRect();
122 else {
123 // Now sort out the position for the summary. This is reckoned as a container
124 // rect because it would be nice to layout the text with newlines and not just a
125 // single line.
126
127 fSummaryContainerRect = BRect(fRatingStarsRect.right + (padding * 2.0), padding,
128 width - padding, fHeight - (padding * 2.0));
129
130 float metadataWidthM = metadataFont->StringWidth("M");
131
132 if (fSummaryContainerRect.Size().Width() < MINIMUM_M_COUNT_SUMMARY * metadataWidthM)
133 fSummaryContainerRect = BRect();
134 }
135 }
136 }
137
Height()138 float Height()
139 {
140 return fHeight;
141 }
142
IconRect()143 BRect IconRect()
144 {
145 return fIconRect;
146 }
147
TitleRect()148 BRect TitleRect()
149 {
150 return fTitleRect;
151 }
152
PublisherRect()153 BRect PublisherRect()
154 {
155 return fPublisherRect;
156 }
157
ChronologicalInfoRect()158 BRect ChronologicalInfoRect()
159 {
160 return fChronologicalInfoRect;
161 }
162
RatingStarsRect()163 BRect RatingStarsRect()
164 {
165 return fRatingStarsRect;
166 }
167
SummaryContainerRect()168 BRect SummaryContainerRect()
169 {
170 return fSummaryContainerRect;
171 }
172
173 private:
174 float fHeight;
175
176 BRect fIconRect;
177 BRect fTitleRect;
178 BRect fPublisherRect;
179 BRect fChronologicalInfoRect;
180
181 BRect fRatingStarsRect;
182
183 BRect fSummaryContainerRect;
184 };
185
186
187 class StackedFeaturedPackagesView : public BView {
188 public:
StackedFeaturedPackagesView(Model & model)189 StackedFeaturedPackagesView(Model& model)
190 :
191 BView("stacked featured packages view", B_WILL_DRAW | B_FRAME_EVENTS),
192 fModel(model),
193 fSelectedIndex(-1),
194 fPackageListener(
195 new(std::nothrow) OnePackageMessagePackageListener(this)),
196 fLowestIndexAddedOrRemoved(-1)
197 {
198 SetEventMask(B_POINTER_EVENTS);
199
200 fTitleFont = StackedFeaturedPackagesView::CreateTitleFont();
201 fMetadataFont = StackedFeaturedPackagesView::CreateMetadataFont();
202 fSummaryFont = StackedFeaturedPackagesView::CreateSummaryFont();
203 fBandMetrics = CreateBandMetrics();
204
205 Clear();
206 }
207
208
~StackedFeaturedPackagesView()209 virtual ~StackedFeaturedPackagesView()
210 {
211 fPackageListener->SetPackage(PackageInfoRef(NULL));
212 fPackageListener->ReleaseReference();
213 delete fBandMetrics;
214 delete fTitleFont;
215 delete fMetadataFont;
216 delete fSummaryFont;
217 }
218
219 // #pragma mark - message handling and events
220
MessageReceived(BMessage * message)221 virtual void MessageReceived(BMessage* message)
222 {
223 switch (message->what) {
224 case MSG_UPDATE_PACKAGE:
225 {
226 BString name;
227 if (message->FindString("name", &name) != B_OK)
228 HDINFO("expected 'name' key on package update message");
229 else
230 _HandleUpdatePackage(name);
231 break;
232 }
233
234 case B_COLORS_UPDATED:
235 {
236 Invalidate();
237 break;
238 }
239
240 default:
241 BView::MessageReceived(message);
242 break;
243 }
244 }
245
246
MouseDown(BPoint where)247 virtual void MouseDown(BPoint where)
248 {
249 if (Window()->IsActive() && !IsHidden()) {
250 BRect bounds = Bounds();
251 BRect parentBounds = Parent()->Bounds();
252 ConvertFromParent(&parentBounds);
253 bounds = bounds & parentBounds;
254 if (bounds.Contains(where)) {
255 _MessageSelectIndex(_IndexOfY(where.y));
256 MakeFocus();
257 }
258 }
259 }
260
261
KeyDown(const char * bytes,int32 numBytes)262 virtual void KeyDown(const char* bytes, int32 numBytes)
263 {
264 char key = bytes[0];
265
266 switch (key) {
267 case B_RIGHT_ARROW:
268 case B_DOWN_ARROW:
269 {
270 int32 lastIndex = static_cast<int32>(fPackages.size()) - 1;
271 if (!IsEmpty() && fSelectedIndex != -1
272 && fSelectedIndex < lastIndex) {
273 _MessageSelectIndex(fSelectedIndex + 1);
274 }
275 break;
276 }
277 case B_LEFT_ARROW:
278 case B_UP_ARROW:
279 if (fSelectedIndex > 0)
280 _MessageSelectIndex( fSelectedIndex - 1);
281 break;
282 case B_PAGE_UP:
283 {
284 BRect bounds = Bounds();
285 ScrollTo(0, fmaxf(0, bounds.top - bounds.Height()));
286 break;
287 }
288 case B_PAGE_DOWN:
289 {
290 BRect bounds = Bounds();
291 float height = fPackages.size() * fBandMetrics->Height();
292 float maxScrollY = height - bounds.Height();
293 float pageDownScrollY = bounds.top + bounds.Height();
294 ScrollTo(0, fminf(maxScrollY, pageDownScrollY));
295 break;
296 }
297 default:
298 BView::KeyDown(bytes, numBytes);
299 break;
300 }
301 }
302
303
304 /*! This method will send a message to the Window so that it can signal
305 back to this and other views that a package has been selected. This
306 method won't actually change the state of this view directly.
307 */
308
_MessageSelectIndex(int32 index) const309 void _MessageSelectIndex(int32 index) const
310 {
311 if (index != -1) {
312 BMessage message(MSG_PACKAGE_SELECTED);
313 BString packageName = fPackages[index]->Name();
314 message.AddString("name", packageName);
315 Window()->PostMessage(&message);
316 }
317 }
318
319
FrameResized(float width,float height)320 virtual void FrameResized(float width, float height)
321 {
322 BView::FrameResized(width, height);
323
324 delete fBandMetrics;
325 fBandMetrics = CreateBandMetrics();
326
327 Invalidate();
328 }
329
330
331 // #pragma mark - update / add / remove / clear data
332
333
UpdatePackage(uint32 changeMask,const PackageInfoRef & package)334 void UpdatePackage(uint32 changeMask, const PackageInfoRef& package)
335 {
336 // TODO; could optimize the invalidation?
337 int32 index = _IndexOfPackage(package);
338 if (index >= 0) {
339 fPackages[index] = package;
340 Invalidate(_RectOfIndex(index));
341 }
342 }
343
344
Clear()345 void Clear()
346 {
347 for (std::vector<PackageInfoRef>::iterator it = fPackages.begin();
348 it != fPackages.end(); it++) {
349 (*it)->RemoveListener(fPackageListener);
350 }
351 fPackages.clear();
352 fSelectedIndex = -1;
353
354 Invalidate();
355 }
356
357
CreateTitleFont()358 static BFont* CreateTitleFont()
359 {
360 BFont* font = new BFont(be_plain_font);
361 font_family family;
362 font_style style;
363 font->SetSize(ceilf(font->Size() * 1.8f));
364 font->GetFamilyAndStyle(&family, &style);
365 font->SetFamilyAndStyle(family, "Bold");
366 return font;
367 }
368
369
CreateMetadataFont()370 static BFont* CreateMetadataFont()
371 {
372 BFont* font = new BFont(be_plain_font);
373 font_family family;
374 font_style style;
375 font->GetFamilyAndStyle(&family, &style);
376 font->SetFamilyAndStyle(family, "Italic");
377 return font;
378 }
379
380
CreateSummaryFont()381 static BFont* CreateSummaryFont()
382 {
383 return new BFont(be_plain_font);
384 }
385
386
CreateBandMetrics()387 StackedFeaturesPackageBandMetrics* CreateBandMetrics()
388 {
389 return new StackedFeaturesPackageBandMetrics(Bounds().Width(), fTitleFont, fMetadataFont);
390 }
391
392
IsEmpty() const393 bool IsEmpty() const
394 {
395 return fPackages.size() == 0;
396 }
397
398
_HandleUpdatePackage(const BString & name)399 void _HandleUpdatePackage(const BString& name)
400 {
401 int32 index = _IndexOfName(name);
402 if (index != -1)
403 Invalidate(_RectOfIndex(index));
404 }
405
406
_CmpProminences(int64 a,int64 b)407 static int _CmpProminences(int64 a, int64 b)
408 {
409 if (a <= 0)
410 a = PROMINANCE_ORDERING_MAX;
411 if (b <= 0)
412 b = PROMINANCE_ORDERING_MAX;
413 if (a == b)
414 return 0;
415 if (a > b)
416 return 1;
417 return -1;
418 }
419
420
421 /*! This method will return true if the packageA is ordered before
422 packageB.
423 */
424
_IsPackageBefore(const PackageInfoRef & packageA,const PackageInfoRef & packageB)425 static bool _IsPackageBefore(const PackageInfoRef& packageA, const PackageInfoRef& packageB)
426 {
427 if (!packageA.IsSet() || !packageB.IsSet())
428 HDFATAL("unexpected NULL reference in a referencable");
429
430 uint32 prominenceA = 0;
431 uint32 prominenceB = 0;
432
433 PackageClassificationInfoRef classificationInfoA = packageA->PackageClassificationInfo();
434 PackageClassificationInfoRef classificationInfoB = packageB->PackageClassificationInfo();
435
436 if (classificationInfoA.IsSet())
437 prominenceA = classificationInfoA->Prominence();
438
439 if (classificationInfoB.IsSet())
440 prominenceB = classificationInfoB->Prominence();
441
442 int c = static_cast<int>(prominenceA) - static_cast<int>(prominenceB);
443
444 if (c == 0) {
445 BString titleA;
446 BString titleB;
447 PackageUtils::Title(packageA, titleA);
448 PackageUtils::Title(packageB, titleB);
449 c = titleA.ICompare(titleB);
450 }
451
452 if (c == 0)
453 c = packageA->Name().Compare(packageB->Name());
454
455 return c < 0;
456 }
457
458
BeginAddRemove()459 void BeginAddRemove()
460 {
461 fLowestIndexAddedOrRemoved = INT32_MAX;
462 }
463
464
EndAddRemove()465 void EndAddRemove()
466 {
467 if (fLowestIndexAddedOrRemoved < INT32_MAX) {
468 if (fPackages.empty())
469 Invalidate();
470 else {
471 BRect invalidRect = Bounds();
472 invalidRect.top = _YOfIndex(fLowestIndexAddedOrRemoved);
473 Invalidate(invalidRect);
474 }
475 }
476 }
477
478
AddPackage(const PackageInfoRef & package)479 void AddPackage(const PackageInfoRef& package)
480 {
481 // fPackages is sorted and for this reason it is possible to find the
482 // insertion point by identifying the first item in fPackages that does
483 // not return true from the method '_IsPackageBefore'.
484
485 std::vector<PackageInfoRef>::iterator itInsertionPt
486 = std::lower_bound(fPackages.begin(), fPackages.end(), package,
487 &_IsPackageBefore);
488
489 if (itInsertionPt == fPackages.end()
490 || package->Name() != (*itInsertionPt)->Name()) {
491 int32 insertionIndex =
492 std::distance<std::vector<PackageInfoRef>::const_iterator>(
493 fPackages.begin(), itInsertionPt);
494 if (fSelectedIndex >= insertionIndex)
495 fSelectedIndex++;
496 fPackages.insert(itInsertionPt, package);
497 package->AddListener(fPackageListener);
498 if (insertionIndex < fLowestIndexAddedOrRemoved)
499 fLowestIndexAddedOrRemoved = insertionIndex;
500 }
501 }
502
503
RemovePackage(const PackageInfoRef & package)504 void RemovePackage(const PackageInfoRef& package)
505 {
506 int32 index = _IndexOfPackage(package);
507 if (index >= 0) {
508 if (fSelectedIndex == index)
509 fSelectedIndex = -1;
510 if (fSelectedIndex > index)
511 fSelectedIndex--;
512 fPackages[index]->RemoveListener(fPackageListener);
513 fPackages.erase(fPackages.begin() + index);
514 if (index < fLowestIndexAddedOrRemoved)
515 fLowestIndexAddedOrRemoved = index;
516 }
517 }
518
519
520 // #pragma mark - selection and index handling
521
522
SelectPackage(const PackageInfoRef & package)523 void SelectPackage(const PackageInfoRef& package)
524 {
525 _SelectIndex(_IndexOfPackage(package));
526 }
527
528
_SelectIndex(int32 index)529 void _SelectIndex(int32 index)
530 {
531 if (index != fSelectedIndex) {
532 int32 previousSelectedIndex = fSelectedIndex;
533 fSelectedIndex = index;
534 if (fSelectedIndex >= 0)
535 Invalidate(_RectOfIndex(fSelectedIndex));
536 if (previousSelectedIndex >= 0)
537 Invalidate(_RectOfIndex(previousSelectedIndex));
538 _EnsureIndexVisible(index);
539 }
540 }
541
542
_IndexOfPackage(PackageInfoRef package) const543 int32 _IndexOfPackage(PackageInfoRef package) const
544 {
545 std::vector<PackageInfoRef>::const_iterator it
546 = std::lower_bound(fPackages.begin(), fPackages.end(), package,
547 &_IsPackageBefore);
548
549 return (it == fPackages.end() || (*it)->Name() != package->Name())
550 ? -1 : it - fPackages.begin();
551 }
552
553
_IndexOfName(const BString & name) const554 int32 _IndexOfName(const BString& name) const
555 {
556 // TODO; slow linear search.
557 // the fPackages is not sorted on name and for this reason it is not
558 // possible to do a binary search.
559 for (uint32 i = 0; i < fPackages.size(); i++) {
560 if (fPackages[i]->Name() == name)
561 return i;
562 }
563 return -1;
564 }
565
566
567 // #pragma mark - drawing and rendering
568
569
Draw(BRect updateRect)570 virtual void Draw(BRect updateRect)
571 {
572 SetHighUIColor(B_LIST_BACKGROUND_COLOR);
573 FillRect(updateRect);
574
575 int32 iStart = _IndexRoundedOfY(updateRect.top);
576
577 if (iStart != -1) {
578 int32 iEnd = _IndexRoundedOfY(updateRect.bottom);
579 for (int32 i = iStart; i <= iEnd; i++)
580 _DrawPackageAtIndex(updateRect, i);
581 }
582 }
583
584
_DrawPackageAtIndex(BRect updateRect,int32 index)585 void _DrawPackageAtIndex(BRect updateRect, int32 index)
586 {
587 _DrawPackage(updateRect, fPackages[index], _YOfIndex(index), index == fSelectedIndex);
588 }
589
590
_DrawPackage(BRect updateRect,PackageInfoRef pkg,float y,bool selected)591 void _DrawPackage(BRect updateRect, PackageInfoRef pkg, float y, bool selected)
592 {
593 if (selected) {
594 SetLowUIColor(B_LIST_SELECTED_BACKGROUND_COLOR);
595 FillRect(_RectOfY(y), B_SOLID_LOW);
596 } else {
597 SetLowUIColor(B_LIST_BACKGROUND_COLOR);
598 }
599
600 BRect iconRect = fBandMetrics->IconRect();
601 BRect titleRect = fBandMetrics->TitleRect();
602 BRect publisherRect = fBandMetrics->PublisherRect();
603 BRect chronologicalInfoRect = fBandMetrics->ChronologicalInfoRect();
604 BRect ratingStarsRect = fBandMetrics->RatingStarsRect();
605 BRect summaryContainerRect = fBandMetrics->SummaryContainerRect();
606
607 iconRect.OffsetBy(0.0, y);
608 titleRect.OffsetBy(0.0, y);
609 publisherRect.OffsetBy(0.0, y);
610 chronologicalInfoRect.OffsetBy(0.0, y);
611 ratingStarsRect.OffsetBy(0.0, y);
612 summaryContainerRect.OffsetBy(0.0, y);
613
614 // TODO; optimization; the updateRect may only cover some of this?
615 _DrawPackageIcon(iconRect, pkg, selected);
616 _DrawPackageTitle(titleRect, pkg, selected);
617 _DrawPackagePublisher(publisherRect, pkg, selected);
618 _DrawPackageChronologicalInfo(chronologicalInfoRect, pkg, selected);
619 _DrawPackageRating(ratingStarsRect, pkg);
620 _DrawPackageSummary(summaryContainerRect, pkg, selected);
621 }
622
623
_DrawPackageIcon(BRect iconRect,PackageInfoRef pkg,bool selected)624 void _DrawPackageIcon(BRect iconRect, PackageInfoRef pkg, bool selected)
625 {
626 if (!iconRect.IsValid())
627 return;
628
629 BitmapHolderRef icon;
630 status_t iconResult = fModel.GetPackageIconRepository().GetIcon(pkg->Name(),
631 iconRect.Width(), icon);
632
633 if (iconResult == B_OK) {
634 if (icon.IsSet()) {
635 const BBitmap* bitmap = icon->Bitmap();
636
637 if (bitmap != NULL && bitmap->IsValid()) {
638 SetDrawingMode(B_OP_ALPHA);
639 DrawBitmap(bitmap, bitmap->Bounds(), iconRect, B_FILTER_BITMAP_BILINEAR);
640 }
641 }
642 }
643 }
644
645
_DrawPackageTitle(BRect textRect,PackageInfoRef pkg,bool selected)646 void _DrawPackageTitle(BRect textRect, PackageInfoRef pkg, bool selected)
647 {
648 if (!textRect.IsValid())
649 return;
650
651 std::vector<BitmapHolderRef> trailingIconBitmaps;
652
653 if (PackageUtils::State(pkg) == ACTIVATED)
654 trailingIconBitmaps.push_back(SharedIcons::IconInstalled16Scaled());
655
656 if (PackageUtils::IsNativeDesktop(pkg))
657 trailingIconBitmaps.push_back(SharedIcons::IconNative16Scaled());
658
659 float trailingIconsWidth = 0.0f;
660
661 for (std::vector<BitmapHolderRef>::iterator it = trailingIconBitmaps.begin();
662 it != trailingIconBitmaps.end(); it++) {
663 trailingIconsWidth += (*it)->Bitmap()->Bounds().Width()
664 * (1.0 + TRAILING_ICON_PADDING_LEFT_FACTOR);
665 }
666
667 SetDrawingMode(B_OP_COPY);
668 SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR : B_LIST_ITEM_TEXT_COLOR);
669 SetFont(fTitleFont);
670
671 float titleRightTrailingIconsPadding = 0.0f;
672
673 if (!trailingIconBitmaps.empty()) {
674 titleRightTrailingIconsPadding = StringWidth("M")
675 * TITLE_RIGHT_TRAILING_ICON_PADDING_M_FACTOR;
676 }
677
678 font_height fontHeight;
679 fTitleFont->GetHeight(&fontHeight);
680 BPoint pt = textRect.LeftTop() + BPoint(0.0, + fontHeight.ascent);
681
682 BString title;
683 PackageUtils::TitleOrName(pkg, title);
684
685 BString renderedText = title;
686 TruncateString(&renderedText, B_TRUNCATE_END, textRect.Width()
687 - (titleRightTrailingIconsPadding + trailingIconsWidth));
688
689 DrawString(renderedText, pt);
690
691 // now draw the trailing icons.
692
693 float stringWidth = StringWidth(title);
694 float trailingIconX = textRect.left + stringWidth + titleRightTrailingIconsPadding;
695 float trailingIconMidY = textRect.top + (textRect.Height() / 2.0);
696
697 SetDrawingMode(B_OP_ALPHA);
698
699 for (std::vector<BitmapHolderRef>::iterator it = trailingIconBitmaps.begin();
700 it != trailingIconBitmaps.end(); it++) {
701 const BBitmap* bitmap = (*it)->Bitmap();
702 BRect bitmapBounds = bitmap->Bounds();
703
704 float trailingIconTopLeftPtX = ceilf(
705 trailingIconX + (bitmapBounds.Width() * TRAILING_ICON_PADDING_LEFT_FACTOR)) + 0.5;
706 float trailingIconTopLeftPtY = ceilf(
707 trailingIconMidY - (bitmapBounds.Height() / 2.0)) + 0.5;
708
709 BRect trailingIconRect(BPoint(trailingIconTopLeftPtX, trailingIconTopLeftPtY),
710 bitmap->Bounds().Size());
711
712 DrawBitmap(bitmap, bitmapBounds, trailingIconRect, B_FILTER_BITMAP_BILINEAR);
713
714 trailingIconX = trailingIconRect.right;
715 }
716 }
717
718
_DrawPackageGenericTextSlug(BRect textRect,const BString & text,bool selected)719 void _DrawPackageGenericTextSlug(BRect textRect, const BString& text, bool selected)
720 {
721 if (!textRect.IsValid())
722 return;
723
724 SetDrawingMode(B_OP_COPY);
725 SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR : B_LIST_ITEM_TEXT_COLOR);
726 SetFont(fMetadataFont);
727
728 font_height fontHeight;
729 fMetadataFont->GetHeight(&fontHeight);
730 BPoint pt = textRect.LeftTop() + BPoint(0.0, + fontHeight.ascent);
731
732 BString renderedText(text);
733 TruncateString(&renderedText, B_TRUNCATE_END, textRect.Width());
734
735 DrawString(renderedText, pt);
736 }
737
738
_DrawPackagePublisher(BRect textRect,PackageInfoRef pkg,bool selected)739 void _DrawPackagePublisher(BRect textRect, PackageInfoRef pkg, bool selected)
740 {
741 _DrawPackageGenericTextSlug(textRect, pkg->Publisher().Name(), selected);
742 }
743
744
_DrawPackageChronologicalInfo(BRect textRect,PackageInfoRef pkg,bool selected)745 void _DrawPackageChronologicalInfo(BRect textRect, PackageInfoRef pkg, bool selected)
746 {
747 BString versionCreateTimestampPresentation
748 = LocaleUtils::TimestampToDateString(pkg->VersionCreateTimestamp());
749 _DrawPackageGenericTextSlug(textRect, versionCreateTimestampPresentation, selected);
750 }
751
752
753 // TODO; show the sample size
_DrawPackageRating(BRect ratingRect,PackageInfoRef pkg)754 void _DrawPackageRating(BRect ratingRect, PackageInfoRef pkg)
755 {
756 if (!ratingRect.IsValid())
757 return;
758
759 UserRatingInfoRef userRatingInfo = pkg->UserRatingInfo();
760
761 if (userRatingInfo.IsSet()) {
762 UserRatingSummaryRef userRatingSummary = userRatingInfo->Summary();
763
764 if (userRatingSummary.IsSet())
765 RatingUtils::Draw(this, ratingRect.LeftTop(), userRatingSummary->AverageRating());
766 }
767 }
768
769
770 // TODO; handle multi-line rendering of the text
_DrawPackageSummary(BRect textRect,PackageInfoRef pkg,bool selected)771 void _DrawPackageSummary(BRect textRect, PackageInfoRef pkg, bool selected)
772 {
773 if (!textRect.IsValid())
774 return;
775
776 SetDrawingMode(B_OP_COPY);
777 SetHighUIColor(selected ? B_LIST_SELECTED_ITEM_TEXT_COLOR : B_LIST_ITEM_TEXT_COLOR);
778 SetFont(fSummaryFont);
779
780 font_height fontHeight;
781 fSummaryFont->GetHeight(&fontHeight);
782
783 // The text rect is a container into which later text can be made to flow multi-line. For
784 // now just draw one line of the summary.
785
786 BPoint pt = textRect.LeftTop() + BPoint(0.0,
787 (textRect.Size().Height() / 2.0) - ((fontHeight.ascent + fontHeight.descent) / 2.0)
788 + fontHeight.ascent);
789
790 BString summary;
791 PackageUtils::Summary(pkg, summary);
792 TruncateString(&summary, B_TRUNCATE_END, textRect.Width());
793
794 DrawString(summary, pt);
795 }
796
797
798 // #pragma mark - geometry and scrolling
799
800
801 /*! This method will make sure that the package at the given index is
802 visible. If the whole of the package can be seen already then it will
803 do nothing. If the package is located above the visible region then it
804 will scroll up to it. If the package is located below the visible
805 region then it will scroll down to it.
806 */
807
_EnsureIndexVisible(int32 index)808 void _EnsureIndexVisible(int32 index)
809 {
810 if (!_IsIndexEntirelyVisible(index)) {
811 BRect bounds = Bounds();
812 int32 indexOfCentreVisible = _IndexOfY(bounds.top + bounds.Height() / 2);
813 if (index < indexOfCentreVisible)
814 ScrollTo(0, _YOfIndex(index));
815 else {
816 float scrollPointY = (_YOfIndex(index) + fBandMetrics->Height()) - bounds.Height();
817 ScrollTo(0, scrollPointY);
818 }
819 }
820 }
821
822
823 /*! This method will return true if the package at the supplied index is
824 entirely visible.
825 */
826
_IsIndexEntirelyVisible(int32 index)827 bool _IsIndexEntirelyVisible(int32 index)
828 {
829 BRect bounds = Bounds();
830 return bounds == (bounds | _RectOfIndex(index));
831 }
832
833
_RectOfIndex(int32 index) const834 BRect _RectOfIndex(int32 index) const
835 {
836 if (index < 0)
837 return BRect(0, 0, 0, 0);
838 return _RectOfY(_YOfIndex(index));
839 }
840
841
842 /*! Provides the top coordinate (offset from the top of view) of the package
843 supplied. If the package does not exist in the view then the coordinate
844 returned will be B_SIZE_UNSET.
845 */
846
TopOfPackage(const PackageInfoRef & package)847 float TopOfPackage(const PackageInfoRef& package)
848 {
849 if (package.IsSet()) {
850 int index = _IndexOfPackage(package);
851 if (-1 != index)
852 return _YOfIndex(index);
853 }
854 return B_SIZE_UNSET;
855 }
856
857
_RectOfY(float y) const858 BRect _RectOfY(float y) const
859 {
860 return BRect(0, y, Bounds().Width(), y + fBandMetrics->Height());
861 }
862
863
_YOfIndex(int32 i) const864 float _YOfIndex(int32 i) const
865 {
866 return i * fBandMetrics->Height();
867 }
868
869
870 /*! Finds the offset into the list of packages for the y-coord in the view's
871 coordinate space. If the y is above or below the list of packages then
872 this will return -1 to signal this.
873 */
874
_IndexOfY(float y) const875 int32 _IndexOfY(float y) const
876 {
877 if (fPackages.empty())
878 return -1;
879 int32 i = static_cast<int32>(y / fBandMetrics->Height());
880 if (i < 0 || i >= static_cast<int32>(fPackages.size()))
881 return -1;
882 return i;
883 }
884
885
886 /*! Find the offset into the list of packages for the y-coord in the view's
887 coordinate space. If the y is above or below the list of packages then
888 this will return the first or last package index respectively. If there
889 are no packages then this will return -1;
890 */
891
_IndexRoundedOfY(float y) const892 int32 _IndexRoundedOfY(float y) const
893 {
894 if (fPackages.empty())
895 return -1;
896 int32 i = static_cast<int32>(y / fBandMetrics->Height());
897 if (i < 0)
898 return 0;
899 return std::min(i, (int32) (fPackages.size() - 1));
900 }
901
902
PreferredSize()903 virtual BSize PreferredSize()
904 {
905 return BSize(B_SIZE_UNLIMITED,
906 fBandMetrics->Height() * static_cast<float>(fPackages.size()));
907 }
908
909
910 private:
911 Model& fModel;
912 std::vector<PackageInfoRef>
913 fPackages;
914 int32 fSelectedIndex;
915 OnePackageMessagePackageListener*
916 fPackageListener;
917 int32 fLowestIndexAddedOrRemoved;
918 StackedFeaturesPackageBandMetrics*
919 fBandMetrics;
920
921 BFont* fTitleFont;
922 BFont* fMetadataFont;
923 BFont* fSummaryFont;
924 };
925
926
927 // #pragma mark - FeaturedPackagesView
928
929
FeaturedPackagesView(Model & model)930 FeaturedPackagesView::FeaturedPackagesView(Model& model)
931 :
932 BView(B_TRANSLATE("Featured packages"), 0),
933 fModel(model)
934 {
935 fPackagesView = new StackedFeaturedPackagesView(fModel);
936
937 fScrollView = new BScrollView("featured packages scroll view",
938 fPackagesView, 0, false, true, B_FANCY_BORDER);
939
940 BLayoutBuilder::Group<>(this)
941 .Add(fScrollView, 1.0f);
942 }
943
944
~FeaturedPackagesView()945 FeaturedPackagesView::~FeaturedPackagesView()
946 {
947 }
948
949
950 void
BeginAddRemove()951 FeaturedPackagesView::BeginAddRemove()
952 {
953 fPackagesView->BeginAddRemove();
954 }
955
956
957 void
EndAddRemove()958 FeaturedPackagesView::EndAddRemove()
959 {
960 fPackagesView->EndAddRemove();
961 _AdjustViews();
962 }
963
964
965 /*! This method will add the package into the list to be displayed. The
966 insertion will occur in alphabetical order.
967 */
968
969 void
AddPackage(const PackageInfoRef & package)970 FeaturedPackagesView::AddPackage(const PackageInfoRef& package)
971 {
972 fPackagesView->AddPackage(package);
973 }
974
975
976 void
RemovePackage(const PackageInfoRef & package)977 FeaturedPackagesView::RemovePackage(const PackageInfoRef& package)
978 {
979 fPackagesView->RemovePackage(package);
980 }
981
982
983 void
Clear()984 FeaturedPackagesView::Clear()
985 {
986 HDINFO("did clear the featured packages view");
987 fPackagesView->Clear();
988 _AdjustViews();
989 }
990
991
992 void
SelectPackage(const PackageInfoRef & package,bool scrollToEntry)993 FeaturedPackagesView::SelectPackage(const PackageInfoRef& package,
994 bool scrollToEntry)
995 {
996 fPackagesView->SelectPackage(package);
997
998 if (scrollToEntry) {
999 float offset = fPackagesView->TopOfPackage(package);
1000 if (offset != B_SIZE_UNSET)
1001 fPackagesView->ScrollTo(0, offset);
1002 }
1003 }
1004
1005
1006 void
DoLayout()1007 FeaturedPackagesView::DoLayout()
1008 {
1009 BView::DoLayout();
1010 _AdjustViews();
1011 }
1012
1013
1014 void
_AdjustViews()1015 FeaturedPackagesView::_AdjustViews()
1016 {
1017 fScrollView->FrameResized(fScrollView->Frame().Width(),
1018 fScrollView->Frame().Height());
1019 }
1020