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