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