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