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 } 164 165 virtual void MouseDown(BPoint where) 166 { 167 BRect bounds = Bounds(); 168 BRect parentBounds = Parent()->Bounds(); 169 ConvertFromParent(&parentBounds); 170 bounds = bounds & parentBounds; 171 172 if (bounds.Contains(where) && Window()->IsActive()) { 173 BMessage message(MSG_PACKAGE_SELECTED); 174 message.AddString("name", PackageName()); 175 Window()->PostMessage(&message); 176 } 177 } 178 179 void SetPackage(const PackageInfoRef& package) 180 { 181 fPackageListener->SetPackage(package); 182 183 _SetIcon(package->Icon()); 184 _SetInstalled(package->State() == ACTIVATED); 185 186 fTitleView->SetText(package->Title()); 187 188 BString publisher = package->Publisher().Name(); 189 fPublisherView->SetText(publisher); 190 191 BString summary = package->ShortDescription(); 192 fSummaryView->SetText(summary); 193 194 _SetRating(package->CalculateRatingSummary()); 195 196 InvalidateLayout(); 197 Invalidate(); 198 } 199 200 void UpdatePackage(uint32 changeMask, const PackageInfoRef& package) 201 { 202 if ((changeMask & PKG_CHANGED_TITLE) != 0) 203 fTitleView->SetText(package->Title()); 204 if ((changeMask & PKG_CHANGED_SUMMARY) != 0) 205 fSummaryView->SetText(package->ShortDescription()); 206 if ((changeMask & PKG_CHANGED_RATINGS) != 0) 207 _SetRating(package->CalculateRatingSummary()); 208 if ((changeMask & PKG_CHANGED_STATE) != 0) 209 _SetInstalled(package->State() == ACTIVATED); 210 if ((changeMask & PKG_CHANGED_ICON) != 0) 211 _SetIcon(package->Icon()); 212 } 213 214 void Clear() 215 { 216 fPackageListener->SetPackage(PackageInfoRef(NULL)); 217 218 fIconView->UnsetBitmap(); 219 fInstalledIconView->UnsetBitmap(); 220 fTitleView->SetText(""); 221 fPublisherView->SetText(""); 222 fSummaryView->SetText(""); 223 fRatingView->SetRating(-1.0f); 224 fAvgRating->SetText(""); 225 fVoteInfo->SetText(""); 226 } 227 228 const char* PackageTitle() const 229 { 230 return fTitleView->Text(); 231 } 232 233 const char* PackageName() const 234 { 235 if (fPackageListener->Package().Get() != NULL) 236 return fPackageListener->Package()->Name(); 237 else 238 return ""; 239 } 240 241 void SetSelected(bool selected) 242 { 243 if (fSelected == selected) 244 return; 245 fSelected = selected; 246 247 _UpdateColors(); 248 } 249 250 void _UpdateColors() 251 { 252 color_which bgColor = B_LIST_BACKGROUND_COLOR; 253 color_which textColor = B_LIST_ITEM_TEXT_COLOR; 254 255 if (fSelected) { 256 bgColor = B_LIST_SELECTED_BACKGROUND_COLOR; 257 textColor = B_LIST_SELECTED_ITEM_TEXT_COLOR; 258 } 259 260 List<BView*, true> views; 261 262 views.Add(this); 263 views.Add(fIconView); 264 views.Add(fInstalledIconView); 265 views.Add(fTitleView); 266 views.Add(fPublisherView); 267 views.Add(fSummaryView); 268 views.Add(fRatingView); 269 views.Add(fAvgRating); 270 views.Add(fVoteInfo); 271 272 for (int32 i = 0; i < views.CountItems(); i++) { 273 BView* view = views.ItemAtFast(i); 274 275 view->SetViewUIColor(bgColor); 276 view->SetLowUIColor(bgColor); 277 view->SetHighUIColor(textColor); 278 view->Invalidate(); 279 } 280 281 BFont font(be_plain_font); 282 rgb_color color = HighColor(); 283 fSummaryView->SetFontAndColor(&font, B_FONT_ALL, &color); 284 } 285 286 void _SetRating(const RatingSummary& ratingSummary) 287 { 288 fRatingView->SetRating(ratingSummary.averageRating); 289 290 if (ratingSummary.ratingCount > 0) { 291 BString avgRating; 292 avgRating.SetToFormat("%.1f", ratingSummary.averageRating); 293 fAvgRating->SetText(avgRating); 294 295 BString votes; 296 votes.SetToFormat("%d", ratingSummary.ratingCount); 297 298 BString voteInfo(B_TRANSLATE("(%Votes%)")); 299 voteInfo.ReplaceAll("%Votes%", votes); 300 301 fVoteInfo->SetText(voteInfo); 302 } else { 303 fAvgRating->SetText(""); 304 fVoteInfo->SetText(""); 305 } 306 } 307 308 void _SetInstalled(bool installed) 309 { 310 if (installed) { 311 fInstalledIconView->SetBitmap(sInstalledIcon, 312 SharedBitmap::SIZE_16); 313 } else 314 fInstalledIconView->UnsetBitmap(); 315 } 316 317 void _SetIcon(const BitmapRef& icon) 318 { 319 if (icon.Get() != NULL) { 320 fIconView->SetBitmap(icon, SharedBitmap::SIZE_64); 321 } else 322 fIconView->UnsetBitmap(); 323 } 324 325 private: 326 OnePackageMessagePackageListener* fPackageListener; 327 328 BitmapView* fIconView; 329 BitmapView* fInstalledIconView; 330 331 BStringView* fTitleView; 332 BStringView* fPublisherView; 333 334 BTextView* fSummaryView; 335 336 RatingView* fRatingView; 337 BStringView* fAvgRating; 338 BStringView* fVoteInfo; 339 340 bool fSelected; 341 342 BString fPackageName; 343 }; 344 345 346 // #pragma mark - FeaturedPackagesView 347 348 349 FeaturedPackagesView::FeaturedPackagesView() 350 : 351 BView(B_TRANSLATE("Featured packages"), 0) 352 { 353 BGroupLayout* layout = new BGroupLayout(B_VERTICAL); 354 SetLayout(layout); 355 356 fContainerView = new ScrollableGroupView(); 357 fContainerView->SetViewUIColor(B_LIST_BACKGROUND_COLOR); 358 fPackageListLayout = fContainerView->GroupLayout(); 359 360 BScrollView* scrollView = new BScrollView( 361 "featured packages scroll view", fContainerView, 362 0, false, true, B_FANCY_BORDER); 363 364 BScrollBar* scrollBar = scrollView->ScrollBar(B_VERTICAL); 365 if (scrollBar != NULL) 366 scrollBar->SetSteps(10.0f, 20.0f); 367 368 BLayoutBuilder::Group<>(this) 369 .Add(scrollView, 1.0f) 370 ; 371 } 372 373 374 FeaturedPackagesView::~FeaturedPackagesView() 375 { 376 } 377 378 379 void 380 FeaturedPackagesView::AddPackage(const PackageInfoRef& package) 381 { 382 // Find insertion index (alphabetical) 383 int32 index = 0; 384 for (int32 i = 0; BLayoutItem* item = fPackageListLayout->ItemAt(i); i++) { 385 PackageView* view = dynamic_cast<PackageView*>(item->View()); 386 if (view == NULL) 387 break; 388 389 BString name = view->PackageName(); 390 if (name == package->Name()) { 391 // Don't add packages more than once 392 return; 393 } 394 395 BString title = view->PackageTitle(); 396 if (title.Compare(package->Title()) < 0) 397 index++; 398 } 399 400 PackageView* view = new PackageView(); 401 view->SetPackage(package); 402 403 fPackageListLayout->AddView(index, view); 404 } 405 406 407 void 408 FeaturedPackagesView::RemovePackage(const PackageInfoRef& package) 409 { 410 // Find the package 411 for (int32 i = 0; BLayoutItem* item = fPackageListLayout->ItemAt(i); i++) { 412 PackageView* view = dynamic_cast<PackageView*>(item->View()); 413 if (view == NULL) 414 break; 415 416 BString name = view->PackageName(); 417 if (name == package->Name()) { 418 view->RemoveSelf(); 419 delete view; 420 break; 421 } 422 } 423 } 424 425 426 void 427 FeaturedPackagesView::Clear() 428 { 429 for (int32 i = fPackageListLayout->CountItems() - 1; 430 BLayoutItem* item = fPackageListLayout->ItemAt(i); i--) { 431 BView* view = dynamic_cast<PackageView*>(item->View()); 432 if (view != NULL) { 433 view->RemoveSelf(); 434 delete view; 435 } 436 } 437 } 438 439 440 void 441 FeaturedPackagesView::SelectPackage(const PackageInfoRef& package, 442 bool scrollToEntry) 443 { 444 BString selectedName; 445 if (package.Get() != NULL) 446 selectedName = package->Name(); 447 448 for (int32 i = 0; BLayoutItem* item = fPackageListLayout->ItemAt(i); i++) { 449 PackageView* view = dynamic_cast<PackageView*>(item->View()); 450 if (view == NULL) 451 break; 452 453 BString name = view->PackageName(); 454 bool match = (name == selectedName); 455 view->SetSelected(match); 456 457 if (match && scrollToEntry) { 458 // Scroll the view so that the package entry shows up in the middle 459 fContainerView->ScrollTo(0, 460 view->Frame().top 461 - fContainerView->Bounds().Height() / 2 462 + view->Bounds().Height() / 2); 463 } 464 } 465 } 466 467 468 void 469 FeaturedPackagesView::CleanupIcons() 470 { 471 sInstalledIcon.Unset(); 472 } 473