2 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
6 #include "PackageInfoView.h"
15 #include <CardLayout.h>
17 #include <ColumnListView.h>
20 #include <LayoutBuilder.h>
21 #include <LayoutUtils.h>
22 #include <LocaleRoster.h>
24 #include <OutlineListView.h>
25 #include <ScrollView.h>
26 #include <SpaceLayoutItem.h>
27 #include <StatusBar.h>
28 #include <StringView.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"
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"
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
62 class CustomScrollView
: public BScrollView
{
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();
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
{
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);
113 scrollBar
->SetSteps(item
->MinSize().height
+ 1,
114 item
->MinSize().height
+ 1);
121 // #pragma mark - rating stats
124 class DiagramBarView
: public BView
{
128 BView("diagram bar view", B_WILL_DRAW
),
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
);
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()
166 virtual BSize
MaxSize()
168 return BSize(64, 10);
171 void SetValue(float value
)
173 if (fValue
!= value
) {
184 // #pragma mark - TitleView
188 MSG_PACKAGE_ACTION
= 'pkga',
189 MSG_MOUSE_ENTERED_RATING
= 'menr',
190 MSG_MOUSE_EXITED_RATING
= 'mexr',
194 class TransitReportingButton
: public BButton
{
196 TransitReportingButton(const char* name
, const char* label
,
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
;
227 BMessage
* fTransitMessage
;
231 class TransitReportingRatingView
: public RatingView
, public BInvoker
{
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
);
255 BMessage
* fTransitMessage
;
259 class TitleView
: public BGroupView
{
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", "");
274 font
.SetSize(ceilf(font
.Size() * 1.5f
));
275 font
.GetFamilyAndStyle(&family
, &style
);
276 font
.SetFamilyAndStyle(family
, "Bold");
277 fTitleView
->SetFont(&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
288 font
.SetSize(ceilf(font
.Size() * 1.2f
));
291 fVersionInfo
= new BStringView("package version info", "");
292 fVersionInfo
->SetFont(&font
);
293 fVersionInfo
->SetHighColor(kLightBlack
);
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", "");
306 font
.SetSize(std::max(9.0f
, floorf(font
.Size() * 0.85f
)));
307 fVoteInfo
->SetFont(&font
);
308 fVoteInfo
->SetHighColor(kLightBlack
);
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
));
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
)
333 ratingStack
->AddChild(ratingGroup
);
334 ratingStack
->AddChild(fRateButton
);
335 fRatingLayout
->SetVisibleItem((int32
)0);
337 BLayoutBuilder::Group
<>(this)
339 .AddGroup(B_VERTICAL
, 1.0f
, 2.2f
)
342 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED
, B_SIZE_UNSET
))
345 .Add(ratingStack
, 0.8f
)
347 .AddGroup(B_HORIZONTAL
, B_USE_SMALL_SPACING
, 2.0f
)
350 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED
, B_SIZE_UNSET
))
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
);
376 case MSG_MOUSE_ENTERED_RATING
:
377 fRatingLayout
->SetVisibleItem(1);
380 case MSG_MOUSE_EXITED_RATING
:
381 fRatingLayout
->SetVisibleItem((int32
)0);
386 void SetPackage(const PackageInfo
& package
)
388 if (package
.Icon().Get() != NULL
)
389 fIconView
->SetBitmap(package
.Icon(), SharedBitmap::SIZE_32
);
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) {
408 avgRating
.SetToFormat("%.1f", ratingSummary
.averageRating
);
409 fAvgRating
->SetText(avgRating
);
412 votes
.SetToFormat("%d", ratingSummary
.ratingCount
);
414 BString
voteInfo(B_TRANSLATE("(%Votes%)"));
415 voteInfo
.ReplaceAll("%Votes%", votes
);
417 fVoteInfo
->SetText(voteInfo
);
419 fAvgRating
->SetText("");
420 fVoteInfo
->SetText(B_TRANSLATE("n/a"));
429 fIconView
->UnsetBitmap();
430 fTitleView
->SetText("");
431 fPublisherView
->SetText("");
432 fVersionInfo
->SetText("");
433 fRatingView
->SetRating(-1.0f
);
434 fAvgRating
->SetText("");
435 fVoteInfo
->SetText("");
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
{
461 PackageActionView(PackageActionHandler
* handler
)
463 BView("about view", B_WILL_DRAW
),
464 fLayout(new BGroupLayout(B_HORIZONTAL
)),
465 fPackageActionHandler(handler
),
469 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
472 fLayout
->AddItem(BSpaceLayoutItem::CreateGlue());
475 virtual ~PackageActionView()
480 virtual void MessageReceived(BMessage
* message
)
482 switch (message
->what
) {
483 case MSG_PACKAGE_ACTION
:
484 _RunPackageAction(message
);
488 BView::MessageReceived(message
);
493 void SetPackage(const PackageInfo
& package
)
495 if (package
.State() == DOWNLOADING
) {
496 AdoptDownloadProgress(package
);
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
;
517 if (actions
.CountItems() != fPackageActions
.CountItems())
520 for (int32 i
= 0; i
< actions
.CountItems(); i
++) {
521 if (actions
.ItemAtFast(i
)->Type()
522 != fPackageActions
.ItemAtFast(i
)->Type()) {
530 fPackageActions
= actions
;
531 if (!clearNeeded
&& fButtons
.CountItems() == actions
.CountItems()) {
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());
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)
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);
581 for (int32 i
= fButtons
.CountItems() - 1; i
>= 0; i
--) {
582 BButton
* button
= (BButton
*)fButtons
.ItemAtFast(i
);
583 button
->RemoveSelf();
586 fButtons
.MakeEmpty();
588 if (fStatusBar
!= NULL
) {
589 fStatusBar
->RemoveSelf();
593 if (fStatusLabel
!= NULL
) {
594 fStatusLabel
->RemoveSelf();
601 void _RunPackageAction(BMessage
* message
)
604 if (message
->FindInt32("index", &index
) != B_OK
)
607 const PackageActionRef
& action
= fPackageActions
.ItemAt(index
);
608 if (action
.Get() == NULL
)
611 PackageActionList actions
;
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(),
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
);
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
);
640 BMessage
* buttonMessage
= button
->Message();
641 if (buttonMessage
== NULL
)
644 if (buttonMessage
->FindInt32("index", &buttonIndex
) != B_OK
)
646 if (buttonIndex
== index
) {
647 button
->SetEnabled(false);
655 BGroupLayout
* fLayout
;
656 PackageActionList fPackageActions
;
657 PackageActionHandler
* fPackageActionHandler
;
660 BStringView
* fStatusLabel
;
661 BStatusBar
* fStatusBar
;
665 // #pragma mark - AboutView
669 MSG_EMAIL_PUBLISHER
= 'emlp',
670 MSG_VISIT_PUBLISHER_WEBSITE
= 'vpws',
674 class AboutView
: public BView
{
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
);
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
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)
738 .SetInsets(B_USE_DEFAULT_SPACING
)
739 .SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED
, B_SIZE_UNSET
))
741 .Add(scrollView
, 2.0f
)
743 .SetExplicitMaxSize(BSize(B_SIZE_UNSET
, B_SIZE_UNLIMITED
))
744 .SetInsets(0.0f
, -1.0f
, -1.0f
, -1.0f
)
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());
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
787 case MSG_VISIT_PUBLISHER_WEBSITE
:
789 BUrl
url(fWebsiteLinkView
->Text());
790 url
.OpenWithPreferredApplication();
795 BView::MessageReceived(message
);
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
);
821 fScreenshotView
->UnsetBitmap();
823 fScreenshotView
->SetEnabled(hasScreenshot
);
828 fDescriptionView
->SetText("");
829 fEmailIconView
->UnsetBitmap();
830 fEmailLinkView
->SetText("");
831 fWebsiteIconView
->UnsetBitmap();
832 fWebsiteLinkView
->SetText("");
834 fScreenshotView
->UnsetBitmap();
835 fScreenshotView
->SetEnabled(false);
839 void _SetContactInfo(LinkView
* view
, const BString
& string
)
841 if (string
.Length() > 0) {
842 view
->SetText(string
);
843 view
->SetEnabled(true);
845 view
->SetText(B_TRANSLATE("<no info>"));
846 view
->SetEnabled(false);
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
{
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());
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",
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
)
946 .Add(fRatingLabelView
)
948 .Add(fPackageVersionView
)
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)
960 .SetInsets(B_USE_DEFAULT_SPACING
)
963 SetFlags(Flags() | B_WILL_DRAW
);
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);
976 StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom());
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;
995 class RatingSummaryView
: public BGridView
{
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);
1007 GetFont(&smallFont
);
1008 smallFont
.SetSize(std::max(9.0f
, floorf(smallFont
.Size() * 0.85f
)));
1010 for (int32 i
= 0; i
< 5; i
++) {
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
];
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
);
1043 fDiagramBarViews
[i
]->SetValue(0.0f
);
1048 for (int32 i
= 0; i
< 5; i
++) {
1049 fCountViews
[i
]->SetText("");
1050 fDiagramBarViews
[i
]->SetValue(0.0f
);
1055 BStringView
* fLabelViews
[5];
1056 DiagramBarView
* fDiagramBarViews
[5];
1057 BStringView
* fCountViews
[5];
1061 class UserRatingsView
: public BGroupView
{
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
,
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
)
1085 .SetInsets(0.0f
, B_USE_DEFAULT_SPACING
, 0.0f
, 0.0f
)
1087 .Add(scrollView
, 1.0f
)
1088 .SetInsets(B_USE_DEFAULT_SPACING
, -1.0f
, -1.0f
, -1.0f
)
1091 _InitPreferredLanguages();
1094 virtual ~UserRatingsView()
1099 void SetPackage(const PackageInfo
& package
)
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();
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
),
1116 noRatingsView
->SetExplicitMaxSize(
1117 BSize(B_SIZE_UNLIMITED
, B_SIZE_UNLIMITED
));
1118 fRatingContainerLayout
->AddView(0, noRatingsView
);
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())) {
1132 RatingItemView
* view
= new RatingItemView(rating
, fThumbsUpIcon
,
1134 fRatingContainerLayout
->AddView(0, view
);
1140 fRatingSummaryView
->Clear();
1146 for (int32 i
= fRatingContainerLayout
->CountItems() - 1;
1147 BLayoutItem
* item
= fRatingContainerLayout
->ItemAt(i
); i
--) {
1148 BView
* view
= dynamic_cast<RatingItemView
*>(item
->View());
1150 view
= dynamic_cast<BStringView
*>(item
->View());
1159 void _InitPreferredLanguages()
1161 fPreferredLanguages
.Clear();
1163 BLocaleRoster
* localeRoster
= BLocaleRoster::Default();
1164 if (localeRoster
== NULL
)
1167 BMessage preferredLanguages
;
1168 if (localeRoster
->GetPreferredLanguages(&preferredLanguages
) != B_OK
)
1173 while (preferredLanguages
.FindString("language", index
++,
1174 &language
) == B_OK
) {
1175 BString languageCode
;
1176 language
.CopyInto(languageCode
, 0, 2);
1177 fPreferredLanguages
.Add(languageCode
);
1182 BGroupLayout
* fRatingContainerLayout
;
1183 RatingSummaryView
* fRatingSummaryView
;
1184 BitmapRef fThumbsUpIcon
;
1185 BitmapRef fThumbsDownIcon
;
1186 StringList fPreferredLanguages
;
1190 // #pragma mark - ContentsView
1193 class ContentsView
: public BGroupView
{
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
);
1221 fPackageContents
->Clear();
1225 PackageContentsView
* fPackageContents
;
1229 // #pragma mark - ChangelogView
1232 class ChangelogView
: public BGroupView
{
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
);
1268 fTextView
->SetDisabledText(B_TRANSLATE("No changelog available."));
1273 fTextView
->SetText("");
1277 MarkupTextView
* fTextView
;
1281 // #pragma mark - PagesView
1284 class PagesView
: public BTabView
{
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();
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"));
1311 virtual ~PagesView()
1316 void SetPackage(const PackageInfoRef
& package
, bool switchToDefaultTab
)
1318 if (switchToDefaultTab
)
1320 fAboutView
->SetPackage(*package
.Get());
1321 fUserRatingsView
->SetPackage(*package
.Get());
1322 fChangelogView
->SetPackage(*package
.Get());
1323 fContentsView
->SetPackage(package
);
1328 fAboutView
->Clear();
1329 fUserRatingsView
->Clear();
1330 fChangelogView
->Clear();
1331 fContentsView
->Clear();
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
)
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
,
1380 fPagesView
= new PagesView();
1382 BLayoutBuilder::Group
<>(packageCard
)
1383 .AddGroup(B_HORIZONTAL
, 0.0f
)
1384 .Add(fTitleView
, 6.0f
)
1385 .Add(fPackageActionView
, 1.0f
)
1387 B_USE_DEFAULT_SPACING
, 0.0f
,
1388 B_USE_DEFAULT_SPACING
, 0.0f
)
1397 PackageInfoView::~PackageInfoView()
1399 fPackageListener
->SetPackage(PackageInfoRef(NULL
));
1400 delete fPackageListener
;
1405 PackageInfoView::AttachedToWindow()
1411 PackageInfoView::MessageReceived(BMessage
* message
)
1413 switch (message
->what
) {
1414 case MSG_UPDATE_PACKAGE
:
1416 if (fPackageListener
->Package().Get() == NULL
)
1421 if (message
->FindString("name", &name
) != B_OK
1422 || message
->FindUInt32("changes", &changes
) != B_OK
) {
1426 const PackageInfoRef
& package
= fPackageListener
->Package();
1427 if (package
->Name() != name
)
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());
1454 BView::MessageReceived(message
);
1461 PackageInfoView::SetPackage(const PackageInfoRef
& packageRef
)
1463 BAutolock
_(fModelLock
);
1465 if (packageRef
.Get() == NULL
) {
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
;
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
));