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