repository_infos: Enable automatic updates on the main Haiku repostiory.
[haiku.git] / src / apps / haikudepot / ui / PackageInfoView.cpp
blob4b4e86af8e5d11073ed6806c6e620f96ad634707
1 /*
2 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
6 #include "PackageInfoView.h"
8 #include <algorithm>
9 #include <stdio.h>
11 #include <Alert.h>
12 #include <Autolock.h>
13 #include <Bitmap.h>
14 #include <Button.h>
15 #include <CardLayout.h>
16 #include <Catalog.h>
17 #include <ColumnListView.h>
18 #include <Font.h>
19 #include <GridView.h>
20 #include <LayoutBuilder.h>
21 #include <LayoutUtils.h>
22 #include <LocaleRoster.h>
23 #include <Message.h>
24 #include <OutlineListView.h>
25 #include <ScrollView.h>
26 #include <SpaceLayoutItem.h>
27 #include <StatusBar.h>
28 #include <StringView.h>
29 #include <TabView.h>
30 #include <Url.h>
32 #include <package/hpkg/PackageReader.h>
33 #include <package/hpkg/NoErrorOutput.h>
34 #include <package/hpkg/PackageContentHandler.h>
35 #include <package/hpkg/PackageEntry.h>
37 #include "BitmapButton.h"
38 #include "BitmapView.h"
39 #include "LinkView.h"
40 #include "LinkedBitmapView.h"
41 #include "MarkupTextView.h"
42 #include "MessagePackageListener.h"
43 #include "PackageActionHandler.h"
44 #include "PackageContentsView.h"
45 #include "PackageInfo.h"
46 #include "PackageManager.h"
47 #include "RatingView.h"
48 #include "ScrollableGroupView.h"
49 #include "TextView.h"
52 #undef B_TRANSLATION_CONTEXT
53 #define B_TRANSLATION_CONTEXT "PackageInfoView"
56 static const rgb_color kLightBlack = (rgb_color) { 60, 60, 60, 255 };
57 static const float kContentTint = (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0f;
60 //! Layouts the scrollbar so it looks nice with no border and the document
61 // window look.
62 class CustomScrollView : public BScrollView {
63 public:
64 CustomScrollView(const char* name, BView* target)
66 BScrollView(name, target, 0, false, true, B_NO_BORDER)
70 virtual void DoLayout()
72 BRect innerFrame = Bounds();
73 innerFrame.right -= B_V_SCROLL_BAR_WIDTH + 1;
75 BView* target = Target();
76 if (target != NULL) {
77 Target()->MoveTo(innerFrame.left, innerFrame.top);
78 Target()->ResizeTo(innerFrame.Width(), innerFrame.Height());
81 BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
82 if (scrollBar != NULL) {
83 BRect rect = innerFrame;
84 rect.left = rect.right + 1;
85 rect.right = rect.left + B_V_SCROLL_BAR_WIDTH;
86 rect.bottom -= B_H_SCROLL_BAR_HEIGHT;
88 scrollBar->MoveTo(rect.left, rect.top);
89 scrollBar->ResizeTo(rect.Width(), rect.Height());
95 class RatingsScrollView : public CustomScrollView {
96 public:
97 RatingsScrollView(const char* name, BView* target)
99 CustomScrollView(name, target)
103 virtual void DoLayout()
105 CustomScrollView::DoLayout();
107 BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
108 BView* target = Target();
109 if (target != NULL && scrollBar != NULL) {
110 // Set the scroll steps
111 BView* item = target->ChildAt(0);
112 if (item != NULL) {
113 scrollBar->SetSteps(item->MinSize().height + 1,
114 item->MinSize().height + 1);
121 // #pragma mark - rating stats
124 class DiagramBarView : public BView {
125 public:
126 DiagramBarView()
128 BView("diagram bar view", B_WILL_DRAW),
129 fValue(0.0f)
131 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
132 SetHighUIColor(B_CONTROL_MARK_COLOR);
135 virtual ~DiagramBarView()
139 virtual void AttachedToWindow()
143 virtual void Draw(BRect updateRect)
145 FillRect(updateRect, B_SOLID_LOW);
147 if (fValue <= 0.0f)
148 return;
150 BRect rect(Bounds());
151 rect.right = ceilf(rect.left + fValue * rect.Width());
153 FillRect(rect, B_SOLID_HIGH);
156 virtual BSize MinSize()
158 return BSize(64, 10);
161 virtual BSize PreferredSize()
163 return MinSize();
166 virtual BSize MaxSize()
168 return BSize(64, 10);
171 void SetValue(float value)
173 if (fValue != value) {
174 fValue = value;
175 Invalidate();
179 private:
180 float fValue;
184 // #pragma mark - TitleView
187 enum {
188 MSG_PACKAGE_ACTION = 'pkga',
189 MSG_MOUSE_ENTERED_RATING = 'menr',
190 MSG_MOUSE_EXITED_RATING = 'mexr',
194 class TransitReportingButton : public BButton {
195 public:
196 TransitReportingButton(const char* name, const char* label,
197 BMessage* message)
199 BButton(name, label, message),
200 fTransitMessage(NULL)
204 virtual ~TransitReportingButton()
206 SetTransitMessage(NULL);
209 virtual void MouseMoved(BPoint point, uint32 transit,
210 const BMessage* dragMessage)
212 BButton::MouseMoved(point, transit, dragMessage);
214 if (fTransitMessage != NULL && transit == B_EXITED_VIEW)
215 Invoke(fTransitMessage);
218 void SetTransitMessage(BMessage* message)
220 if (fTransitMessage != message) {
221 delete fTransitMessage;
222 fTransitMessage = message;
226 private:
227 BMessage* fTransitMessage;
231 class TransitReportingRatingView : public RatingView, public BInvoker {
232 public:
233 TransitReportingRatingView(BMessage* transitMessage)
235 RatingView("package rating view"),
236 fTransitMessage(transitMessage)
240 virtual ~TransitReportingRatingView()
242 delete fTransitMessage;
245 virtual void MouseMoved(BPoint point, uint32 transit,
246 const BMessage* dragMessage)
248 RatingView::MouseMoved(point, transit, dragMessage);
250 if (fTransitMessage != NULL && transit == B_ENTERED_VIEW)
251 Invoke(fTransitMessage);
254 private:
255 BMessage* fTransitMessage;
259 class TitleView : public BGroupView {
260 public:
261 TitleView()
263 BGroupView("title view", B_HORIZONTAL)
265 fIconView = new BitmapView("package icon view");
266 fTitleView = new BStringView("package title view", "");
267 fPublisherView = new BStringView("package publisher view", "");
269 // Title font
270 BFont font;
271 GetFont(&font);
272 font_family family;
273 font_style style;
274 font.SetSize(ceilf(font.Size() * 1.5f));
275 font.GetFamilyAndStyle(&family, &style);
276 font.SetFamilyAndStyle(family, "Bold");
277 fTitleView->SetFont(&font);
279 // Publisher font
280 GetFont(&font);
281 font.SetSize(std::max(9.0f, floorf(font.Size() * 0.92f)));
282 font.SetFamilyAndStyle(family, "Italic");
283 fPublisherView->SetFont(&font);
284 fPublisherView->SetHighColor(kLightBlack);
286 // slightly bigger font
287 GetFont(&font);
288 font.SetSize(ceilf(font.Size() * 1.2f));
290 // Version info
291 fVersionInfo = new BStringView("package version info", "");
292 fVersionInfo->SetFont(&font);
293 fVersionInfo->SetHighColor(kLightBlack);
295 // Rating view
296 fRatingView = new TransitReportingRatingView(
297 new BMessage(MSG_MOUSE_ENTERED_RATING));
299 fAvgRating = new BStringView("package average rating", "");
300 fAvgRating->SetFont(&font);
301 fAvgRating->SetHighColor(kLightBlack);
303 fVoteInfo = new BStringView("package vote info", "");
304 // small font
305 GetFont(&font);
306 font.SetSize(std::max(9.0f, floorf(font.Size() * 0.85f)));
307 fVoteInfo->SetFont(&font);
308 fVoteInfo->SetHighColor(kLightBlack);
310 // Rate button
311 fRateButton = new TransitReportingButton("rate",
312 B_TRANSLATE("Rate package" B_UTF8_ELLIPSIS),
313 new BMessage(MSG_RATE_PACKAGE));
314 fRateButton->SetTransitMessage(new BMessage(MSG_MOUSE_EXITED_RATING));
315 fRateButton->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
316 B_ALIGN_VERTICAL_CENTER));
318 // Rating group
319 BView* ratingStack = new BView("rating stack", 0);
320 fRatingLayout = new BCardLayout();
321 ratingStack->SetLayout(fRatingLayout);
322 ratingStack->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
323 ratingStack->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
325 BGroupView* ratingGroup = new BGroupView(B_HORIZONTAL,
326 B_USE_SMALL_SPACING);
327 BLayoutBuilder::Group<>(ratingGroup)
328 .Add(fRatingView)
329 .Add(fAvgRating)
330 .Add(fVoteInfo)
333 ratingStack->AddChild(ratingGroup);
334 ratingStack->AddChild(fRateButton);
335 fRatingLayout->SetVisibleItem((int32)0);
337 BLayoutBuilder::Group<>(this)
338 .Add(fIconView)
339 .AddGroup(B_VERTICAL, 1.0f, 2.2f)
340 .Add(fTitleView)
341 .Add(fPublisherView)
342 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
343 .End()
344 .AddGlue(0.1f)
345 .Add(ratingStack, 0.8f)
346 .AddGlue(0.2f)
347 .AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING, 2.0f)
348 .Add(fVersionInfo)
349 .AddGlue()
350 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
351 .End()
354 Clear();
357 virtual ~TitleView()
361 virtual void AttachedToWindow()
363 fRateButton->SetTarget(this);
364 fRatingView->SetTarget(this);
367 virtual void MessageReceived(BMessage* message)
369 switch (message->what) {
370 case MSG_RATE_PACKAGE:
371 // Forward to window (The button has us as target so
372 // we receive the message below.)
373 Window()->PostMessage(MSG_RATE_PACKAGE);
374 break;
376 case MSG_MOUSE_ENTERED_RATING:
377 fRatingLayout->SetVisibleItem(1);
378 break;
380 case MSG_MOUSE_EXITED_RATING:
381 fRatingLayout->SetVisibleItem((int32)0);
382 break;
386 void SetPackage(const PackageInfo& package)
388 if (package.Icon().Get() != NULL)
389 fIconView->SetBitmap(package.Icon(), SharedBitmap::SIZE_32);
390 else
391 fIconView->UnsetBitmap();
393 fTitleView->SetText(package.Title());
395 BString publisher = package.Publisher().Name();
396 fPublisherView->SetText(publisher);
398 BString version = B_TRANSLATE("%Version%");
399 version.ReplaceAll("%Version%", package.Version().ToString());
400 fVersionInfo->SetText(version);
402 RatingSummary ratingSummary = package.CalculateRatingSummary();
404 fRatingView->SetRating(ratingSummary.averageRating);
406 if (ratingSummary.ratingCount > 0) {
407 BString avgRating;
408 avgRating.SetToFormat("%.1f", ratingSummary.averageRating);
409 fAvgRating->SetText(avgRating);
411 BString votes;
412 votes.SetToFormat("%d", ratingSummary.ratingCount);
414 BString voteInfo(B_TRANSLATE("(%Votes%)"));
415 voteInfo.ReplaceAll("%Votes%", votes);
417 fVoteInfo->SetText(voteInfo);
418 } else {
419 fAvgRating->SetText("");
420 fVoteInfo->SetText(B_TRANSLATE("n/a"));
423 InvalidateLayout();
424 Invalidate();
427 void Clear()
429 fIconView->UnsetBitmap();
430 fTitleView->SetText("");
431 fPublisherView->SetText("");
432 fVersionInfo->SetText("");
433 fRatingView->SetRating(-1.0f);
434 fAvgRating->SetText("");
435 fVoteInfo->SetText("");
438 private:
439 BitmapView* fIconView;
441 BStringView* fTitleView;
442 BStringView* fPublisherView;
444 BStringView* fVersionInfo;
446 BCardLayout* fRatingLayout;
448 TransitReportingRatingView* fRatingView;
449 BStringView* fAvgRating;
450 BStringView* fVoteInfo;
452 TransitReportingButton* fRateButton;
456 // #pragma mark - PackageActionView
459 class PackageActionView : public BView {
460 public:
461 PackageActionView(PackageActionHandler* handler)
463 BView("about view", B_WILL_DRAW),
464 fLayout(new BGroupLayout(B_HORIZONTAL)),
465 fPackageActionHandler(handler),
466 fStatusLabel(NULL),
467 fStatusBar(NULL)
469 SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
471 SetLayout(fLayout);
472 fLayout->AddItem(BSpaceLayoutItem::CreateGlue());
475 virtual ~PackageActionView()
477 Clear();
480 virtual void MessageReceived(BMessage* message)
482 switch (message->what) {
483 case MSG_PACKAGE_ACTION:
484 _RunPackageAction(message);
485 break;
487 default:
488 BView::MessageReceived(message);
489 break;
493 void SetPackage(const PackageInfo& package)
495 if (package.State() == DOWNLOADING) {
496 AdoptDownloadProgress(package);
497 } else {
498 AdoptActions(package);
503 void AdoptActions(const PackageInfo& package)
505 PackageManager manager(
506 BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_HOME);
508 // TODO: if the given package is either a system package
509 // or a system dependency, show a message indicating that status
510 // so the user knows why no actions are presented
511 PackageActionList actions = manager.GetPackageActions(
512 const_cast<PackageInfo*>(&package),
513 fPackageActionHandler->GetModel());
515 bool clearNeeded = fStatusBar != NULL;
516 if (!clearNeeded) {
517 if (actions.CountItems() != fPackageActions.CountItems())
518 clearNeeded = true;
519 else {
520 for (int32 i = 0; i < actions.CountItems(); i++) {
521 if (actions.ItemAtFast(i)->Type()
522 != fPackageActions.ItemAtFast(i)->Type()) {
523 clearNeeded = true;
524 break;
530 fPackageActions = actions;
531 if (!clearNeeded && fButtons.CountItems() == actions.CountItems()) {
532 int32 index = 0;
533 for (int32 i = fPackageActions.CountItems() - 1; i >= 0; i--) {
534 const PackageActionRef& action = fPackageActions.ItemAtFast(i);
535 BButton* button = (BButton*)fButtons.ItemAtFast(index++);
536 button->SetLabel(action->Label());
538 return;
541 Clear();
543 // Add Buttons in reverse action order
544 for (int32 i = fPackageActions.CountItems() - 1; i >= 0; i--) {
545 const PackageActionRef& action = fPackageActions.ItemAtFast(i);
547 BMessage* message = new BMessage(MSG_PACKAGE_ACTION);
548 message->AddInt32("index", i);
550 BButton* button = new BButton(action->Label(), message);
551 fLayout->AddView(button);
552 button->SetTarget(this);
554 fButtons.AddItem(button);
558 void AdoptDownloadProgress(const PackageInfo& package)
560 if (fButtons.CountItems() > 0)
561 Clear();
563 if (fStatusBar == NULL) {
564 fStatusLabel = new BStringView("progress label",
565 B_TRANSLATE("Downloading:"));
566 fLayout->AddView(fStatusLabel);
568 fStatusBar = new BStatusBar("progress");
569 fStatusBar->SetMaxValue(100.0);
570 fStatusBar->SetExplicitMinSize(
571 BSize(StringWidth("XXX") * 5, B_SIZE_UNSET));
573 fLayout->AddView(fStatusBar);
576 fStatusBar->SetTo(package.DownloadProgress() * 100.0);
579 void Clear()
581 for (int32 i = fButtons.CountItems() - 1; i >= 0; i--) {
582 BButton* button = (BButton*)fButtons.ItemAtFast(i);
583 button->RemoveSelf();
584 delete button;
586 fButtons.MakeEmpty();
588 if (fStatusBar != NULL) {
589 fStatusBar->RemoveSelf();
590 delete fStatusBar;
591 fStatusBar = NULL;
593 if (fStatusLabel != NULL) {
594 fStatusLabel->RemoveSelf();
595 delete fStatusLabel;
596 fStatusLabel = NULL;
600 private:
601 void _RunPackageAction(BMessage* message)
603 int32 index;
604 if (message->FindInt32("index", &index) != B_OK)
605 return;
607 const PackageActionRef& action = fPackageActions.ItemAt(index);
608 if (action.Get() == NULL)
609 return;
611 PackageActionList actions;
612 actions.Add(action);
613 status_t result
614 = fPackageActionHandler->SchedulePackageActions(actions);
616 if (result != B_OK) {
617 fprintf(stderr, "Failed to schedule action: "
618 "%s '%s': %s\n", action->Label(),
619 action->Package()->Name().String(),
620 strerror(result));
621 BString message(B_TRANSLATE("The package action "
622 "could not be scheduled: %Error%"));
623 message.ReplaceAll("%Error%", strerror(result));
624 BAlert* alert = new(std::nothrow) BAlert(
625 B_TRANSLATE("Package action failed"),
626 message, B_TRANSLATE("OK"), NULL, NULL,
627 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
628 if (alert != NULL)
629 alert->Go();
630 } else {
631 // Find the button for this action and disable it.
632 // Actually search the matching button instead of just using
633 // fButtons.ItemAt((fButtons.CountItems() - 1) - index) to
634 // make this robust against for example changing the order of
635 // buttons from right -> left to left -> right...
636 for (int32 i = 0; i < fButtons.CountItems(); i++) {
637 BButton* button = (BButton*)fButtons.ItemAt(index);
638 if (button == NULL)
639 continue;
640 BMessage* buttonMessage = button->Message();
641 if (buttonMessage == NULL)
642 continue;
643 int32 buttonIndex;
644 if (buttonMessage->FindInt32("index", &buttonIndex) != B_OK)
645 continue;
646 if (buttonIndex == index) {
647 button->SetEnabled(false);
648 break;
654 private:
655 BGroupLayout* fLayout;
656 PackageActionList fPackageActions;
657 PackageActionHandler* fPackageActionHandler;
658 BList fButtons;
660 BStringView* fStatusLabel;
661 BStatusBar* fStatusBar;
665 // #pragma mark - AboutView
668 enum {
669 MSG_EMAIL_PUBLISHER = 'emlp',
670 MSG_VISIT_PUBLISHER_WEBSITE = 'vpws',
674 class AboutView : public BView {
675 public:
676 AboutView()
678 BView("about view", 0),
679 fEmailIcon("text/x-email"),
680 fWebsiteIcon("text/html")
682 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
684 fDescriptionView = new MarkupTextView("description view");
685 fDescriptionView->SetViewUIColor(ViewUIColor(), kContentTint);
686 fDescriptionView->SetInsets(be_plain_font->Size());
688 BScrollView* scrollView = new CustomScrollView(
689 "description scroll view", fDescriptionView);
691 BFont smallFont;
692 GetFont(&smallFont);
693 smallFont.SetSize(std::max(9.0f, ceilf(smallFont.Size() * 0.85f)));
695 // TODO: Clicking the screen shot view should open ShowImage with the
696 // the screen shot. This could be done by writing the screen shot to
697 // a temporary folder, launching ShowImage to display it, and writing
698 // all other screenshots associated with the package to the same folder
699 // so the user can use the ShowImage navigation to view the other
700 // screenshots.
701 fScreenshotView = new LinkedBitmapView("screenshot view",
702 new BMessage(MSG_SHOW_SCREENSHOT));
703 fScreenshotView->SetExplicitMinSize(BSize(64.0f, 64.0f));
704 fScreenshotView->SetExplicitMaxSize(
705 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
706 fScreenshotView->SetExplicitAlignment(
707 BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP));
709 fEmailIconView = new BitmapView("email icon view");
710 fEmailLinkView = new LinkView("email link view", "",
711 new BMessage(MSG_EMAIL_PUBLISHER));
712 fEmailLinkView->SetFont(&smallFont);
714 fWebsiteIconView = new BitmapView("website icon view");
715 fWebsiteLinkView = new LinkView("website link view", "",
716 new BMessage(MSG_VISIT_PUBLISHER_WEBSITE));
717 fWebsiteLinkView->SetFont(&smallFont);
719 BGroupView* leftGroup = new BGroupView(B_VERTICAL,
720 B_USE_DEFAULT_SPACING);
722 fScreenshotView->SetViewUIColor(ViewUIColor(), kContentTint);
723 fEmailLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
724 fWebsiteLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
726 BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f)
727 // .Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f))
728 .AddGroup(leftGroup, 1.0f)
729 .Add(fScreenshotView)
730 .AddGroup(B_HORIZONTAL)
731 .AddGrid(B_USE_HALF_ITEM_SPACING, B_USE_HALF_ITEM_SPACING)
732 .Add(fEmailIconView, 0, 0)
733 .Add(fEmailLinkView, 1, 0)
734 .Add(fWebsiteIconView, 0, 1)
735 .Add(fWebsiteLinkView, 1, 1)
736 .End()
737 .End()
738 .SetInsets(B_USE_DEFAULT_SPACING)
739 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
740 .End()
741 .Add(scrollView, 2.0f)
743 .SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED))
744 .SetInsets(0.0f, -1.0f, -1.0f, -1.0f)
748 virtual ~AboutView()
750 Clear();
753 virtual void AttachedToWindow()
755 fScreenshotView->SetTarget(this);
756 fEmailLinkView->SetTarget(this);
757 fWebsiteLinkView->SetTarget(this);
760 virtual void AllAttached()
762 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
764 for (int32 index = 0; index < CountChildren(); ++index)
765 ChildAt(index)->AdoptParentColors();
768 virtual void MessageReceived(BMessage* message)
770 switch (message->what) {
771 case MSG_SHOW_SCREENSHOT:
773 // Forward to window for now
774 Window()->PostMessage(message, Window());
775 break;
778 case MSG_EMAIL_PUBLISHER:
780 // TODO: Implement. If memory serves, there is a
781 // standard command line interface which mail apps should
782 // support, i.e. to open a compose window with the TO: field
783 // already set.
784 break;
787 case MSG_VISIT_PUBLISHER_WEBSITE:
789 BUrl url(fWebsiteLinkView->Text());
790 url.OpenWithPreferredApplication();
791 break;
794 default:
795 BView::MessageReceived(message);
796 break;
800 void SetPackage(const PackageInfo& package)
802 fDescriptionView->SetText(package.ShortDescription(),
803 package.FullDescription());
805 fEmailIconView->SetBitmap(&fEmailIcon, SharedBitmap::SIZE_16);
806 _SetContactInfo(fEmailLinkView, package.Publisher().Email());
807 fWebsiteIconView->SetBitmap(&fWebsiteIcon, SharedBitmap::SIZE_16);
808 _SetContactInfo(fWebsiteLinkView, package.Publisher().Website());
810 bool hasScreenshot = false;
811 const BitmapList& screenShots = package.Screenshots();
812 if (screenShots.CountItems() > 0) {
813 const BitmapRef& bitmapRef = screenShots.ItemAtFast(0);
814 if (bitmapRef.Get() != NULL) {
815 hasScreenshot = true;
816 fScreenshotView->SetBitmap(bitmapRef);
820 if (!hasScreenshot)
821 fScreenshotView->UnsetBitmap();
823 fScreenshotView->SetEnabled(hasScreenshot);
826 void Clear()
828 fDescriptionView->SetText("");
829 fEmailIconView->UnsetBitmap();
830 fEmailLinkView->SetText("");
831 fWebsiteIconView->UnsetBitmap();
832 fWebsiteLinkView->SetText("");
834 fScreenshotView->UnsetBitmap();
835 fScreenshotView->SetEnabled(false);
838 private:
839 void _SetContactInfo(LinkView* view, const BString& string)
841 if (string.Length() > 0) {
842 view->SetText(string);
843 view->SetEnabled(true);
844 } else {
845 view->SetText(B_TRANSLATE("<no info>"));
846 view->SetEnabled(false);
850 private:
851 MarkupTextView* fDescriptionView;
853 LinkedBitmapView* fScreenshotView;
855 SharedBitmap fEmailIcon;
856 BitmapView* fEmailIconView;
857 LinkView* fEmailLinkView;
859 SharedBitmap fWebsiteIcon;
860 BitmapView* fWebsiteIconView;
861 LinkView* fWebsiteLinkView;
865 // #pragma mark - UserRatingsView
868 class RatingItemView : public BGroupView {
869 public:
870 RatingItemView(const UserRating& rating, const BitmapRef& voteUpIcon,
871 const BitmapRef& voteDownIcon)
873 BGroupView(B_HORIZONTAL, 0.0f)
875 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
877 fAvatarView = new BitmapView("avatar view");
878 if (rating.User().Avatar().Get() != NULL) {
879 fAvatarView->SetBitmap(rating.User().Avatar(),
880 SharedBitmap::SIZE_16);
882 fAvatarView->SetExplicitMinSize(BSize(16.0f, 16.0f));
884 fNameView = new BStringView("user name", rating.User().NickName());
886 BFont nameFont(be_bold_font);
887 nameFont.SetSize(std::max(9.0f, floorf(nameFont.Size() * 0.9f)));
888 fNameView->SetFont(&nameFont);
889 fNameView->SetExplicitMaxSize(
890 BSize(nameFont.StringWidth("xxxxxxxxxxxxxxxxxxxxxx"), B_SIZE_UNSET));
892 fRatingView = new RatingView("package rating view");
893 fRatingView->SetRating(rating.Rating());
895 BString ratingLabel;
896 if (rating.Rating() >= 0.0f)
897 ratingLabel.SetToFormat("%.1f", rating.Rating());
898 fRatingLabelView = new BStringView("rating label", ratingLabel);
900 BString versionLabel(B_TRANSLATE("for %Version%"));
901 versionLabel.ReplaceAll("%Version%", rating.PackageVersion());
902 fPackageVersionView = new BStringView("package version",
903 versionLabel);
904 BFont versionFont(be_plain_font);
905 versionFont.SetSize(std::max(9.0f, floorf(versionFont.Size() * 0.85f)));
906 fPackageVersionView->SetFont(&versionFont);
908 // TODO: User rating IDs to identify which rating to vote up or down
909 // BMessage* voteUpMessage = new BMessage(MSG_VOTE_UP);
910 // voteUpMessage->AddInt32("rating id", -1);
911 // BMessage* voteDownMessage = new BMessage(MSG_VOTE_DOWN);
912 // voteDownMessage->AddInt32("rating id", -1);
914 // fVoteUpIconView = new BitmapButton("vote up icon", voteUpMessage);
915 // fUpVoteCountView = new BStringView("up vote count", "");
916 // fVoteDownIconView = new BitmapButton("vote down icon", voteDownMessage);
917 // fDownVoteCountView = new BStringView("up vote count", "");
919 // fVoteUpIconView->SetBitmap(voteUpIcon, SharedBitmap::SIZE_16);
920 // fVoteDownIconView->SetBitmap(voteDownIcon, SharedBitmap::SIZE_16);
922 // fUpVoteCountView->SetFont(&versionFont);
923 // fUpVoteCountView->SetHighColor(kLightBlack);
924 // fDownVoteCountView->SetFont(&versionFont);
925 // fDownVoteCountView->SetHighColor(kLightBlack);
927 // BString voteCountLabel;
928 // voteCountLabel.SetToFormat("%" B_PRId32, rating.UpVotes());
929 // fUpVoteCountView->SetText(voteCountLabel);
930 // voteCountLabel.SetToFormat("%" B_PRId32, rating.DownVotes());
931 // fDownVoteCountView->SetText(voteCountLabel);
933 fTextView = new TextView("rating text");
934 ParagraphStyle paragraphStyle(fTextView->ParagraphStyle());
935 paragraphStyle.SetJustify(true);
936 fTextView->SetParagraphStyle(paragraphStyle);
938 fTextView->SetText(rating.Comment());
940 BLayoutBuilder::Group<>(this)
941 .Add(fAvatarView, 0.2f)
942 .AddGroup(B_VERTICAL, 0.0f)
943 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
944 .Add(fNameView)
945 .Add(fRatingView)
946 .Add(fRatingLabelView)
947 .AddGlue(0.1f)
948 .Add(fPackageVersionView)
949 .AddGlue(5.0f)
950 // .AddGroup(B_HORIZONTAL, 0.0f, 0.0f)
951 // .Add(fVoteUpIconView)
952 // .Add(fUpVoteCountView)
953 // .AddStrut(B_USE_HALF_ITEM_SPACING)
954 // .Add(fVoteDownIconView)
955 // .Add(fDownVoteCountView)
956 // .End()
957 .End()
958 .Add(fTextView)
959 .End()
960 .SetInsets(B_USE_DEFAULT_SPACING)
963 SetFlags(Flags() | B_WILL_DRAW);
966 void AllAttached()
968 for (int32 index = 0; index < CountChildren(); ++index)
969 ChildAt(index)->AdoptParentColors();
972 void Draw(BRect rect)
974 rgb_color color = mix_color(ViewColor(), ui_color(B_PANEL_TEXT_COLOR), 64);
975 SetHighColor(color);
976 StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom());
979 private:
980 BitmapView* fAvatarView;
981 BStringView* fNameView;
982 RatingView* fRatingView;
983 BStringView* fRatingLabelView;
984 BStringView* fPackageVersionView;
986 // BitmapView* fVoteUpIconView;
987 // BStringView* fUpVoteCountView;
988 // BitmapView* fVoteDownIconView;
989 // BStringView* fDownVoteCountView;
991 TextView* fTextView;
995 class RatingSummaryView : public BGridView {
996 public:
997 RatingSummaryView()
999 BGridView("rating summary view", B_USE_HALF_ITEM_SPACING, 0.0f)
1001 float tint = kContentTint - 0.1;
1002 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, tint);
1004 BLayoutBuilder::Grid<> layoutBuilder(this);
1006 BFont smallFont;
1007 GetFont(&smallFont);
1008 smallFont.SetSize(std::max(9.0f, floorf(smallFont.Size() * 0.85f)));
1010 for (int32 i = 0; i < 5; i++) {
1011 BString label;
1012 label.SetToFormat("%" B_PRId32, 5 - i);
1013 fLabelViews[i] = new BStringView("", label);
1014 fLabelViews[i]->SetFont(&smallFont);
1015 fLabelViews[i]->SetViewUIColor(ViewUIColor(), tint);
1016 layoutBuilder.Add(fLabelViews[i], 0, i);
1018 fDiagramBarViews[i] = new DiagramBarView();
1019 layoutBuilder.Add(fDiagramBarViews[i], 1, i);
1021 fCountViews[i] = new BStringView("", "");
1022 fCountViews[i]->SetFont(&smallFont);
1023 fCountViews[i]->SetViewUIColor(ViewUIColor(), tint);
1024 fCountViews[i]->SetAlignment(B_ALIGN_RIGHT);
1025 layoutBuilder.Add(fCountViews[i], 2, i);
1028 layoutBuilder.SetInsets(5);
1031 void SetToSummary(const RatingSummary& summary) {
1032 for (int32 i = 0; i < 5; i++) {
1033 int32 count = summary.ratingCountByStar[4 - i];
1035 BString label;
1036 label.SetToFormat("%" B_PRId32, count);
1037 fCountViews[i]->SetText(label);
1039 if (summary.ratingCount > 0) {
1040 fDiagramBarViews[i]->SetValue(
1041 (float)count / summary.ratingCount);
1042 } else
1043 fDiagramBarViews[i]->SetValue(0.0f);
1047 void Clear() {
1048 for (int32 i = 0; i < 5; i++) {
1049 fCountViews[i]->SetText("");
1050 fDiagramBarViews[i]->SetValue(0.0f);
1054 private:
1055 BStringView* fLabelViews[5];
1056 DiagramBarView* fDiagramBarViews[5];
1057 BStringView* fCountViews[5];
1061 class UserRatingsView : public BGroupView {
1062 public:
1063 UserRatingsView()
1065 BGroupView("package ratings view", B_HORIZONTAL),
1066 fThumbsUpIcon(BitmapRef(new SharedBitmap(502), true)),
1067 fThumbsDownIcon(BitmapRef(new SharedBitmap(503), true))
1069 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1071 fRatingSummaryView = new RatingSummaryView();
1073 ScrollableGroupView* ratingsContainerView = new ScrollableGroupView();
1074 ratingsContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR,
1075 kContentTint);
1076 fRatingContainerLayout = ratingsContainerView->GroupLayout();
1078 BScrollView* scrollView = new RatingsScrollView(
1079 "ratings scroll view", ratingsContainerView);
1081 BLayoutBuilder::Group<>(this)
1082 .AddGroup(B_VERTICAL)
1083 .Add(fRatingSummaryView, 0.0f)
1084 .AddGlue()
1085 .SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f)
1086 .End()
1087 .Add(scrollView, 1.0f)
1088 .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
1091 _InitPreferredLanguages();
1094 virtual ~UserRatingsView()
1096 Clear();
1099 void SetPackage(const PackageInfo& package)
1101 ClearRatings();
1103 // TODO: Re-use rating summary already used for TitleView...
1104 fRatingSummaryView->SetToSummary(package.CalculateRatingSummary());
1106 const UserRatingList& userRatings = package.UserRatings();
1108 int count = userRatings.CountItems();
1109 if (count == 0) {
1110 BStringView* noRatingsView = new BStringView("no ratings",
1111 B_TRANSLATE("No user ratings available."));
1112 noRatingsView->SetViewUIColor(ViewUIColor(), kContentTint);
1113 noRatingsView->SetAlignment(B_ALIGN_CENTER);
1114 noRatingsView->SetHighColor(disable_color(ui_color(B_PANEL_TEXT_COLOR),
1115 ViewColor()));
1116 noRatingsView->SetExplicitMaxSize(
1117 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
1118 fRatingContainerLayout->AddView(0, noRatingsView);
1119 return;
1122 // TODO: Sort by age or usefullness rating
1123 for (int i = count - 1; i >= 0; i--) {
1124 const UserRating& rating = userRatings.ItemAtFast(i);
1125 // Prevent ratings from showing that have a comment which
1126 // is in another language
1127 if (!rating.Comment().IsEmpty()
1128 && fPreferredLanguages.CountItems() > 0
1129 && !fPreferredLanguages.Contains(rating.Language())) {
1130 continue;
1132 RatingItemView* view = new RatingItemView(rating, fThumbsUpIcon,
1133 fThumbsDownIcon);
1134 fRatingContainerLayout->AddView(0, view);
1138 void Clear()
1140 fRatingSummaryView->Clear();
1141 ClearRatings();
1144 void ClearRatings()
1146 for (int32 i = fRatingContainerLayout->CountItems() - 1;
1147 BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) {
1148 BView* view = dynamic_cast<RatingItemView*>(item->View());
1149 if (view == NULL)
1150 view = dynamic_cast<BStringView*>(item->View());
1151 if (view != NULL) {
1152 view->RemoveSelf();
1153 delete view;
1158 private:
1159 void _InitPreferredLanguages()
1161 fPreferredLanguages.Clear();
1163 BLocaleRoster* localeRoster = BLocaleRoster::Default();
1164 if (localeRoster == NULL)
1165 return;
1167 BMessage preferredLanguages;
1168 if (localeRoster->GetPreferredLanguages(&preferredLanguages) != B_OK)
1169 return;
1171 BString language;
1172 int32 index = 0;
1173 while (preferredLanguages.FindString("language", index++,
1174 &language) == B_OK) {
1175 BString languageCode;
1176 language.CopyInto(languageCode, 0, 2);
1177 fPreferredLanguages.Add(languageCode);
1181 private:
1182 BGroupLayout* fRatingContainerLayout;
1183 RatingSummaryView* fRatingSummaryView;
1184 BitmapRef fThumbsUpIcon;
1185 BitmapRef fThumbsDownIcon;
1186 StringList fPreferredLanguages;
1190 // #pragma mark - ContentsView
1193 class ContentsView : public BGroupView {
1194 public:
1195 ContentsView()
1197 BGroupView("package contents view", B_HORIZONTAL)
1199 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1201 fPackageContents = new PackageContentsView("contents_list");
1202 AddChild(fPackageContents);
1206 virtual ~ContentsView()
1210 virtual void Draw(BRect updateRect)
1214 void SetPackage(const PackageInfoRef& package)
1216 fPackageContents->SetPackage(package);
1219 void Clear()
1221 fPackageContents->Clear();
1224 private:
1225 PackageContentsView* fPackageContents;
1229 // #pragma mark - ChangelogView
1232 class ChangelogView : public BGroupView {
1233 public:
1234 ChangelogView()
1236 BGroupView("package changelog view", B_HORIZONTAL)
1238 SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1240 fTextView = new MarkupTextView("changelog view");
1241 fTextView->SetLowUIColor(ViewUIColor());
1242 fTextView->SetInsets(be_plain_font->Size());
1244 BScrollView* scrollView = new CustomScrollView(
1245 "changelog scroll view", fTextView);
1247 BLayoutBuilder::Group<>(this)
1248 .Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f))
1249 .Add(scrollView, 1.0f)
1250 .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
1254 virtual ~ChangelogView()
1258 virtual void Draw(BRect updateRect)
1262 void SetPackage(const PackageInfo& package)
1264 const BString& changelog = package.Changelog();
1265 if (changelog.Length() > 0)
1266 fTextView->SetText(changelog);
1267 else
1268 fTextView->SetDisabledText(B_TRANSLATE("No changelog available."));
1271 void Clear()
1273 fTextView->SetText("");
1276 private:
1277 MarkupTextView* fTextView;
1281 // #pragma mark - PagesView
1284 class PagesView : public BTabView {
1285 public:
1286 PagesView()
1288 BTabView("pages view", B_WIDTH_FROM_WIDEST),
1289 fLayout(new BCardLayout())
1291 SetBorder(B_NO_BORDER);
1293 fAboutView = new AboutView();
1294 fUserRatingsView = new UserRatingsView();
1295 fChangelogView = new ChangelogView();
1296 fContentsView = new ContentsView();
1298 AddTab(fAboutView);
1299 AddTab(fUserRatingsView);
1300 AddTab(fChangelogView);
1301 AddTab(fContentsView);
1303 TabAt(0)->SetLabel(B_TRANSLATE("About"));
1304 TabAt(1)->SetLabel(B_TRANSLATE("Ratings"));
1305 TabAt(2)->SetLabel(B_TRANSLATE("Changelog"));
1306 TabAt(3)->SetLabel(B_TRANSLATE("Contents"));
1308 Select(0);
1311 virtual ~PagesView()
1313 Clear();
1316 void SetPackage(const PackageInfoRef& package, bool switchToDefaultTab)
1318 if (switchToDefaultTab)
1319 Select(0);
1320 fAboutView->SetPackage(*package.Get());
1321 fUserRatingsView->SetPackage(*package.Get());
1322 fChangelogView->SetPackage(*package.Get());
1323 fContentsView->SetPackage(package);
1326 void Clear()
1328 fAboutView->Clear();
1329 fUserRatingsView->Clear();
1330 fChangelogView->Clear();
1331 fContentsView->Clear();
1334 private:
1335 BCardLayout* fLayout;
1337 AboutView* fAboutView;
1338 UserRatingsView* fUserRatingsView;
1339 ChangelogView* fChangelogView;
1340 ContentsView* fContentsView;
1344 // #pragma mark - PackageInfoView
1347 PackageInfoView::PackageInfoView(BLocker* modelLock,
1348 PackageActionHandler* handler)
1350 BView("package info view", 0),
1351 fModelLock(modelLock),
1352 fPackageListener(new(std::nothrow) OnePackageMessagePackageListener(this))
1354 fCardLayout = new BCardLayout();
1355 SetLayout(fCardLayout);
1357 BGroupView* noPackageCard = new BGroupView("no package card", B_VERTICAL);
1358 AddChild(noPackageCard);
1360 BStringView* noPackageView = new BStringView("no package view",
1361 B_TRANSLATE("Click a package to view information"));
1362 noPackageView->SetHighColor(kLightBlack);
1363 noPackageView->SetExplicitAlignment(BAlignment(
1364 B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
1366 BLayoutBuilder::Group<>(noPackageCard)
1367 .Add(noPackageView)
1368 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED))
1371 BGroupView* packageCard = new BGroupView("package card", B_VERTICAL);
1372 AddChild(packageCard);
1374 fCardLayout->SetVisibleItem((int32)0);
1376 fTitleView = new TitleView();
1377 fPackageActionView = new PackageActionView(handler);
1378 fPackageActionView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
1379 B_SIZE_UNSET));
1380 fPagesView = new PagesView();
1382 BLayoutBuilder::Group<>(packageCard)
1383 .AddGroup(B_HORIZONTAL, 0.0f)
1384 .Add(fTitleView, 6.0f)
1385 .Add(fPackageActionView, 1.0f)
1386 .SetInsets(
1387 B_USE_DEFAULT_SPACING, 0.0f,
1388 B_USE_DEFAULT_SPACING, 0.0f)
1389 .End()
1390 .Add(fPagesView)
1393 Clear();
1397 PackageInfoView::~PackageInfoView()
1399 fPackageListener->SetPackage(PackageInfoRef(NULL));
1400 delete fPackageListener;
1404 void
1405 PackageInfoView::AttachedToWindow()
1410 void
1411 PackageInfoView::MessageReceived(BMessage* message)
1413 switch (message->what) {
1414 case MSG_UPDATE_PACKAGE:
1416 if (fPackageListener->Package().Get() == NULL)
1417 break;
1419 BString name;
1420 uint32 changes;
1421 if (message->FindString("name", &name) != B_OK
1422 || message->FindUInt32("changes", &changes) != B_OK) {
1423 break;
1426 const PackageInfoRef& package = fPackageListener->Package();
1427 if (package->Name() != name)
1428 break;
1430 BAutolock _(fModelLock);
1432 if ((changes & PKG_CHANGED_SUMMARY) != 0
1433 || (changes & PKG_CHANGED_DESCRIPTION) != 0
1434 || (changes & PKG_CHANGED_SCREENSHOTS) != 0
1435 || (changes & PKG_CHANGED_TITLE) != 0
1436 || (changes & PKG_CHANGED_RATINGS) != 0
1437 || (changes & PKG_CHANGED_STATE) != 0
1438 || (changes & PKG_CHANGED_CHANGELOG) != 0) {
1439 fPagesView->SetPackage(package, false);
1442 if ((changes & PKG_CHANGED_TITLE) != 0
1443 || (changes & PKG_CHANGED_RATINGS) != 0) {
1444 fTitleView->SetPackage(*package.Get());
1447 if ((changes & PKG_CHANGED_STATE) != 0) {
1448 fPackageActionView->SetPackage(*package.Get());
1451 break;
1453 default:
1454 BView::MessageReceived(message);
1455 break;
1460 void
1461 PackageInfoView::SetPackage(const PackageInfoRef& packageRef)
1463 BAutolock _(fModelLock);
1465 if (packageRef.Get() == NULL) {
1466 Clear();
1467 return;
1470 bool switchToDefaultTab = true;
1471 if (fPackage == packageRef) {
1472 // When asked to display the already showing package ref,
1473 // don't switch to the default tab.
1474 switchToDefaultTab = false;
1475 } else if (fPackage.Get() != NULL && packageRef.Get() != NULL
1476 && fPackage->Name() == packageRef->Name()) {
1477 // When asked to display a different PackageInfo instance,
1478 // but it has the same package title as the already showing
1479 // instance, this probably means there was a repository
1480 // refresh and we are in fact still requested to show the
1481 // same package as before the refresh.
1482 switchToDefaultTab = false;
1485 const PackageInfo& package = *packageRef.Get();
1487 fTitleView->SetPackage(package);
1488 fPackageActionView->SetPackage(package);
1489 fPagesView->SetPackage(packageRef, switchToDefaultTab);
1491 fCardLayout->SetVisibleItem(1);
1493 fPackageListener->SetPackage(packageRef);
1495 // Set the fPackage reference last, so we keep a reference to the
1496 // previous package before switching all the views to the new package.
1497 // Otherwise the PackageInfo instance may go away because we had the
1498 // last reference. And some of the views, the PackageActionView for
1499 // example, keeps references to stuff from the previous package and
1500 // access it while switching to the new package.
1501 fPackage = packageRef;
1505 void
1506 PackageInfoView::Clear()
1508 BAutolock _(fModelLock);
1510 fTitleView->Clear();
1511 fPackageActionView->Clear();
1512 fPagesView->Clear();
1514 fCardLayout->SetVisibleItem((int32)0);
1516 fPackageListener->SetPackage(PackageInfoRef(NULL));
1518 fPackage.Unset();