/*
 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
 * Copyright 2018-2019, Andrew Lindesay <apl@lindesay.co.nz>.
 * All rights reserved. Distributed under the terms of the MIT License.
 */
 
#include "PackageInfoView.h"
 
#include <algorithm>
#include <stdio.h>
 
#include <Alert.h>
#include <Autolock.h>
#include <Bitmap.h>
#include <Button.h>
#include <CardLayout.h>
#include <Catalog.h>
#include <ColumnListView.h>
#include <DateFormat.h>
#include <Font.h>
#include <GridView.h>
#include <LayoutBuilder.h>
#include <LayoutUtils.h>
#include <LocaleRoster.h>
#include <Message.h>
#include <OutlineListView.h>
#include <ScrollView.h>
#include <SpaceLayoutItem.h>
#include <StatusBar.h>
#include <StringView.h>
#include <TabView.h>
#include <Url.h>
 
#include <package/hpkg/PackageReader.h>
#include <package/hpkg/NoErrorOutput.h>
#include <package/hpkg/PackageContentHandler.h>
#include <package/hpkg/PackageEntry.h>
 
#include "BitmapView.h"
#include "LinkView.h"
#include "LinkedBitmapView.h"
#include "MarkupTextView.h"
#include "MessagePackageListener.h"
#include "PackageActionHandler.h"
#include "PackageContentsView.h"
#include "PackageInfo.h"
#include "PackageManager.h"
#include "RatingView.h"
#include "ScrollableGroupView.h"
#include "TextView.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PackageInfoView"
 
 
static const rgb_color kLightBlack = (rgb_color) { 60, 60, 60, 255 };
static const float kContentTint = (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0f;
 
 
//! Layouts the scrollbar so it looks nice with no border and the document
// window look.
class CustomScrollView : public BScrollView {
public:
	CustomScrollView(const char* name, BView* target)
		:
		BScrollView(name, target, 0, false, true, B_NO_BORDER)
	{
	}
 
	virtual void DoLayout()
	{
		BRect innerFrame = Bounds();
		innerFrame.right -= B_V_SCROLL_BAR_WIDTH + 1;
 
		BView* target = Target();
		if (target != NULL) {
			Target()->MoveTo(innerFrame.left, innerFrame.top);
			Target()->ResizeTo(innerFrame.Width(), innerFrame.Height());
		}
 
		BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
		if (scrollBar != NULL) {
			BRect rect = innerFrame;
			rect.left = rect.right + 1;
			rect.right = rect.left + B_V_SCROLL_BAR_WIDTH;
			rect.bottom -= B_H_SCROLL_BAR_HEIGHT;
 
			scrollBar->MoveTo(rect.left, rect.top);
			scrollBar->ResizeTo(rect.Width(), rect.Height());
		}
	}
};
 
 
class RatingsScrollView : public CustomScrollView {
public:
	RatingsScrollView(const char* name, BView* target)
		:
		CustomScrollView(name, target)
	{
	}
 
	virtual void DoLayout()
	{
		CustomScrollView::DoLayout();
 
		BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
		BView* target = Target();
		if (target != NULL && scrollBar != NULL) {
			// Set the scroll steps
			BView* item = target->ChildAt(0);
			if (item != NULL) {
				scrollBar->SetSteps(item->MinSize().height + 1,
					item->MinSize().height + 1);
			}
		}
	}
};
 
 
// #pragma mark - rating stats
 
 
class DiagramBarView : public BView {
public:
	DiagramBarView()
		:
		BView("diagram bar view", B_WILL_DRAW),
		fValue(0.0f)
	{
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
		SetHighUIColor(B_CONTROL_MARK_COLOR);
	}
 
	virtual ~DiagramBarView()
	{
	}
 
	virtual void AttachedToWindow()
	{
	}
 
	virtual void Draw(BRect updateRect)
	{
		FillRect(updateRect, B_SOLID_LOW);
 
		if (fValue <= 0.0f)
			return;
 
		BRect rect(Bounds());
		rect.right = ceilf(rect.left + fValue * rect.Width());
 
		FillRect(rect, B_SOLID_HIGH);
	}
 
	virtual BSize MinSize()
	{
		return BSize(64, 10);
	}
 
	virtual BSize PreferredSize()
	{
		return MinSize();
	}
 
	virtual BSize MaxSize()
	{
		return BSize(64, 10);
	}
 
	void SetValue(float value)
	{
		if (fValue != value) {
			fValue = value;
			Invalidate();
		}
	}
 
private:
	float			fValue;
};
 
 
// #pragma mark - TitleView
 
 
enum {
	MSG_PACKAGE_ACTION			= 'pkga',
	MSG_MOUSE_ENTERED_RATING	= 'menr',
	MSG_MOUSE_EXITED_RATING		= 'mexr',
};
 
 
class TransitReportingButton : public BButton {
public:
	TransitReportingButton(const char* name, const char* label,
			BMessage* message)
		:
		BButton(name, label, message),
		fTransitMessage(NULL)
	{
	}
 
	virtual ~TransitReportingButton()
	{
		SetTransitMessage(NULL);
	}
 
	virtual void MouseMoved(BPoint point, uint32 transit,
		const BMessage* dragMessage)
	{
		BButton::MouseMoved(point, transit, dragMessage);
 
		if (fTransitMessage != NULL && transit == B_EXITED_VIEW)
			Invoke(fTransitMessage);
	}
 
	void SetTransitMessage(BMessage* message)
	{
		if (fTransitMessage != message) {
			delete fTransitMessage;
			fTransitMessage = message;
		}
	}
 
private:
	BMessage*	fTransitMessage;
};
 
 
class TransitReportingRatingView : public RatingView, public BInvoker {
public:
	TransitReportingRatingView(BMessage* transitMessage)
		:
		RatingView("package rating view"),
		fTransitMessage(transitMessage)
	{
	}
 
	virtual ~TransitReportingRatingView()
	{
		delete fTransitMessage;
	}
 
	virtual void MouseMoved(BPoint point, uint32 transit,
		const BMessage* dragMessage)
	{
		RatingView::MouseMoved(point, transit, dragMessage);
 
		if (fTransitMessage != NULL && transit == B_ENTERED_VIEW)
			Invoke(fTransitMessage);
	}
 
private:
	BMessage*	fTransitMessage;
};
 
 
class TitleView : public BGroupView {
public:
	TitleView()
		:
		BGroupView("title view", B_HORIZONTAL)
	{
		fIconView = new BitmapView("package icon view");
		fTitleView = new BStringView("package title view", "");
		fPublisherView = new BStringView("package publisher view", "");
 
		// Title font
		BFont font;
		GetFont(&font);
		font_family family;
		font_style style;
		font.SetSize(ceilf(font.Size() * 1.5f));
		font.GetFamilyAndStyle(&family, &style);
		font.SetFamilyAndStyle(family, "Bold");
		fTitleView->SetFont(&font);
 
		// Publisher font
		GetFont(&font);
		font.SetSize(std::max(9.0f, floorf(font.Size() * 0.92f)));
		font.SetFamilyAndStyle(family, "Italic");
		fPublisherView->SetFont(&font);
		fPublisherView->SetHighColor(kLightBlack);
 
		// slightly bigger font
		GetFont(&font);
		font.SetSize(ceilf(font.Size() * 1.2f));
 
		// Version info
		fVersionInfo = new BStringView("package version info", "");
		fVersionInfo->SetFont(&font);
		fVersionInfo->SetHighColor(kLightBlack);
 
		// Rating view
		fRatingView = new TransitReportingRatingView(
			new BMessage(MSG_MOUSE_ENTERED_RATING));
 
		fAvgRating = new BStringView("package average rating", "");
		fAvgRating->SetFont(&font);
		fAvgRating->SetHighColor(kLightBlack);
 
		fVoteInfo = new BStringView("package vote info", "");
		// small font
		GetFont(&font);
		font.SetSize(std::max(9.0f, floorf(font.Size() * 0.85f)));
		fVoteInfo->SetFont(&font);
		fVoteInfo->SetHighColor(kLightBlack);
 
		// Rate button
		fRateButton = new TransitReportingButton("rate",
			B_TRANSLATE("Rate package" B_UTF8_ELLIPSIS),
			new BMessage(MSG_RATE_PACKAGE));
		fRateButton->SetTransitMessage(new BMessage(MSG_MOUSE_EXITED_RATING));
		fRateButton->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
			B_ALIGN_VERTICAL_CENTER));
 
		// Rating group
		BView* ratingStack = new BView("rating stack", 0);
		fRatingLayout = new BCardLayout();
		ratingStack->SetLayout(fRatingLayout);
		ratingStack->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
		ratingStack->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
 
		BGroupView* ratingGroup = new BGroupView(B_HORIZONTAL,
			B_USE_SMALL_SPACING);
		BLayoutBuilder::Group<>(ratingGroup)
			.Add(fRatingView)
			.Add(fAvgRating)
			.Add(fVoteInfo)
		;
 
		ratingStack->AddChild(ratingGroup);
		ratingStack->AddChild(fRateButton);
		fRatingLayout->SetVisibleItem((int32)0);
 
		BLayoutBuilder::Group<>(this)
			.Add(fIconView)
			.AddGroup(B_VERTICAL, 1.0f, 2.2f)
				.Add(fTitleView)
				.Add(fPublisherView)
				.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
			.End()
			.AddGlue(0.1f)
			.Add(ratingStack, 0.8f)
			.AddGlue(0.2f)
			.AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING, 2.0f)
				.Add(fVersionInfo)
				.AddGlue()
				.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
			.End()
		;
 
		Clear();
	}
 
	virtual ~TitleView()
	{
	}
 
	virtual void AttachedToWindow()
	{
		fRateButton->SetTarget(this);
		fRatingView->SetTarget(this);
	}
 
	virtual void MessageReceived(BMessage* message)
	{
		switch (message->what) {
			case MSG_RATE_PACKAGE:
				// Forward to window (The button has us as target so
				// we receive the message below.)
				Window()->PostMessage(MSG_RATE_PACKAGE);
				break;
 
			case MSG_MOUSE_ENTERED_RATING:
				fRatingLayout->SetVisibleItem(1);
				break;
 
			case MSG_MOUSE_EXITED_RATING:
				fRatingLayout->SetVisibleItem((int32)0);
				break;
		}
	}
 
	void SetPackage(const PackageInfo& package)
	{
		if (package.Icon().Get() != NULL)
			fIconView->SetBitmap(package.Icon(), SharedBitmap::SIZE_32);
		else
			fIconView->UnsetBitmap();
 
		fTitleView->SetText(package.Title());
 
		BString publisher = package.Publisher().Name();
		if (publisher.CountChars() > 45) {
			fPublisherView->SetToolTip(publisher);
			fPublisherView->SetText(publisher.TruncateChars(45)
				.Append(B_UTF8_ELLIPSIS));
		} else
			fPublisherView->SetText(publisher);
 
		BString version = B_TRANSLATE("%Version%");
		version.ReplaceAll("%Version%", package.Version().ToString());
		fVersionInfo->SetText(version);
 
		RatingSummary ratingSummary = package.CalculateRatingSummary();
 
		fRatingView->SetRating(ratingSummary.averageRating);
 
		if (ratingSummary.ratingCount > 0) {
			BString avgRating;
			avgRating.SetToFormat("%.1f", ratingSummary.averageRating);
			fAvgRating->SetText(avgRating);
 
			BString votes;
			votes.SetToFormat("%d", ratingSummary.ratingCount);
 
			BString voteInfo(B_TRANSLATE("(%Votes%)"));
			voteInfo.ReplaceAll("%Votes%", votes);
 
			fVoteInfo->SetText(voteInfo);
		} else {
			fAvgRating->SetText("");
			fVoteInfo->SetText(B_TRANSLATE("n/a"));
		}
 
		InvalidateLayout();
		Invalidate();
	}
 
	void Clear()
	{
		fIconView->UnsetBitmap();
		fTitleView->SetText("");
		fPublisherView->SetText("");
		fVersionInfo->SetText("");
		fRatingView->SetRating(-1.0f);
		fAvgRating->SetText("");
		fVoteInfo->SetText("");
	}
 
private:
	BitmapView*						fIconView;
 
	BStringView*					fTitleView;
	BStringView*					fPublisherView;
 
	BStringView*					fVersionInfo;
 
	BCardLayout*					fRatingLayout;
 
	TransitReportingRatingView*		fRatingView;
	BStringView*					fAvgRating;
	BStringView*					fVoteInfo;
 
	TransitReportingButton*			fRateButton;
};
 
 
// #pragma mark - PackageActionView
 
 
class PackageActionView : public BView {
public:
	PackageActionView(PackageActionHandler* handler)
		:
		BView("about view", B_WILL_DRAW),
		fLayout(new BGroupLayout(B_HORIZONTAL)),
		fPackageActionHandler(handler),
		fStatusLabel(NULL),
		fStatusBar(NULL)
	{
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
 
		SetLayout(fLayout);
		fLayout->AddItem(BSpaceLayoutItem::CreateGlue());
	}
 
	virtual ~PackageActionView()
	{
		Clear();
	}
 
	virtual void MessageReceived(BMessage* message)
	{
		switch (message->what) {
			case MSG_PACKAGE_ACTION:
				_RunPackageAction(message);
				break;
 
			default:
				BView::MessageReceived(message);
				break;
		}
	}
 
	void SetPackage(const PackageInfo& package)
	{
		if (package.State() == DOWNLOADING) {
			AdoptDownloadProgress(package);
		} else {
			AdoptActions(package);
		}
 
	}
 
	void AdoptActions(const PackageInfo& package)
	{
		PackageManager manager(
			BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_HOME);
 
		// TODO: if the given package is either a system package
		// or a system dependency, show a message indicating that status
		// so the user knows why no actions are presented
		PackageActionList actions = manager.GetPackageActions(
			const_cast<PackageInfo*>(&package),
			fPackageActionHandler->GetModel());
 
		bool clearNeeded = fStatusBar != NULL;
		if (!clearNeeded) {
			if (actions.CountItems() != fPackageActions.CountItems())
				clearNeeded = true;
			else {
				for (int32 i = 0; i < actions.CountItems(); i++) {
					if (actions.ItemAtFast(i)->Type()
							!= fPackageActions.ItemAtFast(i)->Type()) {
						clearNeeded = true;
						break;
					}
				}
			}
		}
 
		fPackageActions = actions;
		if (!clearNeeded && fButtons.CountItems() == actions.CountItems()) {
			int32 index = 0;
			for (int32 i = fPackageActions.CountItems() - 1; i >= 0; i--) {
				const PackageActionRef& action = fPackageActions.ItemAtFast(i);
				BButton* button = (BButton*)fButtons.ItemAtFast(index++);
				button->SetLabel(action->Label());
			}
			return;
		}
 
		Clear();
 
		// Add Buttons in reverse action order
		for (int32 i = fPackageActions.CountItems() - 1; i >= 0; i--) {
			const PackageActionRef& action = fPackageActions.ItemAtFast(i);
 
			BMessage* message = new BMessage(MSG_PACKAGE_ACTION);
			message->AddInt32("index", i);
 
			BButton* button = new BButton(action->Label(), message);
			fLayout->AddView(button);
			button->SetTarget(this);
 
			fButtons.AddItem(button);
		}
	}
 
	void AdoptDownloadProgress(const PackageInfo& package)
	{
		if (fButtons.CountItems() > 0)
			Clear();
 
		if (fStatusBar == NULL) {
			fStatusLabel = new BStringView("progress label",
				B_TRANSLATE("Downloading:"));
			fLayout->AddView(fStatusLabel);
 
			fStatusBar = new BStatusBar("progress");
			fStatusBar->SetMaxValue(100.0);
			fStatusBar->SetExplicitMinSize(
				BSize(StringWidth("XXX") * 5, B_SIZE_UNSET));
 
			fLayout->AddView(fStatusBar);
		}
 
		fStatusBar->SetTo(package.DownloadProgress() * 100.0);
	}
 
	void Clear()
	{
		for (int32 i = fButtons.CountItems() - 1; i >= 0; i--) {
			BButton* button = (BButton*)fButtons.ItemAtFast(i);
			button->RemoveSelf();
			delete button;
		}
		fButtons.MakeEmpty();
 
		if (fStatusBar != NULL) {
			fStatusBar->RemoveSelf();
			delete fStatusBar;
			fStatusBar = NULL;
		}
		if (fStatusLabel != NULL) {
			fStatusLabel->RemoveSelf();
			delete fStatusLabel;
			fStatusLabel = NULL;
		}
	}
 
private:
	void _RunPackageAction(BMessage* message)
	{
		int32 index;
		if (message->FindInt32("index", &index) != B_OK)
			return;
 
		const PackageActionRef& action = fPackageActions.ItemAt(index);
		if (action.Get() == NULL)
			return;
 
		PackageActionList actions;
		actions.Add(action);
		status_t result
			= fPackageActionHandler->SchedulePackageActions(actions);
 
		if (result != B_OK) {
			fprintf(stderr, "Failed to schedule action: "
				"%s '%s': %s\n", action->Label(),
				action->Package()->Name().String(),
				strerror(result));
			BString message(B_TRANSLATE("The package action "
				"could not be scheduled: %Error%"));
			message.ReplaceAll("%Error%", strerror(result));
			BAlert* alert = new(std::nothrow) BAlert(
				B_TRANSLATE("Package action failed"),
				message, B_TRANSLATE("OK"), NULL, NULL,
				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
			if (alert != NULL)
				alert->Go();
		} else {
			// Find the button for this action and disable it.
			// Actually search the matching button instead of just using
			// fButtons.ItemAt((fButtons.CountItems() - 1) - index) to
			// make this robust against for example changing the order of
			// buttons from right -> left to left -> right...
			for (int32 i = 0; i < fButtons.CountItems(); i++) {
				BButton* button = (BButton*)fButtons.ItemAt(index);
				if (button == NULL)
					continue;
				BMessage* buttonMessage = button->Message();
				if (buttonMessage == NULL)
					continue;
				int32 buttonIndex;
				if (buttonMessage->FindInt32("index", &buttonIndex) != B_OK)
					continue;
				if (buttonIndex == index) {
					button->SetEnabled(false);
					break;
				}
			}
		}
	}
 
private:
	BGroupLayout*		fLayout;
	PackageActionList	fPackageActions;
	PackageActionHandler* fPackageActionHandler;
	BList				fButtons;
 
	BStringView*		fStatusLabel;
	BStatusBar*			fStatusBar;
};
 
 
// #pragma mark - AboutView
 
 
enum {
	MSG_EMAIL_PUBLISHER				= 'emlp',
	MSG_VISIT_PUBLISHER_WEBSITE		= 'vpws',
};
 
 
class AboutView : public BView {
public:
	AboutView()
		:
		BView("about view", 0),
		fEmailIcon("text/x-email"),
		fWebsiteIcon("text/html")
	{
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
 
		fDescriptionView = new MarkupTextView("description view");
		fDescriptionView->SetViewUIColor(ViewUIColor(), kContentTint);
		fDescriptionView->SetInsets(be_plain_font->Size());
 
		BScrollView* scrollView = new CustomScrollView(
			"description scroll view", fDescriptionView);
 
		BFont smallFont;
		GetFont(&smallFont);
		smallFont.SetSize(std::max(9.0f, ceilf(smallFont.Size() * 0.85f)));
 
		// TODO: Clicking the screen shot view should open ShowImage with the
		// the screen shot. This could be done by writing the screen shot to
		// a temporary folder, launching ShowImage to display it, and writing
		// all other screenshots associated with the package to the same folder
		// so the user can use the ShowImage navigation to view the other
		// screenshots.
		fScreenshotView = new LinkedBitmapView("screenshot view",
			new BMessage(MSG_SHOW_SCREENSHOT));
		fScreenshotView->SetExplicitMinSize(BSize(64.0f, 64.0f));
		fScreenshotView->SetExplicitMaxSize(
			BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
		fScreenshotView->SetExplicitAlignment(
			BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP));
 
		fEmailIconView = new BitmapView("email icon view");
		fEmailLinkView = new LinkView("email link view", "",
			new BMessage(MSG_EMAIL_PUBLISHER));
		fEmailLinkView->SetFont(&smallFont);
 
		fWebsiteIconView = new BitmapView("website icon view");
		fWebsiteLinkView = new LinkView("website link view", "",
			new BMessage(MSG_VISIT_PUBLISHER_WEBSITE));
		fWebsiteLinkView->SetFont(&smallFont);
 
		BGroupView* leftGroup = new BGroupView(B_VERTICAL,
			B_USE_DEFAULT_SPACING);
 
		fScreenshotView->SetViewUIColor(ViewUIColor(), kContentTint);
		fEmailLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
		fWebsiteLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
 
		BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f)
			.AddGroup(leftGroup, 1.0f)
				.Add(fScreenshotView)
				.AddGroup(B_HORIZONTAL)
					.AddGrid(B_USE_HALF_ITEM_SPACING, B_USE_HALF_ITEM_SPACING)
						.Add(fEmailIconView, 0, 0)
						.Add(fEmailLinkView, 1, 0)
						.Add(fWebsiteIconView, 0, 1)
						.Add(fWebsiteLinkView, 1, 1)
					.End()
				.End()
				.SetInsets(B_USE_DEFAULT_SPACING)
				.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
			.End()
			.Add(scrollView, 2.0f)
 
			.SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED))
			.SetInsets(0.0f, -1.0f, -1.0f, -1.0f)
		;
	}
 
	virtual ~AboutView()
	{
		Clear();
	}
 
	virtual void AttachedToWindow()
	{
		fScreenshotView->SetTarget(this);
		fEmailLinkView->SetTarget(this);
		fWebsiteLinkView->SetTarget(this);
	}
 
	virtual void AllAttached()
	{
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
 
		for (int32 index = 0; index < CountChildren(); ++index)
			ChildAt(index)->AdoptParentColors();
	}
 
	virtual void MessageReceived(BMessage* message)
	{
		switch (message->what) {
			case MSG_SHOW_SCREENSHOT:
			{
				// Forward to window for now
				Window()->PostMessage(message, Window());
				break;
			}
 
			case MSG_EMAIL_PUBLISHER:
			{
				// TODO: Implement. If memory serves, there is a
				// standard command line interface which mail apps should
				// support, i.e. to open a compose window with the TO: field
				// already set.
				break;
			}
 
			case MSG_VISIT_PUBLISHER_WEBSITE:
			{
				BUrl url(fWebsiteLinkView->Text());
				url.OpenWithPreferredApplication();
				break;
			}
 
			default:
				BView::MessageReceived(message);
				break;
		}
	}
 
	void SetPackage(const PackageInfo& package)
	{
		fDescriptionView->SetText(package.ShortDescription(),
			package.FullDescription());
 
		fEmailIconView->SetBitmap(&fEmailIcon, SharedBitmap::SIZE_16);
		_SetContactInfo(fEmailLinkView, package.Publisher().Email());
		fWebsiteIconView->SetBitmap(&fWebsiteIcon, SharedBitmap::SIZE_16);
		_SetContactInfo(fWebsiteLinkView, package.Publisher().Website());
 
		bool hasScreenshot = false;
		const BitmapList& screenShots = package.Screenshots();
		if (screenShots.CountItems() > 0) {
			const BitmapRef& bitmapRef = screenShots.ItemAtFast(0);
			if (bitmapRef.Get() != NULL) {
				hasScreenshot = true;
				fScreenshotView->SetBitmap(bitmapRef);
			}
		}
 
		if (!hasScreenshot)
			fScreenshotView->UnsetBitmap();
 
		fScreenshotView->SetEnabled(hasScreenshot);
	}
 
	void Clear()
	{
		fDescriptionView->SetText("");
		fEmailIconView->UnsetBitmap();
		fEmailLinkView->SetText("");
		fWebsiteIconView->UnsetBitmap();
		fWebsiteLinkView->SetText("");
 
		fScreenshotView->UnsetBitmap();
		fScreenshotView->SetEnabled(false);
	}
 
private:
	void _SetContactInfo(LinkView* view, const BString& string)
	{
		if (string.Length() > 0) {
			view->SetText(string);
			view->SetEnabled(true);
		} else {
			view->SetText(B_TRANSLATE("<no info>"));
			view->SetEnabled(false);
		}
	}
 
private:
	MarkupTextView*		fDescriptionView;
 
	LinkedBitmapView*	fScreenshotView;
 
	SharedBitmap		fEmailIcon;
	BitmapView*			fEmailIconView;
	LinkView*			fEmailLinkView;
 
	SharedBitmap		fWebsiteIcon;
	BitmapView*			fWebsiteIconView;
	LinkView*			fWebsiteLinkView;
};
 
 
// #pragma mark - UserRatingsView
 
 
class RatingItemView : public BGroupView {
public:
	RatingItemView(const UserRating& rating)
		:
		BGroupView(B_HORIZONTAL, 0.0f)
	{
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
 
		BGroupLayout* verticalGroup = new BGroupLayout(B_VERTICAL, 0.0f);
		GroupLayout()->AddItem(verticalGroup);
 
		{
			BStringView* userNicknameView = new BStringView("user-nickname",
				rating.User().NickName());
			userNicknameView->SetFont(be_bold_font);
			verticalGroup->AddView(userNicknameView);
		}
 
		BGroupLayout* ratingGroup =
			new BGroupLayout(B_HORIZONTAL, B_USE_DEFAULT_SPACING);
		verticalGroup->AddItem(ratingGroup);
 
		if (rating.Rating() >= 0) {
			RatingView* ratingView = new RatingView("package rating view");
			ratingView->SetRating(rating.Rating());
			ratingGroup->AddView(ratingView);
		}
 
		{
			BDateFormat dateFormat;
			BString createTimestampPresentation;
 
			dateFormat.Format(createTimestampPresentation,
				rating.CreateTimestamp().Date(), B_MEDIUM_DATE_FORMAT);
 
			BString ratingContextDescription(
				B_TRANSLATE("%hd.timestamp% (version %hd.version%)"));
			ratingContextDescription.ReplaceAll("%hd.timestamp%",
				createTimestampPresentation);
			ratingContextDescription.ReplaceAll("%hd.version%",
				rating.PackageVersion());
 
			BStringView* ratingContextView = new BStringView("rating-context",
				ratingContextDescription);
			BFont versionFont(be_plain_font);
			ratingContextView->SetFont(&versionFont);
			ratingGroup->AddView(ratingContextView);
		}
 
		ratingGroup->AddItem(BSpaceLayoutItem::CreateGlue());
 
		if (rating.Comment() > 0) {
			TextView* textView = new TextView("rating-text");
			ParagraphStyle paragraphStyle(textView->ParagraphStyle());
			paragraphStyle.SetJustify(true);
			textView->SetParagraphStyle(paragraphStyle);
			textView->SetText(rating.Comment());
			verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f));
			verticalGroup->AddView(textView);
			verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f));
		}
 
		verticalGroup->SetInsets(B_USE_DEFAULT_SPACING);
 
		SetFlags(Flags() | B_WILL_DRAW);
	}
 
	void AllAttached()
	{
		for (int32 index = 0; index < CountChildren(); ++index)
			ChildAt(index)->AdoptParentColors();
	}
 
	void Draw(BRect rect)
	{
		rgb_color color = mix_color(ViewColor(), ui_color(B_PANEL_TEXT_COLOR), 64);
		SetHighColor(color);
		StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom());
	}
 
};
 
 
class RatingSummaryView : public BGridView {
public:
	RatingSummaryView()
		:
		BGridView("rating summary view", B_USE_HALF_ITEM_SPACING, 0.0f)
	{
		float tint = kContentTint - 0.1;
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, tint);
 
		BLayoutBuilder::Grid<> layoutBuilder(this);
 
		BFont smallFont;
		GetFont(&smallFont);
		smallFont.SetSize(std::max(9.0f, floorf(smallFont.Size() * 0.85f)));
 
		for (int32 i = 0; i < 5; i++) {
			BString label;
			label.SetToFormat("%" B_PRId32, 5 - i);
			fLabelViews[i] = new BStringView("", label);
			fLabelViews[i]->SetFont(&smallFont);
			fLabelViews[i]->SetViewUIColor(ViewUIColor(), tint);
			layoutBuilder.Add(fLabelViews[i], 0, i);
 
			fDiagramBarViews[i] = new DiagramBarView();
			layoutBuilder.Add(fDiagramBarViews[i], 1, i);
 
			fCountViews[i] = new BStringView("", "");
			fCountViews[i]->SetFont(&smallFont);
			fCountViews[i]->SetViewUIColor(ViewUIColor(), tint);
			fCountViews[i]->SetAlignment(B_ALIGN_RIGHT);
			layoutBuilder.Add(fCountViews[i], 2, i);
		}
 
		layoutBuilder.SetInsets(5);
	}
 
	void SetToSummary(const RatingSummary& summary) {
		for (int32 i = 0; i < 5; i++) {
			int32 count = summary.ratingCountByStar[4 - i];
 
			BString label;
			label.SetToFormat("%" B_PRId32, count);
			fCountViews[i]->SetText(label);
 
			if (summary.ratingCount > 0) {
				fDiagramBarViews[i]->SetValue(
					(float)count / summary.ratingCount);
			} else
				fDiagramBarViews[i]->SetValue(0.0f);
		}
	}
 
	void Clear() {
		for (int32 i = 0; i < 5; i++) {
			fCountViews[i]->SetText("");
			fDiagramBarViews[i]->SetValue(0.0f);
		}
	}
 
private:
	BStringView*	fLabelViews[5];
	DiagramBarView*	fDiagramBarViews[5];
	BStringView*	fCountViews[5];
};
 
 
class UserRatingsView : public BGroupView {
public:
	UserRatingsView()
		:
		BGroupView("package ratings view", B_HORIZONTAL)
	{
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
 
		fRatingSummaryView = new RatingSummaryView();
 
		ScrollableGroupView* ratingsContainerView = new ScrollableGroupView();
		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));
 
		BLayoutBuilder::Group<>(this)
			.AddGroup(B_VERTICAL)
				.Add(fRatingSummaryView, 0.0f)
				.AddGlue()
				.SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f)
			.End()
			.AddStrut(64.0)
			.Add(scrollView, 1.0f)
			.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
		;
	}
 
	virtual ~UserRatingsView()
	{
		Clear();
	}
 
	void SetPackage(const PackageInfo& package)
	{
		ClearRatings();
 
		// TODO: Re-use rating summary already used for TitleView...
		fRatingSummaryView->SetToSummary(package.CalculateRatingSummary());
 
		const UserRatingList& userRatings = package.UserRatings();
 
		int count = userRatings.CountItems();
		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);
			return;
		}
 
		// TODO: Sort by age or usefullness rating
		for (int i = count - 1; i >= 0; i--) {
			const UserRating& rating = userRatings.ItemAtFast(i);
				// was previously filtering comments just for the current
				// user's language, but as there are not so many comments at
				// the moment, just show all of them for now.
			RatingItemView* view = new RatingItemView(rating);
			fRatingContainerLayout->AddView(0, view);
		}
	}
 
	void Clear()
	{
		fRatingSummaryView->Clear();
		ClearRatings();
	}
 
	void ClearRatings()
	{
		for (int32 i = fRatingContainerLayout->CountItems() - 1;
				BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) {
			BView* view = dynamic_cast<RatingItemView*>(item->View());
			if (view == NULL)
				view = dynamic_cast<BStringView*>(item->View());
			if (view != NULL) {
				view->RemoveSelf();
				delete view;
			}
		}
	}
 
private:
	BGroupLayout*			fRatingContainerLayout;
	RatingSummaryView*		fRatingSummaryView;
};
 
 
// #pragma mark - ContentsView
 
 
class ContentsView : public BGroupView {
public:
	ContentsView()
		:
		BGroupView("package contents view", B_HORIZONTAL)
	{
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
 
		fPackageContents = new PackageContentsView("contents_list");
		AddChild(fPackageContents);
 
	}
 
	virtual ~ContentsView()
	{
	}
 
	virtual void Draw(BRect updateRect)
	{
	}
 
	void SetPackage(const PackageInfoRef& package)
	{
		fPackageContents->SetPackage(package);
	}
 
	void Clear()
	{
		fPackageContents->Clear();
	}
 
private:
	PackageContentsView*	fPackageContents;
};
 
 
// #pragma mark - ChangelogView
 
 
class ChangelogView : public BGroupView {
public:
	ChangelogView()
		:
		BGroupView("package changelog view", B_HORIZONTAL)
	{
		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
 
		fTextView = new MarkupTextView("changelog view");
		fTextView->SetLowUIColor(ViewUIColor());
		fTextView->SetInsets(be_plain_font->Size());
 
		BScrollView* scrollView = new CustomScrollView(
			"changelog scroll view", fTextView);
 
		BLayoutBuilder::Group<>(this)
			.Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f))
			.Add(scrollView, 1.0f)
			.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
		;
	}
 
	virtual ~ChangelogView()
	{
	}
 
	virtual void Draw(BRect updateRect)
	{
	}
 
	void SetPackage(const PackageInfo& package)
	{
		const BString& changelog = package.Changelog();
		if (changelog.Length() > 0)
			fTextView->SetText(changelog);
		else
			fTextView->SetDisabledText(B_TRANSLATE("No changelog available."));
	}
 
	void Clear()
	{
		fTextView->SetText("");
	}
 
private:
	MarkupTextView*		fTextView;
};
 
 
// #pragma mark - PagesView
 
 
class PagesView : public BTabView {
public:
	PagesView()
		:
		BTabView("pages view", B_WIDTH_FROM_WIDEST),
		fLayout(new BCardLayout())
	{
		SetBorder(B_NO_BORDER);
 
		fAboutView = new AboutView();
		fUserRatingsView = new UserRatingsView();
		fChangelogView = new ChangelogView();
		fContentsView = new ContentsView();
 
		AddTab(fAboutView);
		AddTab(fUserRatingsView);
		AddTab(fChangelogView);
		AddTab(fContentsView);
 
		TabAt(0)->SetLabel(B_TRANSLATE("About"));
		TabAt(1)->SetLabel(B_TRANSLATE("Ratings"));
		TabAt(2)->SetLabel(B_TRANSLATE("Changelog"));
		TabAt(3)->SetLabel(B_TRANSLATE("Contents"));
 
		Select(0);
	}
 
	virtual ~PagesView()
	{
		Clear();
	}
 
	void SetPackage(const PackageInfoRef& package, bool switchToDefaultTab)
	{
		if (switchToDefaultTab)
			Select(0);
		fAboutView->SetPackage(*package.Get());
		fUserRatingsView->SetPackage(*package.Get());
		fChangelogView->SetPackage(*package.Get());
		fContentsView->SetPackage(package);
	}
 
	void Clear()
	{
		fAboutView->Clear();
		fUserRatingsView->Clear();
		fChangelogView->Clear();
		fContentsView->Clear();
	}
 
private:
	BCardLayout*		fLayout;
 
	AboutView*			fAboutView;
	UserRatingsView*	fUserRatingsView;
	ChangelogView*		fChangelogView;
	ContentsView* 		fContentsView;
};
 
 
// #pragma mark - PackageInfoView
 
 
PackageInfoView::PackageInfoView(BLocker* modelLock,
		PackageActionHandler* handler)
	:
	BView("package info view", 0),
	fModelLock(modelLock),
	fPackageListener(new(std::nothrow) OnePackageMessagePackageListener(this))
{
	fCardLayout = new BCardLayout();
	SetLayout(fCardLayout);
 
	BGroupView* noPackageCard = new BGroupView("no package card", B_VERTICAL);
	AddChild(noPackageCard);
 
	BStringView* noPackageView = new BStringView("no package view",
		B_TRANSLATE("Click a package to view information"));
	noPackageView->SetHighColor(kLightBlack);
	noPackageView->SetExplicitAlignment(BAlignment(
		B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
 
	BLayoutBuilder::Group<>(noPackageCard)
		.Add(noPackageView)
		.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED))
	;
 
	BGroupView* packageCard = new BGroupView("package card", B_VERTICAL);
	AddChild(packageCard);
 
	fCardLayout->SetVisibleItem((int32)0);
 
	fTitleView = new TitleView();
	fPackageActionView = new PackageActionView(handler);
	fPackageActionView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
		B_SIZE_UNSET));
	fPagesView = new PagesView();
 
	BLayoutBuilder::Group<>(packageCard)
		.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()
		.Add(fPagesView)
	;
 
	Clear();
}
 
 
PackageInfoView::~PackageInfoView()
{
	fPackageListener->SetPackage(PackageInfoRef(NULL));
	delete fPackageListener;
}
 
 
void
PackageInfoView::AttachedToWindow()
{
}
 
 
void
PackageInfoView::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MSG_UPDATE_PACKAGE:
		{
			if (fPackageListener->Package().Get() == NULL)
				break;
 
			BString name;
			uint32 changes;
			if (message->FindString("name", &name) != B_OK
				|| message->FindUInt32("changes", &changes) != B_OK) {
				break;
			}
 
			const PackageInfoRef& package = fPackageListener->Package();
			if (package->Name() != name)
				break;
 
			BAutolock _(fModelLock);
 
			if ((changes & PKG_CHANGED_SUMMARY) != 0
				|| (changes & PKG_CHANGED_DESCRIPTION) != 0
				|| (changes & PKG_CHANGED_SCREENSHOTS) != 0
				|| (changes & PKG_CHANGED_TITLE) != 0
				|| (changes & PKG_CHANGED_RATINGS) != 0
				|| (changes & PKG_CHANGED_STATE) != 0
				|| (changes & PKG_CHANGED_CHANGELOG) != 0) {
				fPagesView->SetPackage(package, false);
			}
 
			if ((changes & PKG_CHANGED_TITLE) != 0
				|| (changes & PKG_CHANGED_RATINGS) != 0) {
				fTitleView->SetPackage(*package.Get());
			}
 
			if ((changes & PKG_CHANGED_STATE) != 0) {
				fPackageActionView->SetPackage(*package.Get());
			}
 
			break;
		}
		default:
			BView::MessageReceived(message);
			break;
	}
}
 
 
void
PackageInfoView::SetPackage(const PackageInfoRef& packageRef)
{
	BAutolock _(fModelLock);
 
	if (packageRef.Get() == NULL) {
		Clear();
		return;
	}
 
	bool switchToDefaultTab = true;
	if (fPackage == packageRef) {
		// When asked to display the already showing package ref,
		// don't switch to the default tab.
		switchToDefaultTab = false;
	} else if (fPackage.Get() != NULL && packageRef.Get() != NULL
		&& fPackage->Name() == packageRef->Name()) {
		// When asked to display a different PackageInfo instance,
		// but it has the same package title as the already showing
		// instance, this probably means there was a repository
		// refresh and we are in fact still requested to show the
		// same package as before the refresh.
		switchToDefaultTab = false;
	}
 
	const PackageInfo& package = *packageRef.Get();
 
	fTitleView->SetPackage(package);
	fPackageActionView->SetPackage(package);
	fPagesView->SetPackage(packageRef, switchToDefaultTab);
 
	fCardLayout->SetVisibleItem(1);
 
	fPackageListener->SetPackage(packageRef);
 
	// Set the fPackage reference last, so we keep a reference to the
	// previous package before switching all the views to the new package.
	// Otherwise the PackageInfo instance may go away because we had the
	// last reference. And some of the views, the PackageActionView for
	// example, keeps references to stuff from the previous package and
	// access it while switching to the new package.
	fPackage = packageRef;
}
 
 
void
PackageInfoView::Clear()
{
	BAutolock _(fModelLock);
 
	fTitleView->Clear();
	fPackageActionView->Clear();
	fPagesView->Clear();
 
	fCardLayout->SetVisibleItem((int32)0);
 
	fPackageListener->SetPackage(PackageInfoRef(NULL));
 
	fPackage.Unset();
}

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.