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