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