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