diff --git a/src/apps/haikudepot/Jamfile b/src/apps/haikudepot/Jamfile index 6855ec2da1..13c7f4cc72 100644 --- a/src/apps/haikudepot/Jamfile +++ b/src/apps/haikudepot/Jamfile @@ -140,6 +140,7 @@ local applicationSources = MainWindow.cpp MarkupTextView.cpp Model.cpp + NotAvailableStringView.cpp PackageContentsView.cpp PackageInfoListener.cpp PackageInfoView.cpp diff --git a/src/apps/haikudepot/ui/NotAvailableStringView.cpp b/src/apps/haikudepot/ui/NotAvailableStringView.cpp new file mode 100644 index 0000000000..0ba0451637 --- /dev/null +++ b/src/apps/haikudepot/ui/NotAvailableStringView.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2026, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#include "NotAvailableStringView.h" + +#include + +#include "Logger.h" + + +NotAvailableStringView::NotAvailableStringView(const char* name, const char* text) + : + BView(name, B_FULL_UPDATE_ON_RESIZE | B_WILL_DRAW), + fText(text) +{ +} + + +NotAvailableStringView::~NotAvailableStringView() +{ +} + + +BSize +NotAvailableStringView::MaxSize() +{ + return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); + // can be any size +} + + +BAlignment +NotAvailableStringView::LayoutAlignment() +{ + return BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE); +} + + +void +NotAvailableStringView::SetText(const char* text) +{ + if (fText != text) { + fText = text; + Invalidate(); + } +} + + +const char* +NotAvailableStringView::Text() const +{ + return fText; +} + + +void +NotAvailableStringView::Draw(BRect updateRect) +{ + rgb_color highColor = HighColor(); + be_control_look->DrawLabel(this, fText, Bounds(), updateRect, ViewColor(), + BControlLook::B_DISABLED, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE), &highColor); +} diff --git a/src/apps/haikudepot/ui/NotAvailableStringView.h b/src/apps/haikudepot/ui/NotAvailableStringView.h new file mode 100644 index 0000000000..33e92c3901 --- /dev/null +++ b/src/apps/haikudepot/ui/NotAvailableStringView.h @@ -0,0 +1,36 @@ +/* + * Copyright 2026, Andrew Lindesay . + * All rights reserved. Distributed under the terms of the MIT License. + */ +#ifndef NOT_AVAILABLE_STRING_VIEW_H +#define NOT_AVAILABLE_STRING_VIEW_H + + +#include + + +/*! This view abstracts a message to indicate that there is no item to view. + * This exists because the BStringView only renders at the bottom of the + * view frame according to the legacy Be documentation. This is unfortunately + * not convenient for this use-case. In addition, this view will centralize + * the configuration of colors etc... for this purpose. + */ +class NotAvailableStringView : public BView { +public: + NotAvailableStringView(const char* name, const char* text); + virtual ~NotAvailableStringView(); + + virtual BSize MaxSize(); + virtual BAlignment LayoutAlignment(); + + void SetText(const char* text); + const char* Text() const; + + virtual void Draw(BRect bounds); + +private: + BString fText; +}; + + +#endif // NOT_AVAILABLE_STRING_VIEW_H diff --git a/src/apps/haikudepot/ui/PackageInfoView.cpp b/src/apps/haikudepot/ui/PackageInfoView.cpp index 7769e7e7ca..bc85f8b3a5 100644 --- a/src/apps/haikudepot/ui/PackageInfoView.cpp +++ b/src/apps/haikudepot/ui/PackageInfoView.cpp @@ -36,11 +36,13 @@ #include "BitmapView.h" #include "GeneralContentScrollView.h" +#include "InterfaceDefs.h" #include "LinkView.h" #include "LinkedBitmapView.h" #include "LocaleUtils.h" #include "Logger.h" #include "MarkupTextView.h" +#include "NotAvailableStringView.h" #include "PackageContentsView.h" #include "PackageInfo.h" #include "PackageManager.h" @@ -57,10 +59,16 @@ enum { - TAB_ABOUT = 0, - TAB_RATINGS = 1, - TAB_CHANGELOG = 2, - TAB_CONTENTS = 3 + TAB_ABOUT = 0, + TAB_RATINGS = 1, + TAB_CHANGELOG = 2, + TAB_CONTENTS = 3 +}; + + +enum { + CARD_AVAILABLE = 0, + CARD_NOT_AVAILABLE = 1 }; @@ -159,8 +167,8 @@ private: enum { - MSG_MOUSE_ENTERED_RATING = 'menr', - MSG_MOUSE_EXITED_RATING = 'mexr', + MSG_MOUSE_ENTERED_RATING = 'menr', + MSG_MOUSE_EXITED_RATING = 'mexr', }; @@ -662,7 +670,7 @@ private: enum { - MSG_VISIT_PUBLISHER_WEBSITE = 'vpws', + MSG_VISIT_PUBLISHER_WEBSITE = 'vpws', }; @@ -907,10 +915,12 @@ public: SetHighColor(color); StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom()); } - }; +/*! This class will draw a little graphic of the relative count of the ratings + that have been assigned to the package. +*/ class RatingSummaryView : public BGridView { public: RatingSummaryView() @@ -989,25 +999,40 @@ private: }; +/*! This view renders the ratings information for the package. It will appear + when the user chooses the "ratings" tab. +*/ class UserRatingsView : public BGroupView { public: UserRatingsView() : BGroupView("package ratings view", B_HORIZONTAL) { + fPackageName = ""; + fAmPopulating = false; + SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); fRatingSummaryView = new RatingSummaryView(); ScrollableGroupView* ratingsContainerView = new ScrollableGroupView(); - ratingsContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR, - kContentTint); + ratingsContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); fRatingContainerLayout = ratingsContainerView->GroupLayout(); BScrollView* scrollView = new RatingsScrollView("ratings scroll view", ratingsContainerView); scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); + fNoRatingsView = new NotAvailableStringView( + "no ratings",B_TRANSLATE("No user ratings available.")); + fNoRatingsView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); + + // This allows for switching between the "no ratings" and the + // list of ratings views if there are any. + fRatingContainerCardView = new BCardView(); + fRatingContainerCardView->AddChild(scrollView); + fRatingContainerCardView->AddChild(fNoRatingsView); + BLayoutBuilder::Group<>(this) .AddGroup(B_VERTICAL) .Add(fRatingSummaryView, 0.0f) @@ -1015,7 +1040,7 @@ public: .SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f) .End() .AddStrut(64.0) - .Add(scrollView, 1.0f) + .Add(fRatingContainerCardView, 1.0f) .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f); } @@ -1041,14 +1066,11 @@ public: if (userRatingInfo.IsSet()) count = userRatingInfo->CountUserRatings(); + BString packageName = package.IsSet() ? package->Name() : ""; + fAmPopulating = fAmPopulating && (packageName == fPackageName) && count == 0; + if (count == 0) { - BStringView* noRatingsView - = new BStringView("no ratings", B_TRANSLATE("No user ratings available.")); - noRatingsView->SetViewUIColor(ViewUIColor(), kContentTint); - noRatingsView->SetAlignment(B_ALIGN_CENTER); - noRatingsView->SetHighColor(disable_color(ui_color(B_PANEL_TEXT_COLOR), ViewColor())); - noRatingsView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); - fRatingContainerLayout->AddView(0, noRatingsView); + _SetNotAvailable(); } else { for (int i = count - 1; i >= 0; i--) { UserRatingRef rating = userRatingInfo->UserRatingAtIndex(i); @@ -1058,15 +1080,18 @@ public: RatingItemView* view = new RatingItemView(rating); fRatingContainerLayout->AddView(0, view); } + fRatingContainerCardView->CardLayout()->SetVisibleItem(CARD_AVAILABLE); } InvalidateLayout(); + + fPackageName = packageName; } void Clear() { - fRatingSummaryView->Clear(); - ClearRatings(); + fAmPopulating = false; + _SetNotAvailable(); } void ClearRatings() @@ -1074,8 +1099,6 @@ public: for (int32 i = fRatingContainerLayout->CountItems() - 1; BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) { BView* view = dynamic_cast(item->View()); - if (view == NULL) - view = dynamic_cast(item->View()); if (view != NULL) { view->RemoveSelf(); delete view; @@ -1083,9 +1106,32 @@ public: } } + void _SetNotAvailable() { + fNoRatingsView->SetText(_NotAvailableText()); + fRatingContainerCardView->CardLayout()->SetVisibleItem(CARD_NOT_AVAILABLE); + fRatingSummaryView->Clear(); + ClearRatings(); + } + + // TODO; need some way to signal failure. + void SetAmPopulating() { + fAmPopulating = true; + _SetNotAvailable(); + } + + const char* _NotAvailableText() { + if (fAmPopulating) + return B_TRANSLATE("User ratings loading" B_UTF8_ELLIPSIS); + return B_TRANSLATE("No user ratings available."); + } + private: - BGroupLayout* fRatingContainerLayout; - RatingSummaryView* fRatingSummaryView; + BString fPackageName; + bool fAmPopulating; + NotAvailableStringView* fNoRatingsView; + BGroupLayout* fRatingContainerLayout; + RatingSummaryView* fRatingSummaryView; + BCardView* fRatingContainerCardView; }; @@ -1101,8 +1147,15 @@ public: SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); fPackageContents = new PackageContentsView("contents_list"); - AddChild(fPackageContents); + fNotAvailableView = new NotAvailableStringView("no contents", + B_TRANSLATE("No contents available.")); + + fCardView = new BCardView(); + fCardView->AddChild(fPackageContents); + fCardView->AddChild(fNotAvailableView); + + AddChild(fCardView); } virtual ~ContentsView() @@ -1115,40 +1168,64 @@ public: void SetPackage(const PackageInfoRef package) { - fPackageContents->SetPackage(package); + // if the package is not installed and is not a local file on disk then + // there is no point in attempting to populate data for it. + + if (PackageUtils::IsActivatedOrLocalFile(package)) { + fPackageContents->SetPackage(package); + fCardView->CardLayout()->SetVisibleItem(CARD_AVAILABLE); + } else { + Clear(); + } } void Clear() { fPackageContents->Clear(); + fCardView->CardLayout()->SetVisibleItem(CARD_NOT_AVAILABLE); } private: - PackageContentsView* fPackageContents; + BCardView* fCardView; + NotAvailableStringView* fNotAvailableView; + PackageContentsView* fPackageContents; }; // #pragma mark - ChangelogView +/*! This view appears to show the Changelog when the user chooses the + changelog tab. +*/ class ChangelogView : public BGroupView { public: ChangelogView() : BGroupView("package changelog view", B_HORIZONTAL) { + fAmPopulating = false; + fPackageName = ""; + SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); fTextView = new MarkupTextView("changelog view"); fTextView->SetLowUIColor(ViewUIColor()); fTextView->SetInsets(be_plain_font->Size()); + fTextScrollView = new GeneralContentScrollView("changelog scroll view", fTextView); - BScrollView* scrollView = new GeneralContentScrollView("changelog scroll view", fTextView); + fNotAvailableView = new NotAvailableStringView("no changelog", _NotAvailableText()); + + fCardView = new BCardView(); + fCardView->AddChild(fTextScrollView); + fCardView->AddChild(fNotAvailableView); BLayoutBuilder::Group<>(this) .Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f)) - .Add(scrollView, 1.0f) + .Add(fCardView, 1.0f) .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f); + + Clear(); } virtual ~ChangelogView() @@ -1161,6 +1238,11 @@ public: void SetPackage(const PackageInfoRef package) { + if (package.IsSet()) + HDDEBUG("setting package on changelog view [%s]", package->Name().String()); + else + HDDEBUG("setting package on changelog view to empty package"); + PackageLocalizedTextRef localizedText = package->LocalizedText(); BString changelog; @@ -1169,19 +1251,52 @@ public: changelog = localizedText->Changelog(); } - if (changelog.Length() > 0) + BString packageName = package.IsSet() ? package->Name() : ""; + fAmPopulating = fAmPopulating && (packageName == fPackageName) && changelog.Length() == 0; + + if (changelog.Length() > 0) { fTextView->SetText(changelog); - else - fTextView->SetDisabledText(B_TRANSLATE("No changelog available.")); + fCardView->CardLayout()->SetVisibleItem(CARD_AVAILABLE); + } else { + _SetNotAvailable(); + } + + fPackageName = packageName; + } + + // TODO; need some way to signal failure. + void SetAmPopulating() { + HDDEBUG("set am populating changelog view"); + fAmPopulating = true; + _SetNotAvailable(); } void Clear() { + HDDEBUG("clearing changelog view"); + fAmPopulating = false; + _SetNotAvailable(); + } + + void _SetNotAvailable() { + fNotAvailableView->SetText(_NotAvailableText()); + fCardView->CardLayout()->SetVisibleItem(CARD_NOT_AVAILABLE); fTextView->SetText(""); } + const char* _NotAvailableText() { + if (fAmPopulating) + return B_TRANSLATE("Changelog loading" B_UTF8_ELLIPSIS); + return B_TRANSLATE("No changelog available."); + } + private: - MarkupTextView* fTextView; + BString fPackageName; + MarkupTextView* fTextView; + BCardView* fCardView; + NotAvailableStringView* fNotAvailableView; + BScrollView* fTextScrollView; + bool fAmPopulating; }; @@ -1359,6 +1474,8 @@ private: PopulateChangelogPackageAction action(package->Name()); BMessage message = action.Message(); Window()->PostMessage(&message); + + fChangelogView->SetAmPopulating(); } void _MaybePopulateUserRatings(const PackageInfoRef package) { @@ -1386,6 +1503,8 @@ private: PopulateUserRatingsPackageAction action(package->Name()); BMessage message = action.Message(); Window()->PostMessage(&message); + + fUserRatingsView->SetAmPopulating(); } private: @@ -1436,10 +1555,8 @@ PackageInfoView::PackageInfoView(Model* model) .AddGroup(B_HORIZONTAL, 0.0f) .Add(fTitleView, 6.0f) .Add(fPackageActionView, 1.0f) - .SetInsets( - B_USE_DEFAULT_SPACING, 0.0f, - B_USE_DEFAULT_SPACING, 0.0f) - .End() + .SetInsets(B_USE_DEFAULT_SPACING, 0.0f, B_USE_DEFAULT_SPACING, 0.0f) + .End() .Add(fPagesView); Clear();