xref: /haiku/src/apps/haikudepot/ui/PackageInfoView.cpp (revision 99f2b968946b4a0f391e62a417c370c7e9b942de)
1 /*
2  * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2018-2020, Andrew Lindesay <apl@lindesay.co.nz>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 #include "PackageInfoView.h"
8 
9 #include <algorithm>
10 
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>
31 
32 #include <package/hpkg/PackageReader.h>
33 #include <package/hpkg/NoErrorOutput.h>
34 #include <package/hpkg/PackageContentHandler.h>
35 #include <package/hpkg/PackageEntry.h>
36 
37 #include "BitmapView.h"
38 #include "LinkView.h"
39 #include "LinkedBitmapView.h"
40 #include "LocaleUtils.h"
41 #include "Logger.h"
42 #include "MarkupTextView.h"
43 #include "MessagePackageListener.h"
44 #include "PackageActionHandler.h"
45 #include "PackageContentsView.h"
46 #include "PackageInfo.h"
47 #include "PackageManager.h"
48 #include "RatingView.h"
49 #include "ScrollableGroupView.h"
50 #include "TextView.h"
51 
52 
53 #undef B_TRANSLATION_CONTEXT
54 #define B_TRANSLATION_CONTEXT "PackageInfoView"
55 
56 
57 enum {
58 	TAB_ABOUT		= 0,
59 	TAB_RATINGS		= 1,
60 	TAB_CHANGELOG	= 2,
61 	TAB_CONTENTS	= 3
62 };
63 
64 
65 static const float kContentTint = (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0f;
66 
67 
68 //! Layouts the scrollbar so it looks nice with no border and the document
69 // window look.
70 class CustomScrollView : public BScrollView {
71 public:
72 	CustomScrollView(const char* name, BView* target)
73 		:
74 		BScrollView(name, target, 0, false, true, B_NO_BORDER)
75 	{
76 	}
77 
78 	virtual void DoLayout()
79 	{
80 		BRect innerFrame = Bounds();
81 		innerFrame.right -= B_V_SCROLL_BAR_WIDTH + 1;
82 
83 		BView* target = Target();
84 		if (target != NULL) {
85 			Target()->MoveTo(innerFrame.left, innerFrame.top);
86 			Target()->ResizeTo(innerFrame.Width(), innerFrame.Height());
87 		}
88 
89 		BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
90 		if (scrollBar != NULL) {
91 			BRect rect = innerFrame;
92 			rect.left = rect.right + 1;
93 			rect.right = rect.left + B_V_SCROLL_BAR_WIDTH;
94 			rect.bottom -= B_H_SCROLL_BAR_HEIGHT;
95 
96 			scrollBar->MoveTo(rect.left, rect.top);
97 			scrollBar->ResizeTo(rect.Width(), rect.Height());
98 		}
99 	}
100 };
101 
102 
103 class RatingsScrollView : public CustomScrollView {
104 public:
105 	RatingsScrollView(const char* name, BView* target)
106 		:
107 		CustomScrollView(name, target)
108 	{
109 	}
110 
111 	virtual void DoLayout()
112 	{
113 		CustomScrollView::DoLayout();
114 
115 		BScrollBar* scrollBar = ScrollBar(B_VERTICAL);
116 		BView* target = Target();
117 		if (target != NULL && scrollBar != NULL) {
118 			// Set the scroll steps
119 			BView* item = target->ChildAt(0);
120 			if (item != NULL) {
121 				scrollBar->SetSteps(item->MinSize().height + 1,
122 					item->MinSize().height + 1);
123 			}
124 		}
125 	}
126 };
127 
128 
129 // #pragma mark - rating stats
130 
131 
132 class DiagramBarView : public BView {
133 public:
134 	DiagramBarView()
135 		:
136 		BView("diagram bar view", B_WILL_DRAW),
137 		fValue(0.0f)
138 	{
139 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
140 		SetHighUIColor(B_CONTROL_MARK_COLOR);
141 	}
142 
143 	virtual ~DiagramBarView()
144 	{
145 	}
146 
147 	virtual void AttachedToWindow()
148 	{
149 	}
150 
151 	virtual void Draw(BRect updateRect)
152 	{
153 		FillRect(updateRect, B_SOLID_LOW);
154 
155 		if (fValue <= 0.0f)
156 			return;
157 
158 		BRect rect(Bounds());
159 		rect.right = ceilf(rect.left + fValue * rect.Width());
160 
161 		FillRect(rect, B_SOLID_HIGH);
162 	}
163 
164 	virtual BSize MinSize()
165 	{
166 		return BSize(64, 10);
167 	}
168 
169 	virtual BSize PreferredSize()
170 	{
171 		return MinSize();
172 	}
173 
174 	virtual BSize MaxSize()
175 	{
176 		return BSize(64, 10);
177 	}
178 
179 	void SetValue(float value)
180 	{
181 		if (fValue != value) {
182 			fValue = value;
183 			Invalidate();
184 		}
185 	}
186 
187 private:
188 	float			fValue;
189 };
190 
191 
192 // #pragma mark - TitleView
193 
194 
195 enum {
196 	MSG_PACKAGE_ACTION			= 'pkga',
197 	MSG_MOUSE_ENTERED_RATING	= 'menr',
198 	MSG_MOUSE_EXITED_RATING		= 'mexr',
199 };
200 
201 
202 class TransitReportingButton : public BButton {
203 public:
204 	TransitReportingButton(const char* name, const char* label,
205 			BMessage* message)
206 		:
207 		BButton(name, label, message),
208 		fTransitMessage(NULL)
209 	{
210 	}
211 
212 	virtual ~TransitReportingButton()
213 	{
214 		SetTransitMessage(NULL);
215 	}
216 
217 	virtual void MouseMoved(BPoint point, uint32 transit,
218 		const BMessage* dragMessage)
219 	{
220 		BButton::MouseMoved(point, transit, dragMessage);
221 
222 		if (fTransitMessage != NULL && transit == B_EXITED_VIEW)
223 			Invoke(fTransitMessage);
224 	}
225 
226 	void SetTransitMessage(BMessage* message)
227 	{
228 		if (fTransitMessage != message) {
229 			delete fTransitMessage;
230 			fTransitMessage = message;
231 		}
232 	}
233 
234 private:
235 	BMessage*	fTransitMessage;
236 };
237 
238 
239 class TransitReportingRatingView : public RatingView, public BInvoker {
240 public:
241 	TransitReportingRatingView(BMessage* transitMessage)
242 		:
243 		RatingView("package rating view"),
244 		fTransitMessage(transitMessage)
245 	{
246 	}
247 
248 	virtual ~TransitReportingRatingView()
249 	{
250 		delete fTransitMessage;
251 	}
252 
253 	virtual void MouseMoved(BPoint point, uint32 transit,
254 		const BMessage* dragMessage)
255 	{
256 		RatingView::MouseMoved(point, transit, dragMessage);
257 
258 		if (fTransitMessage != NULL && transit == B_ENTERED_VIEW)
259 			Invoke(fTransitMessage);
260 	}
261 
262 private:
263 	BMessage*	fTransitMessage;
264 };
265 
266 
267 class TitleView : public BGroupView {
268 public:
269 	TitleView(PackageIconRepository& packageIconRepository)
270 		:
271 		BGroupView("title view", B_HORIZONTAL),
272 		fPackageIconRepository(packageIconRepository)
273 	{
274 		fIconView = new BitmapView("package icon view");
275 		fTitleView = new BStringView("package title view", "");
276 		fPublisherView = new BStringView("package publisher view", "");
277 
278 		// Title font
279 		BFont font;
280 		GetFont(&font);
281 		font_family family;
282 		font_style style;
283 		font.SetSize(ceilf(font.Size() * 1.5f));
284 		font.GetFamilyAndStyle(&family, &style);
285 		font.SetFamilyAndStyle(family, "Bold");
286 		fTitleView->SetFont(&font);
287 
288 		// Publisher font
289 		GetFont(&font);
290 		font.SetSize(std::max(9.0f, floorf(font.Size() * 0.92f)));
291 		font.SetFamilyAndStyle(family, "Italic");
292 		fPublisherView->SetFont(&font);
293 		fPublisherView->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT);
294 
295 		// slightly bigger font
296 		GetFont(&font);
297 		font.SetSize(ceilf(font.Size() * 1.2f));
298 
299 		// Version info
300 		fVersionInfo = new BStringView("package version info", "");
301 		fVersionInfo->SetFont(&font);
302 		fVersionInfo->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT);
303 
304 		// Rating view
305 		fRatingView = new TransitReportingRatingView(
306 			new BMessage(MSG_MOUSE_ENTERED_RATING));
307 
308 		fAvgRating = new BStringView("package average rating", "");
309 		fAvgRating->SetFont(&font);
310 		fAvgRating->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT);
311 
312 		fVoteInfo = new BStringView("package vote info", "");
313 		// small font
314 		GetFont(&font);
315 		font.SetSize(std::max(9.0f, floorf(font.Size() * 0.85f)));
316 		fVoteInfo->SetFont(&font);
317 		fVoteInfo->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT);
318 
319 		// Rate button
320 		fRateButton = new TransitReportingButton("rate",
321 			B_TRANSLATE("Rate package" B_UTF8_ELLIPSIS),
322 			new BMessage(MSG_RATE_PACKAGE));
323 		fRateButton->SetTransitMessage(new BMessage(MSG_MOUSE_EXITED_RATING));
324 		fRateButton->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
325 			B_ALIGN_VERTICAL_CENTER));
326 
327 		// Rating group
328 		BView* ratingStack = new BView("rating stack", 0);
329 		fRatingLayout = new BCardLayout();
330 		ratingStack->SetLayout(fRatingLayout);
331 		ratingStack->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
332 		ratingStack->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
333 
334 		BGroupView* ratingGroup = new BGroupView(B_HORIZONTAL,
335 			B_USE_SMALL_SPACING);
336 		BLayoutBuilder::Group<>(ratingGroup)
337 			.Add(fRatingView)
338 			.Add(fAvgRating)
339 			.Add(fVoteInfo)
340 		;
341 
342 		ratingStack->AddChild(ratingGroup);
343 		ratingStack->AddChild(fRateButton);
344 		fRatingLayout->SetVisibleItem((int32)0);
345 
346 		BLayoutBuilder::Group<>(this)
347 			.Add(fIconView)
348 			.AddGroup(B_VERTICAL, 1.0f, 2.2f)
349 				.Add(fTitleView)
350 				.Add(fPublisherView)
351 				.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
352 			.End()
353 			.AddGlue(0.1f)
354 			.Add(ratingStack, 0.8f)
355 			.AddGlue(0.2f)
356 			.AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING, 2.0f)
357 				.Add(fVersionInfo)
358 				.AddGlue()
359 				.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
360 			.End()
361 		;
362 
363 		Clear();
364 	}
365 
366 	virtual ~TitleView()
367 	{
368 	}
369 
370 	virtual void AttachedToWindow()
371 	{
372 		fRateButton->SetTarget(this);
373 		fRatingView->SetTarget(this);
374 	}
375 
376 	virtual void MessageReceived(BMessage* message)
377 	{
378 		switch (message->what) {
379 			case MSG_RATE_PACKAGE:
380 				// Forward to window (The button has us as target so
381 				// we receive the message below.)
382 				Window()->PostMessage(MSG_RATE_PACKAGE);
383 				break;
384 
385 			case MSG_MOUSE_ENTERED_RATING:
386 				fRatingLayout->SetVisibleItem(1);
387 				break;
388 
389 			case MSG_MOUSE_EXITED_RATING:
390 				fRatingLayout->SetVisibleItem((int32)0);
391 				break;
392 		}
393 	}
394 
395 	void SetPackage(const PackageInfo& package)
396 	{
397 		BitmapRef bitmap;
398 		status_t iconResult = fPackageIconRepository.GetIcon(
399 			package.Name(), BITMAP_SIZE_64, bitmap);
400 
401 		if (iconResult == B_OK)
402 			fIconView->SetBitmap(bitmap, BITMAP_SIZE_32);
403 		else
404 			fIconView->UnsetBitmap();
405 
406 		fTitleView->SetText(package.Title());
407 
408 		BString publisher = package.Publisher().Name();
409 		if (publisher.CountChars() > 45) {
410 			fPublisherView->SetToolTip(publisher);
411 			fPublisherView->SetText(publisher.TruncateChars(45)
412 				.Append(B_UTF8_ELLIPSIS));
413 		} else
414 			fPublisherView->SetText(publisher);
415 
416 		fVersionInfo->SetText(package.Version().ToString());
417 
418 		RatingSummary ratingSummary = package.CalculateRatingSummary();
419 
420 		fRatingView->SetRating(ratingSummary.averageRating);
421 
422 		if (ratingSummary.ratingCount > 0) {
423 			BString avgRating;
424 			avgRating.SetToFormat("%.1f", ratingSummary.averageRating);
425 			fAvgRating->SetText(avgRating);
426 
427 			BString votes;
428 			votes.SetToFormat("%d", ratingSummary.ratingCount);
429 
430 			BString voteInfo(B_TRANSLATE("(%Votes%)"));
431 			voteInfo.ReplaceAll("%Votes%", votes);
432 
433 			fVoteInfo->SetText(voteInfo);
434 		} else {
435 			fAvgRating->SetText("");
436 			fVoteInfo->SetText(B_TRANSLATE("n/a"));
437 		}
438 
439 		InvalidateLayout();
440 		Invalidate();
441 	}
442 
443 	void Clear()
444 	{
445 		fIconView->UnsetBitmap();
446 		fTitleView->SetText("");
447 		fPublisherView->SetText("");
448 		fVersionInfo->SetText("");
449 		fRatingView->SetRating(-1.0f);
450 		fAvgRating->SetText("");
451 		fVoteInfo->SetText("");
452 	}
453 
454 private:
455 	PackageIconRepository&			fPackageIconRepository;
456 
457 	BitmapView*						fIconView;
458 
459 	BStringView*					fTitleView;
460 	BStringView*					fPublisherView;
461 
462 	BStringView*					fVersionInfo;
463 
464 	BCardLayout*					fRatingLayout;
465 
466 	TransitReportingRatingView*		fRatingView;
467 	BStringView*					fAvgRating;
468 	BStringView*					fVoteInfo;
469 
470 	TransitReportingButton*			fRateButton;
471 };
472 
473 
474 // #pragma mark - PackageActionView
475 
476 
477 class PackageActionView : public BView {
478 public:
479 	PackageActionView(PackageActionHandler* handler)
480 		:
481 		BView("about view", B_WILL_DRAW),
482 		fLayout(new BGroupLayout(B_HORIZONTAL)),
483 		fPackageActionHandler(handler),
484 		fStatusLabel(NULL),
485 		fStatusBar(NULL)
486 	{
487 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
488 
489 		SetLayout(fLayout);
490 		fLayout->AddItem(BSpaceLayoutItem::CreateGlue());
491 	}
492 
493 	virtual ~PackageActionView()
494 	{
495 		Clear();
496 	}
497 
498 	virtual void MessageReceived(BMessage* message)
499 	{
500 		switch (message->what) {
501 			case MSG_PACKAGE_ACTION:
502 				_RunPackageAction(message);
503 				break;
504 
505 			default:
506 				BView::MessageReceived(message);
507 				break;
508 		}
509 	}
510 
511 	void SetPackage(const PackageInfo& package)
512 	{
513 		if (package.State() == DOWNLOADING) {
514 			AdoptDownloadProgress(package);
515 		} else {
516 			AdoptActions(package);
517 		}
518 	}
519 
520 	void AdoptActions(const PackageInfo& package)
521 	{
522 		PackageManager manager(
523 			BPackageKit::B_PACKAGE_INSTALLATION_LOCATION_HOME);
524 
525 		// TODO: if the given package is either a system package
526 		// or a system dependency, show a message indicating that status
527 		// so the user knows why no actions are presented
528 		PackageActionList actions = manager.GetPackageActions(
529 			const_cast<PackageInfo*>(&package),
530 			fPackageActionHandler->GetModel());
531 
532 		bool clearNeeded = fStatusBar != NULL;
533 		if (!clearNeeded) {
534 			if (actions.CountItems() != fPackageActions.CountItems())
535 				clearNeeded = true;
536 			else {
537 				for (int32 i = 0; i < actions.CountItems(); i++) {
538 					if (actions.ItemAtFast(i)->Type()
539 							!= fPackageActions.ItemAtFast(i)->Type()) {
540 						clearNeeded = true;
541 						break;
542 					}
543 				}
544 			}
545 		}
546 
547 		fPackageActions = actions;
548 		if (!clearNeeded && fButtons.CountItems() == actions.CountItems()) {
549 			int32 index = 0;
550 			for (int32 i = fPackageActions.CountItems() - 1; i >= 0; i--) {
551 				const PackageActionRef& action = fPackageActions.ItemAtFast(i);
552 				BButton* button = (BButton*)fButtons.ItemAtFast(index++);
553 				button->SetLabel(action->Label());
554 			}
555 			return;
556 		}
557 
558 		Clear();
559 
560 		// Add Buttons in reverse action order
561 		for (int32 i = fPackageActions.CountItems() - 1; i >= 0; i--) {
562 			const PackageActionRef& action = fPackageActions.ItemAtFast(i);
563 
564 			BMessage* message = new BMessage(MSG_PACKAGE_ACTION);
565 			message->AddInt32("index", i);
566 
567 			BButton* button = new BButton(action->Label(), message);
568 			fLayout->AddView(button);
569 			button->SetTarget(this);
570 
571 			fButtons.AddItem(button);
572 		}
573 	}
574 
575 	void AdoptDownloadProgress(const PackageInfo& package)
576 	{
577 		if (fButtons.CountItems() > 0)
578 			Clear();
579 
580 		if (fStatusBar == NULL) {
581 			fStatusLabel = new BStringView("progress label",
582 				B_TRANSLATE("Downloading:"));
583 			fLayout->AddView(fStatusLabel);
584 
585 			fStatusBar = new BStatusBar("progress");
586 			fStatusBar->SetMaxValue(100.0);
587 			fStatusBar->SetExplicitMinSize(
588 				BSize(StringWidth("XXX") * 5, B_SIZE_UNSET));
589 
590 			fLayout->AddView(fStatusBar);
591 		}
592 
593 		fStatusBar->SetTo(package.DownloadProgress() * 100.0);
594 	}
595 
596 	void Clear()
597 	{
598 		for (int32 i = fButtons.CountItems() - 1; i >= 0; i--) {
599 			BButton* button = (BButton*)fButtons.ItemAtFast(i);
600 			button->RemoveSelf();
601 			delete button;
602 		}
603 		fButtons.MakeEmpty();
604 
605 		if (fStatusBar != NULL) {
606 			fStatusBar->RemoveSelf();
607 			delete fStatusBar;
608 			fStatusBar = NULL;
609 		}
610 		if (fStatusLabel != NULL) {
611 			fStatusLabel->RemoveSelf();
612 			delete fStatusLabel;
613 			fStatusLabel = NULL;
614 		}
615 	}
616 
617 private:
618 	void _RunPackageAction(BMessage* message)
619 	{
620 		int32 index;
621 		if (message->FindInt32("index", &index) != B_OK)
622 			return;
623 
624 		const PackageActionRef& action = fPackageActions.ItemAt(index);
625 		if (action.Get() == NULL)
626 			return;
627 
628 		PackageActionList actions;
629 		actions.Add(action);
630 		status_t result
631 			= fPackageActionHandler->SchedulePackageActions(actions);
632 
633 		if (result != B_OK) {
634 			HDERROR("Failed to schedule action: %s '%s': %s",
635 				action->Label(),
636 				action->Package()->Name().String(),
637 				strerror(result));
638 			BString message(B_TRANSLATE("The package action "
639 				"could not be scheduled: %Error%"));
640 			message.ReplaceAll("%Error%", strerror(result));
641 			BAlert* alert = new(std::nothrow) BAlert(
642 				B_TRANSLATE("Package action failed"),
643 				message, B_TRANSLATE("OK"), NULL, NULL,
644 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
645 			if (alert != NULL)
646 				alert->Go();
647 		} else {
648 			// Find the button for this action and disable it.
649 			// Actually search the matching button instead of just using
650 			// fButtons.ItemAt((fButtons.CountItems() - 1) - index) to
651 			// make this robust against for example changing the order of
652 			// buttons from right -> left to left -> right...
653 			for (int32 i = 0; i < fButtons.CountItems(); i++) {
654 				BButton* button = (BButton*)fButtons.ItemAt(index);
655 				if (button == NULL)
656 					continue;
657 				BMessage* buttonMessage = button->Message();
658 				if (buttonMessage == NULL)
659 					continue;
660 				int32 buttonIndex;
661 				if (buttonMessage->FindInt32("index", &buttonIndex) != B_OK)
662 					continue;
663 				if (buttonIndex == index) {
664 					button->SetEnabled(false);
665 					break;
666 				}
667 			}
668 		}
669 	}
670 
671 private:
672 	BGroupLayout*		fLayout;
673 	PackageActionList	fPackageActions;
674 	PackageActionHandler* fPackageActionHandler;
675 	BList				fButtons;
676 
677 	BStringView*		fStatusLabel;
678 	BStatusBar*			fStatusBar;
679 };
680 
681 
682 // #pragma mark - AboutView
683 
684 
685 enum {
686 	MSG_EMAIL_PUBLISHER				= 'emlp',
687 	MSG_VISIT_PUBLISHER_WEBSITE		= 'vpws',
688 };
689 
690 
691 class AboutView : public BView {
692 public:
693 	AboutView()
694 		:
695 		BView("about view", 0),
696 		fEmailIcon("text/x-email"),
697 		fWebsiteIcon("text/html")
698 	{
699 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
700 
701 		fDescriptionView = new MarkupTextView("description view");
702 		fDescriptionView->SetViewUIColor(ViewUIColor(), kContentTint);
703 		fDescriptionView->SetInsets(be_plain_font->Size());
704 
705 		BScrollView* scrollView = new CustomScrollView(
706 			"description scroll view", fDescriptionView);
707 
708 		BFont smallFont;
709 		GetFont(&smallFont);
710 		smallFont.SetSize(std::max(9.0f, ceilf(smallFont.Size() * 0.85f)));
711 
712 		// TODO: Clicking the screen shot view should open ShowImage with the
713 		// the screen shot. This could be done by writing the screen shot to
714 		// a temporary folder, launching ShowImage to display it, and writing
715 		// all other screenshots associated with the package to the same folder
716 		// so the user can use the ShowImage navigation to view the other
717 		// screenshots.
718 		fScreenshotView = new LinkedBitmapView("screenshot view",
719 			new BMessage(MSG_SHOW_SCREENSHOT));
720 		fScreenshotView->SetExplicitMinSize(BSize(64.0f, 64.0f));
721 		fScreenshotView->SetExplicitMaxSize(
722 			BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
723 		fScreenshotView->SetExplicitAlignment(
724 			BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP));
725 
726 		fEmailIconView = new BitmapView("email icon view");
727 		fEmailLinkView = new LinkView("email link view", "",
728 			new BMessage(MSG_EMAIL_PUBLISHER));
729 		fEmailLinkView->SetFont(&smallFont);
730 
731 		fWebsiteIconView = new BitmapView("website icon view");
732 		fWebsiteLinkView = new LinkView("website link view", "",
733 			new BMessage(MSG_VISIT_PUBLISHER_WEBSITE));
734 		fWebsiteLinkView->SetFont(&smallFont);
735 
736 		BGroupView* leftGroup = new BGroupView(B_VERTICAL,
737 			B_USE_DEFAULT_SPACING);
738 
739 		fScreenshotView->SetViewUIColor(ViewUIColor(), kContentTint);
740 		fEmailLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
741 		fWebsiteLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
742 
743 		BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f)
744 			.AddGroup(leftGroup, 1.0f)
745 				.Add(fScreenshotView)
746 				.AddGroup(B_HORIZONTAL)
747 					.AddGrid(B_USE_HALF_ITEM_SPACING, B_USE_HALF_ITEM_SPACING)
748 						.Add(fEmailIconView, 0, 0)
749 						.Add(fEmailLinkView, 1, 0)
750 						.Add(fWebsiteIconView, 0, 1)
751 						.Add(fWebsiteLinkView, 1, 1)
752 					.End()
753 				.End()
754 				.SetInsets(B_USE_DEFAULT_SPACING)
755 				.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
756 			.End()
757 			.Add(scrollView, 2.0f)
758 
759 			.SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED))
760 			.SetInsets(0.0f, -1.0f, -1.0f, -1.0f)
761 		;
762 	}
763 
764 	virtual ~AboutView()
765 	{
766 		Clear();
767 	}
768 
769 	virtual void AttachedToWindow()
770 	{
771 		fScreenshotView->SetTarget(this);
772 		fEmailLinkView->SetTarget(this);
773 		fWebsiteLinkView->SetTarget(this);
774 	}
775 
776 	virtual void AllAttached()
777 	{
778 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
779 
780 		for (int32 index = 0; index < CountChildren(); ++index)
781 			ChildAt(index)->AdoptParentColors();
782 	}
783 
784 	virtual void MessageReceived(BMessage* message)
785 	{
786 		switch (message->what) {
787 			case MSG_SHOW_SCREENSHOT:
788 			{
789 				// Forward to window for now
790 				Window()->PostMessage(message, Window());
791 				break;
792 			}
793 
794 			case MSG_EMAIL_PUBLISHER:
795 			{
796 				// TODO: Implement. If memory serves, there is a
797 				// standard command line interface which mail apps should
798 				// support, i.e. to open a compose window with the TO: field
799 				// already set.
800 				break;
801 			}
802 
803 			case MSG_VISIT_PUBLISHER_WEBSITE:
804 			{
805 				BUrl url(fWebsiteLinkView->Text());
806 				url.OpenWithPreferredApplication();
807 				break;
808 			}
809 
810 			default:
811 				BView::MessageReceived(message);
812 				break;
813 		}
814 	}
815 
816 	void SetPackage(const PackageInfo& package)
817 	{
818 		fDescriptionView->SetText(package.ShortDescription(),
819 			package.FullDescription());
820 
821 		fEmailIconView->SetBitmap(&fEmailIcon, BITMAP_SIZE_16);
822 		_SetContactInfo(fEmailLinkView, package.Publisher().Email());
823 		fWebsiteIconView->SetBitmap(&fWebsiteIcon, BITMAP_SIZE_16);
824 		_SetContactInfo(fWebsiteLinkView, package.Publisher().Website());
825 
826 		int32 countScreenshots = package.CountScreenshots();
827 		bool hasScreenshot = false;
828 		if (countScreenshots > 0) {
829 			const BitmapRef& bitmapRef = package.ScreenshotAtIndex(0);
830 			if (bitmapRef.Get() != NULL) {
831 				HDDEBUG("did find screenshot for package [%s]",
832 					package.Name().String());
833 				hasScreenshot = true;
834 				fScreenshotView->SetBitmap(bitmapRef);
835 			}
836 		}
837 		else {
838 			HDTRACE("did not find screenshots for package [%s]",
839 				package.Name().String());
840 		}
841 
842 		if (!hasScreenshot)
843 			fScreenshotView->UnsetBitmap();
844 
845 		fScreenshotView->SetEnabled(hasScreenshot);
846 	}
847 
848 	void Clear()
849 	{
850 		fDescriptionView->SetText("");
851 		fEmailIconView->UnsetBitmap();
852 		fEmailLinkView->SetText("");
853 		fWebsiteIconView->UnsetBitmap();
854 		fWebsiteLinkView->SetText("");
855 
856 		fScreenshotView->UnsetBitmap();
857 		fScreenshotView->SetEnabled(false);
858 	}
859 
860 private:
861 	void _SetContactInfo(LinkView* view, const BString& string)
862 	{
863 		if (string.Length() > 0) {
864 			view->SetText(string);
865 			view->SetEnabled(true);
866 		} else {
867 			view->SetText(B_TRANSLATE("<no info>"));
868 			view->SetEnabled(false);
869 		}
870 	}
871 
872 private:
873 	MarkupTextView*		fDescriptionView;
874 
875 	LinkedBitmapView*	fScreenshotView;
876 
877 	SharedBitmap		fEmailIcon;
878 	BitmapView*			fEmailIconView;
879 	LinkView*			fEmailLinkView;
880 
881 	SharedBitmap		fWebsiteIcon;
882 	BitmapView*			fWebsiteIconView;
883 	LinkView*			fWebsiteLinkView;
884 };
885 
886 
887 // #pragma mark - UserRatingsView
888 
889 
890 class RatingItemView : public BGroupView {
891 public:
892 	RatingItemView(const UserRating& rating)
893 		:
894 		BGroupView(B_HORIZONTAL, 0.0f)
895 	{
896 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
897 
898 		BGroupLayout* verticalGroup = new BGroupLayout(B_VERTICAL, 0.0f);
899 		GroupLayout()->AddItem(verticalGroup);
900 
901 		{
902 			BStringView* userNicknameView = new BStringView("user-nickname",
903 				rating.User().NickName());
904 			userNicknameView->SetFont(be_bold_font);
905 			verticalGroup->AddView(userNicknameView);
906 		}
907 
908 		BGroupLayout* ratingGroup =
909 			new BGroupLayout(B_HORIZONTAL, B_USE_DEFAULT_SPACING);
910 		verticalGroup->AddItem(ratingGroup);
911 
912 		if (rating.Rating() >= 0) {
913 			RatingView* ratingView = new RatingView("package rating view");
914 			ratingView->SetRating(rating.Rating());
915 			ratingGroup->AddView(ratingView);
916 		}
917 
918 		{
919 			BString createTimestampPresentation =
920 				LocaleUtils::TimestampToDateTimeString(
921 					rating.CreateTimestamp());
922 
923 			BString ratingContextDescription(
924 				B_TRANSLATE("%hd.timestamp% (version %hd.version%)"));
925 			ratingContextDescription.ReplaceAll("%hd.timestamp%",
926 				createTimestampPresentation);
927 			ratingContextDescription.ReplaceAll("%hd.version%",
928 				rating.PackageVersion());
929 
930 			BStringView* ratingContextView = new BStringView("rating-context",
931 				ratingContextDescription);
932 			BFont versionFont(be_plain_font);
933 			ratingContextView->SetFont(&versionFont);
934 			ratingGroup->AddView(ratingContextView);
935 		}
936 
937 		ratingGroup->AddItem(BSpaceLayoutItem::CreateGlue());
938 
939 		if (rating.Comment() > 0) {
940 			TextView* textView = new TextView("rating-text");
941 			ParagraphStyle paragraphStyle(textView->ParagraphStyle());
942 			paragraphStyle.SetJustify(true);
943 			textView->SetParagraphStyle(paragraphStyle);
944 			textView->SetText(rating.Comment());
945 			verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f));
946 			verticalGroup->AddView(textView);
947 			verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f));
948 		}
949 
950 		verticalGroup->SetInsets(B_USE_DEFAULT_SPACING);
951 
952 		SetFlags(Flags() | B_WILL_DRAW);
953 	}
954 
955 	void AllAttached()
956 	{
957 		for (int32 index = 0; index < CountChildren(); ++index)
958 			ChildAt(index)->AdoptParentColors();
959 	}
960 
961 	void Draw(BRect rect)
962 	{
963 		rgb_color color = mix_color(ViewColor(), ui_color(B_PANEL_TEXT_COLOR), 64);
964 		SetHighColor(color);
965 		StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom());
966 	}
967 
968 };
969 
970 
971 class RatingSummaryView : public BGridView {
972 public:
973 	RatingSummaryView()
974 		:
975 		BGridView("rating summary view", B_USE_HALF_ITEM_SPACING, 0.0f)
976 	{
977 		float tint = kContentTint - 0.1;
978 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, tint);
979 
980 		BLayoutBuilder::Grid<> layoutBuilder(this);
981 
982 		BFont smallFont;
983 		GetFont(&smallFont);
984 		smallFont.SetSize(std::max(9.0f, floorf(smallFont.Size() * 0.85f)));
985 
986 		for (int32 i = 0; i < 5; i++) {
987 			BString label;
988 			label.SetToFormat("%" B_PRId32, 5 - i);
989 			fLabelViews[i] = new BStringView("", label);
990 			fLabelViews[i]->SetFont(&smallFont);
991 			fLabelViews[i]->SetViewUIColor(ViewUIColor(), tint);
992 			layoutBuilder.Add(fLabelViews[i], 0, i);
993 
994 			fDiagramBarViews[i] = new DiagramBarView();
995 			layoutBuilder.Add(fDiagramBarViews[i], 1, i);
996 
997 			fCountViews[i] = new BStringView("", "");
998 			fCountViews[i]->SetFont(&smallFont);
999 			fCountViews[i]->SetViewUIColor(ViewUIColor(), tint);
1000 			fCountViews[i]->SetAlignment(B_ALIGN_RIGHT);
1001 			layoutBuilder.Add(fCountViews[i], 2, i);
1002 		}
1003 
1004 		layoutBuilder.SetInsets(5);
1005 	}
1006 
1007 	void SetToSummary(const RatingSummary& summary) {
1008 		for (int32 i = 0; i < 5; i++) {
1009 			int32 count = summary.ratingCountByStar[4 - i];
1010 
1011 			BString label;
1012 			label.SetToFormat("%" B_PRId32, count);
1013 			fCountViews[i]->SetText(label);
1014 
1015 			if (summary.ratingCount > 0) {
1016 				fDiagramBarViews[i]->SetValue(
1017 					(float)count / summary.ratingCount);
1018 			} else
1019 				fDiagramBarViews[i]->SetValue(0.0f);
1020 		}
1021 	}
1022 
1023 	void Clear() {
1024 		for (int32 i = 0; i < 5; i++) {
1025 			fCountViews[i]->SetText("");
1026 			fDiagramBarViews[i]->SetValue(0.0f);
1027 		}
1028 	}
1029 
1030 private:
1031 	BStringView*	fLabelViews[5];
1032 	DiagramBarView*	fDiagramBarViews[5];
1033 	BStringView*	fCountViews[5];
1034 };
1035 
1036 
1037 class UserRatingsView : public BGroupView {
1038 public:
1039 	UserRatingsView()
1040 		:
1041 		BGroupView("package ratings view", B_HORIZONTAL)
1042 	{
1043 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1044 
1045 		fRatingSummaryView = new RatingSummaryView();
1046 
1047 		ScrollableGroupView* ratingsContainerView = new ScrollableGroupView();
1048 		ratingsContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR,
1049 												kContentTint);
1050 		fRatingContainerLayout = ratingsContainerView->GroupLayout();
1051 
1052 		BScrollView* scrollView = new RatingsScrollView(
1053 			"ratings scroll view", ratingsContainerView);
1054 		scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
1055 			B_SIZE_UNLIMITED));
1056 
1057 		BLayoutBuilder::Group<>(this)
1058 			.AddGroup(B_VERTICAL)
1059 				.Add(fRatingSummaryView, 0.0f)
1060 				.AddGlue()
1061 				.SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f)
1062 			.End()
1063 			.AddStrut(64.0)
1064 			.Add(scrollView, 1.0f)
1065 			.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
1066 		;
1067 	}
1068 
1069 	virtual ~UserRatingsView()
1070 	{
1071 		Clear();
1072 	}
1073 
1074 	void SetPackage(const PackageInfo& package)
1075 	{
1076 		ClearRatings();
1077 
1078 		// TODO: Re-use rating summary already used for TitleView...
1079 		fRatingSummaryView->SetToSummary(package.CalculateRatingSummary());
1080 
1081 		const UserRatingList& userRatings = package.UserRatings();
1082 
1083 		int count = userRatings.CountItems();
1084 		if (count == 0) {
1085 			BStringView* noRatingsView = new BStringView("no ratings",
1086 				B_TRANSLATE("No user ratings available."));
1087 			noRatingsView->SetViewUIColor(ViewUIColor(), kContentTint);
1088 			noRatingsView->SetAlignment(B_ALIGN_CENTER);
1089 			noRatingsView->SetHighColor(disable_color(ui_color(B_PANEL_TEXT_COLOR),
1090 				ViewColor()));
1091 			noRatingsView->SetExplicitMaxSize(
1092 				BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
1093 			fRatingContainerLayout->AddView(0, noRatingsView);
1094 			return;
1095 		}
1096 
1097 		// TODO: Sort by age or usefullness rating
1098 		for (int i = count - 1; i >= 0; i--) {
1099 			const UserRating& rating = userRatings.ItemAtFast(i);
1100 				// was previously filtering comments just for the current
1101 				// user's language, but as there are not so many comments at
1102 				// the moment, just show all of them for now.
1103 			RatingItemView* view = new RatingItemView(rating);
1104 			fRatingContainerLayout->AddView(0, view);
1105 		}
1106 	}
1107 
1108 	void Clear()
1109 	{
1110 		fRatingSummaryView->Clear();
1111 		ClearRatings();
1112 	}
1113 
1114 	void ClearRatings()
1115 	{
1116 		for (int32 i = fRatingContainerLayout->CountItems() - 1;
1117 				BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) {
1118 			BView* view = dynamic_cast<RatingItemView*>(item->View());
1119 			if (view == NULL)
1120 				view = dynamic_cast<BStringView*>(item->View());
1121 			if (view != NULL) {
1122 				view->RemoveSelf();
1123 				delete view;
1124 			}
1125 		}
1126 	}
1127 
1128 private:
1129 	BGroupLayout*			fRatingContainerLayout;
1130 	RatingSummaryView*		fRatingSummaryView;
1131 };
1132 
1133 
1134 // #pragma mark - ContentsView
1135 
1136 
1137 class ContentsView : public BGroupView {
1138 public:
1139 	ContentsView()
1140 		:
1141 		BGroupView("package contents view", B_HORIZONTAL)
1142 	{
1143 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1144 
1145 		fPackageContents = new PackageContentsView("contents_list");
1146 		AddChild(fPackageContents);
1147 
1148 	}
1149 
1150 	virtual ~ContentsView()
1151 	{
1152 	}
1153 
1154 	virtual void Draw(BRect updateRect)
1155 	{
1156 	}
1157 
1158 	void SetPackage(const PackageInfoRef& package)
1159 	{
1160 		fPackageContents->SetPackage(package);
1161 	}
1162 
1163 	void Clear()
1164 	{
1165 		fPackageContents->Clear();
1166 	}
1167 
1168 private:
1169 	PackageContentsView*	fPackageContents;
1170 };
1171 
1172 
1173 // #pragma mark - ChangelogView
1174 
1175 
1176 class ChangelogView : public BGroupView {
1177 public:
1178 	ChangelogView()
1179 		:
1180 		BGroupView("package changelog view", B_HORIZONTAL)
1181 	{
1182 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1183 
1184 		fTextView = new MarkupTextView("changelog view");
1185 		fTextView->SetLowUIColor(ViewUIColor());
1186 		fTextView->SetInsets(be_plain_font->Size());
1187 
1188 		BScrollView* scrollView = new CustomScrollView(
1189 			"changelog scroll view", fTextView);
1190 
1191 		BLayoutBuilder::Group<>(this)
1192 			.Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f))
1193 			.Add(scrollView, 1.0f)
1194 			.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
1195 		;
1196 	}
1197 
1198 	virtual ~ChangelogView()
1199 	{
1200 	}
1201 
1202 	virtual void Draw(BRect updateRect)
1203 	{
1204 	}
1205 
1206 	void SetPackage(const PackageInfo& package)
1207 	{
1208 		const BString& changelog = package.Changelog();
1209 		if (changelog.Length() > 0)
1210 			fTextView->SetText(changelog);
1211 		else
1212 			fTextView->SetDisabledText(B_TRANSLATE("No changelog available."));
1213 	}
1214 
1215 	void Clear()
1216 	{
1217 		fTextView->SetText("");
1218 	}
1219 
1220 private:
1221 	MarkupTextView*		fTextView;
1222 };
1223 
1224 
1225 // #pragma mark - PagesView
1226 
1227 
1228 class PagesView : public BTabView {
1229 public:
1230 	PagesView()
1231 		:
1232 		BTabView("pages view", B_WIDTH_FROM_WIDEST)
1233 	{
1234 		SetBorder(B_NO_BORDER);
1235 
1236 		fAboutView = new AboutView();
1237 		fUserRatingsView = new UserRatingsView();
1238 		fChangelogView = new ChangelogView();
1239 		fContentsView = new ContentsView();
1240 
1241 		AddTab(fAboutView);
1242 		AddTab(fUserRatingsView);
1243 		AddTab(fChangelogView);
1244 		AddTab(fContentsView);
1245 
1246 		TabAt(TAB_ABOUT)->SetLabel(B_TRANSLATE("About"));
1247 		TabAt(TAB_RATINGS)->SetLabel(B_TRANSLATE("Ratings"));
1248 		TabAt(TAB_CHANGELOG)->SetLabel(B_TRANSLATE("Changelog"));
1249 		TabAt(TAB_CONTENTS)->SetLabel(B_TRANSLATE("Contents"));
1250 
1251 		Select(TAB_ABOUT);
1252 	}
1253 
1254 	virtual ~PagesView()
1255 	{
1256 		Clear();
1257 	}
1258 
1259 	void SetPackage(const PackageInfoRef& package, bool switchToDefaultTab)
1260 	{
1261 		if (switchToDefaultTab)
1262 			Select(TAB_ABOUT);
1263 
1264 		TabAt(TAB_CHANGELOG)->SetEnabled(
1265 			package.Get() != NULL && package->HasChangelog());
1266 		TabAt(TAB_CONTENTS)->SetEnabled(
1267 			package.Get() != NULL
1268 				&& (package->State() == ACTIVATED || package->IsLocalFile()));
1269 		Invalidate(TabFrame(TAB_CHANGELOG));
1270 		Invalidate(TabFrame(TAB_CONTENTS));
1271 
1272 		fAboutView->SetPackage(*package.Get());
1273 		fUserRatingsView->SetPackage(*package.Get());
1274 		fChangelogView->SetPackage(*package.Get());
1275 		fContentsView->SetPackage(package);
1276 	}
1277 
1278 	void Clear()
1279 	{
1280 		fAboutView->Clear();
1281 		fUserRatingsView->Clear();
1282 		fChangelogView->Clear();
1283 		fContentsView->Clear();
1284 	}
1285 
1286 private:
1287 	AboutView*			fAboutView;
1288 	UserRatingsView*	fUserRatingsView;
1289 	ChangelogView*		fChangelogView;
1290 	ContentsView* 		fContentsView;
1291 };
1292 
1293 
1294 // #pragma mark - PackageInfoView
1295 
1296 
1297 PackageInfoView::PackageInfoView(Model* model,
1298 		PackageActionHandler* handler)
1299 	:
1300 	BView("package info view", 0),
1301 	fModel(model),
1302 	fPackageListener(new(std::nothrow) OnePackageMessagePackageListener(this))
1303 {
1304 	fCardLayout = new BCardLayout();
1305 	SetLayout(fCardLayout);
1306 
1307 	BGroupView* noPackageCard = new BGroupView("no package card", B_VERTICAL);
1308 	AddChild(noPackageCard);
1309 
1310 	BStringView* noPackageView = new BStringView("no package view",
1311 		B_TRANSLATE("Click a package to view information"));
1312 	noPackageView->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT);
1313 	noPackageView->SetExplicitAlignment(BAlignment(
1314 		B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
1315 
1316 	BLayoutBuilder::Group<>(noPackageCard)
1317 		.Add(noPackageView)
1318 		.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED))
1319 	;
1320 
1321 	BGroupView* packageCard = new BGroupView("package card", B_VERTICAL);
1322 	AddChild(packageCard);
1323 
1324 	fCardLayout->SetVisibleItem((int32)0);
1325 
1326 	fTitleView = new TitleView(fModel->GetPackageIconRepository());
1327 	fPackageActionView = new PackageActionView(handler);
1328 	fPackageActionView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
1329 		B_SIZE_UNSET));
1330 	fPagesView = new PagesView();
1331 
1332 	BLayoutBuilder::Group<>(packageCard)
1333 		.AddGroup(B_HORIZONTAL, 0.0f)
1334 			.Add(fTitleView, 6.0f)
1335 			.Add(fPackageActionView, 1.0f)
1336 			.SetInsets(
1337 				B_USE_DEFAULT_SPACING, 0.0f,
1338 				B_USE_DEFAULT_SPACING, 0.0f)
1339 		.End()
1340 		.Add(fPagesView)
1341 	;
1342 
1343 	Clear();
1344 }
1345 
1346 
1347 PackageInfoView::~PackageInfoView()
1348 {
1349 	fPackageListener->SetPackage(PackageInfoRef(NULL));
1350 	delete fPackageListener;
1351 }
1352 
1353 
1354 void
1355 PackageInfoView::AttachedToWindow()
1356 {
1357 }
1358 
1359 
1360 void
1361 PackageInfoView::MessageReceived(BMessage* message)
1362 {
1363 	switch (message->what) {
1364 		case MSG_UPDATE_PACKAGE:
1365 		{
1366 			if (fPackageListener->Package().Get() == NULL)
1367 				break;
1368 
1369 			BString name;
1370 			uint32 changes;
1371 			if (message->FindString("name", &name) != B_OK
1372 				|| message->FindUInt32("changes", &changes) != B_OK) {
1373 				break;
1374 			}
1375 
1376 			const PackageInfoRef& package = fPackageListener->Package();
1377 			if (package->Name() != name)
1378 				break;
1379 
1380 			BAutolock _(fModel->Lock());
1381 
1382 			if ((changes & PKG_CHANGED_SUMMARY) != 0
1383 				|| (changes & PKG_CHANGED_DESCRIPTION) != 0
1384 				|| (changes & PKG_CHANGED_SCREENSHOTS) != 0
1385 				|| (changes & PKG_CHANGED_TITLE) != 0
1386 				|| (changes & PKG_CHANGED_RATINGS) != 0
1387 				|| (changes & PKG_CHANGED_STATE) != 0
1388 				|| (changes & PKG_CHANGED_CHANGELOG) != 0) {
1389 				fPagesView->SetPackage(package, false);
1390 			}
1391 
1392 			if ((changes & PKG_CHANGED_TITLE) != 0
1393 				|| (changes & PKG_CHANGED_RATINGS) != 0) {
1394 				fTitleView->SetPackage(*package.Get());
1395 			}
1396 
1397 			if ((changes & PKG_CHANGED_STATE) != 0)
1398 				fPackageActionView->SetPackage(*package.Get());
1399 
1400 			break;
1401 		}
1402 		default:
1403 			BView::MessageReceived(message);
1404 			break;
1405 	}
1406 }
1407 
1408 
1409 void
1410 PackageInfoView::SetPackage(const PackageInfoRef& packageRef)
1411 {
1412 	BAutolock _(fModel->Lock());
1413 
1414 	if (packageRef.Get() == NULL) {
1415 		Clear();
1416 		return;
1417 	}
1418 
1419 	bool switchToDefaultTab = true;
1420 	if (fPackage == packageRef) {
1421 		// When asked to display the already showing package ref,
1422 		// don't switch to the default tab.
1423 		switchToDefaultTab = false;
1424 	} else if (fPackage.Get() != NULL && packageRef.Get() != NULL
1425 		&& fPackage->Name() == packageRef->Name()) {
1426 		// When asked to display a different PackageInfo instance,
1427 		// but it has the same package title as the already showing
1428 		// instance, this probably means there was a repository
1429 		// refresh and we are in fact still requested to show the
1430 		// same package as before the refresh.
1431 		switchToDefaultTab = false;
1432 	}
1433 
1434 	const PackageInfo& package = *packageRef.Get();
1435 
1436 	fTitleView->SetPackage(package);
1437 	fPackageActionView->SetPackage(package);
1438 	fPagesView->SetPackage(packageRef, switchToDefaultTab);
1439 
1440 	fCardLayout->SetVisibleItem(1);
1441 
1442 	fPackageListener->SetPackage(packageRef);
1443 
1444 	// Set the fPackage reference last, so we keep a reference to the
1445 	// previous package before switching all the views to the new package.
1446 	// Otherwise the PackageInfo instance may go away because we had the
1447 	// last reference. And some of the views, the PackageActionView for
1448 	// example, keeps references to stuff from the previous package and
1449 	// access it while switching to the new package.
1450 	fPackage = packageRef;
1451 }
1452 
1453 
1454 void
1455 PackageInfoView::Clear()
1456 {
1457 	BAutolock _(fModel->Lock());
1458 
1459 	fTitleView->Clear();
1460 	fPackageActionView->Clear();
1461 	fPagesView->Clear();
1462 
1463 	fCardLayout->SetVisibleItem((int32)0);
1464 
1465 	fPackageListener->SetPackage(PackageInfoRef(NULL));
1466 
1467 	fPackage.Unset();
1468 }
1469