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