xref: /haiku/src/apps/haikudepot/ui/PackageInfoView.cpp (revision 1978089f7cec856677e46204e992c7273d70b9af)
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_EMAIL_PUBLISHER				= 'emlp',
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 		fEmailIcon("text/x-email"),
646 		fWebsiteIcon("text/html")
647 	{
648 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
649 
650 		fDescriptionView = new MarkupTextView("description view");
651 		fDescriptionView->SetViewUIColor(ViewUIColor(), kContentTint);
652 		fDescriptionView->SetInsets(be_plain_font->Size());
653 
654 		BScrollView* scrollView = new GeneralContentScrollView(
655 			"description scroll view", fDescriptionView);
656 
657 		BFont smallFont;
658 		GetFont(&smallFont);
659 		smallFont.SetSize(std::max(9.0f, ceilf(smallFont.Size() * 0.85f)));
660 
661 		// TODO: Clicking the screen shot view should open ShowImage with the
662 		// the screen shot. This could be done by writing the screen shot to
663 		// a temporary folder, launching ShowImage to display it, and writing
664 		// all other screenshots associated with the package to the same folder
665 		// so the user can use the ShowImage navigation to view the other
666 		// screenshots.
667 		fScreenshotView = new LinkedBitmapView("screenshot view",
668 			new BMessage(MSG_SHOW_SCREENSHOT));
669 		fScreenshotView->SetExplicitMinSize(BSize(64.0f, 64.0f));
670 		fScreenshotView->SetExplicitMaxSize(
671 			BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
672 		fScreenshotView->SetExplicitAlignment(
673 			BAlignment(B_ALIGN_CENTER, B_ALIGN_TOP));
674 
675 		fEmailIconView = new BitmapView("email icon view");
676 		fEmailLinkView = new LinkView("email link view", "",
677 			new BMessage(MSG_EMAIL_PUBLISHER));
678 		fEmailLinkView->SetFont(&smallFont);
679 
680 		fWebsiteIconView = new BitmapView("website icon view");
681 		fWebsiteLinkView = new LinkView("website link view", "",
682 			new BMessage(MSG_VISIT_PUBLISHER_WEBSITE));
683 		fWebsiteLinkView->SetFont(&smallFont);
684 
685 		BGroupView* leftGroup = new BGroupView(B_VERTICAL,
686 			B_USE_DEFAULT_SPACING);
687 
688 		fScreenshotView->SetViewUIColor(ViewUIColor(), kContentTint);
689 		fEmailLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
690 		fWebsiteLinkView->SetViewUIColor(ViewUIColor(), kContentTint);
691 
692 		BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f)
693 			.AddGroup(leftGroup, 1.0f)
694 				.Add(fScreenshotView)
695 				.AddGroup(B_HORIZONTAL)
696 					.AddGrid(B_USE_HALF_ITEM_SPACING, B_USE_HALF_ITEM_SPACING)
697 						.Add(fEmailIconView, 0, 0)
698 						.Add(fEmailLinkView, 1, 0)
699 						.Add(fWebsiteIconView, 0, 1)
700 						.Add(fWebsiteLinkView, 1, 1)
701 					.End()
702 				.End()
703 				.SetInsets(B_USE_DEFAULT_SPACING)
704 				.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET))
705 			.End()
706 			.Add(scrollView, 2.0f)
707 
708 			.SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED))
709 			.SetInsets(0.0f, -1.0f, -1.0f, -1.0f)
710 		;
711 	}
712 
713 	virtual ~AboutView()
714 	{
715 		Clear();
716 	}
717 
718 	virtual void AttachedToWindow()
719 	{
720 		fScreenshotView->SetTarget(this);
721 		fEmailLinkView->SetTarget(this);
722 		fWebsiteLinkView->SetTarget(this);
723 	}
724 
725 	virtual void AllAttached()
726 	{
727 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
728 
729 		for (int32 index = 0; index < CountChildren(); ++index)
730 			ChildAt(index)->AdoptParentColors();
731 	}
732 
733 	virtual void MessageReceived(BMessage* message)
734 	{
735 		switch (message->what) {
736 			case MSG_SHOW_SCREENSHOT:
737 			{
738 				// Forward to window for now
739 				Window()->PostMessage(message, Window());
740 				break;
741 			}
742 
743 			case MSG_EMAIL_PUBLISHER:
744 			{
745 				// TODO: Implement. If memory serves, there is a
746 				// standard command line interface which mail apps should
747 				// support, i.e. to open a compose window with the TO: field
748 				// already set.
749 				break;
750 			}
751 
752 			case MSG_VISIT_PUBLISHER_WEBSITE:
753 			{
754 				BUrl url(fWebsiteLinkView->Text());
755 				url.OpenWithPreferredApplication();
756 				break;
757 			}
758 
759 			default:
760 				BView::MessageReceived(message);
761 				break;
762 		}
763 	}
764 
765 	void SetScreenshotThumbnail(const BitmapRef& bitmapRef)
766 	{
767 		if (bitmapRef.IsSet()) {
768 			fScreenshotView->SetBitmap(bitmapRef);
769 			fScreenshotView->SetEnabled(true);
770 		} else {
771 			fScreenshotView->UnsetBitmap();
772 			fScreenshotView->SetEnabled(false);
773 		}
774 	}
775 
776 	void SetPackage(const PackageInfoRef package)
777 	{
778 		fDescriptionView->SetText(package->ShortDescription(), package->FullDescription());
779 		fEmailIconView->SetBitmap(&fEmailIcon, BITMAP_SIZE_16);
780 		_SetContactInfo(fEmailLinkView, package->Publisher().Email());
781 		fWebsiteIconView->SetBitmap(&fWebsiteIcon, BITMAP_SIZE_16);
782 		_SetContactInfo(fWebsiteLinkView, package->Publisher().Website());
783 	}
784 
785 	void Clear()
786 	{
787 		fDescriptionView->SetText("");
788 		fEmailIconView->UnsetBitmap();
789 		fEmailLinkView->SetText("");
790 		fWebsiteIconView->UnsetBitmap();
791 		fWebsiteLinkView->SetText("");
792 		fScreenshotView->UnsetBitmap();
793 		fScreenshotView->SetEnabled(false);
794 	}
795 
796 private:
797 	void _SetContactInfo(LinkView* view, const BString& string)
798 	{
799 		if (string.Length() > 0) {
800 			view->SetText(string);
801 			view->SetEnabled(true);
802 		} else {
803 			view->SetText(B_TRANSLATE("<no info>"));
804 			view->SetEnabled(false);
805 		}
806 	}
807 
808 private:
809 	MarkupTextView*		fDescriptionView;
810 
811 	LinkedBitmapView*	fScreenshotView;
812 
813 	SharedBitmap		fEmailIcon;
814 	BitmapView*			fEmailIconView;
815 	LinkView*			fEmailLinkView;
816 
817 	SharedBitmap		fWebsiteIcon;
818 	BitmapView*			fWebsiteIconView;
819 	LinkView*			fWebsiteLinkView;
820 };
821 
822 
823 // #pragma mark - UserRatingsView
824 
825 
826 class RatingItemView : public BGroupView {
827 public:
828 	RatingItemView(const UserRatingRef rating)
829 		:
830 		BGroupView(B_HORIZONTAL, 0.0f)
831 	{
832 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
833 
834 		BGroupLayout* verticalGroup = new BGroupLayout(B_VERTICAL, 0.0f);
835 		GroupLayout()->AddItem(verticalGroup);
836 
837 		{
838 			BStringView* userNicknameView = new BStringView("user-nickname",
839 				rating->User().NickName());
840 			userNicknameView->SetFont(be_bold_font);
841 			verticalGroup->AddView(userNicknameView);
842 		}
843 
844 		BGroupLayout* ratingGroup =
845 			new BGroupLayout(B_HORIZONTAL, B_USE_DEFAULT_SPACING);
846 		verticalGroup->AddItem(ratingGroup);
847 
848 		if (rating->Rating() >= 0) {
849 			RatingView* ratingView = new RatingView("package rating view");
850 			ratingView->SetRating(rating->Rating());
851 			ratingGroup->AddView(ratingView);
852 		}
853 
854 		{
855 			BString createTimestampPresentation =
856 				LocaleUtils::TimestampToDateTimeString(
857 					rating->CreateTimestamp());
858 
859 			BString ratingContextDescription(
860 				B_TRANSLATE("%hd.timestamp% (version %hd.version%)"));
861 			ratingContextDescription.ReplaceAll("%hd.timestamp%",
862 				createTimestampPresentation);
863 			ratingContextDescription.ReplaceAll("%hd.version%",
864 				rating->PackageVersion());
865 
866 			BStringView* ratingContextView = new BStringView("rating-context",
867 				ratingContextDescription);
868 			BFont versionFont(be_plain_font);
869 			ratingContextView->SetFont(&versionFont);
870 			ratingGroup->AddView(ratingContextView);
871 		}
872 
873 		ratingGroup->AddItem(BSpaceLayoutItem::CreateGlue());
874 
875 		if (rating->Comment() > 0) {
876 			TextView* textView = new TextView("rating-text");
877 			ParagraphStyle paragraphStyle(textView->ParagraphStyle());
878 			paragraphStyle.SetJustify(true);
879 			textView->SetParagraphStyle(paragraphStyle);
880 			textView->SetText(rating->Comment());
881 			verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f));
882 			verticalGroup->AddView(textView);
883 			verticalGroup->AddItem(BSpaceLayoutItem::CreateVerticalStrut(8.0f));
884 		}
885 
886 		verticalGroup->SetInsets(B_USE_DEFAULT_SPACING);
887 
888 		SetFlags(Flags() | B_WILL_DRAW);
889 	}
890 
891 	void AllAttached()
892 	{
893 		for (int32 index = 0; index < CountChildren(); ++index)
894 			ChildAt(index)->AdoptParentColors();
895 	}
896 
897 	void Draw(BRect rect)
898 	{
899 		rgb_color color = mix_color(ViewColor(), ui_color(B_PANEL_TEXT_COLOR), 64);
900 		SetHighColor(color);
901 		StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom());
902 	}
903 
904 };
905 
906 
907 class RatingSummaryView : public BGridView {
908 public:
909 	RatingSummaryView()
910 		:
911 		BGridView("rating summary view", B_USE_HALF_ITEM_SPACING, 0.0f)
912 	{
913 		float tint = kContentTint - 0.1;
914 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, tint);
915 
916 		BLayoutBuilder::Grid<> layoutBuilder(this);
917 
918 		BFont smallFont;
919 		GetFont(&smallFont);
920 		smallFont.SetSize(std::max(9.0f, floorf(smallFont.Size() * 0.85f)));
921 
922 		for (int32 i = 0; i < 5; i++) {
923 			BString label;
924 			label.SetToFormat("%" B_PRId32, 5 - i);
925 			fLabelViews[i] = new BStringView("", label);
926 			fLabelViews[i]->SetFont(&smallFont);
927 			fLabelViews[i]->SetViewUIColor(ViewUIColor(), tint);
928 			layoutBuilder.Add(fLabelViews[i], 0, i);
929 
930 			fDiagramBarViews[i] = new DiagramBarView();
931 			layoutBuilder.Add(fDiagramBarViews[i], 1, i);
932 
933 			fCountViews[i] = new BStringView("", "");
934 			fCountViews[i]->SetFont(&smallFont);
935 			fCountViews[i]->SetViewUIColor(ViewUIColor(), tint);
936 			fCountViews[i]->SetAlignment(B_ALIGN_RIGHT);
937 			layoutBuilder.Add(fCountViews[i], 2, i);
938 		}
939 
940 		layoutBuilder.SetInsets(5);
941 	}
942 
943 	void SetToSummary(const RatingSummary& summary) {
944 		for (int32 i = 0; i < 5; i++) {
945 			int32 count = summary.ratingCountByStar[4 - i];
946 
947 			BString label;
948 			label.SetToFormat("%" B_PRId32, count);
949 			fCountViews[i]->SetText(label);
950 
951 			if (summary.ratingCount > 0) {
952 				fDiagramBarViews[i]->SetValue(
953 					(float)count / summary.ratingCount);
954 			} else
955 				fDiagramBarViews[i]->SetValue(0.0f);
956 		}
957 	}
958 
959 	void Clear() {
960 		for (int32 i = 0; i < 5; i++) {
961 			fCountViews[i]->SetText("");
962 			fDiagramBarViews[i]->SetValue(0.0f);
963 		}
964 	}
965 
966 private:
967 	BStringView*	fLabelViews[5];
968 	DiagramBarView*	fDiagramBarViews[5];
969 	BStringView*	fCountViews[5];
970 };
971 
972 
973 class UserRatingsView : public BGroupView {
974 public:
975 	UserRatingsView()
976 		:
977 		BGroupView("package ratings view", B_HORIZONTAL)
978 	{
979 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
980 
981 		fRatingSummaryView = new RatingSummaryView();
982 
983 		ScrollableGroupView* ratingsContainerView = new ScrollableGroupView();
984 		ratingsContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR,
985 												kContentTint);
986 		fRatingContainerLayout = ratingsContainerView->GroupLayout();
987 
988 		BScrollView* scrollView = new RatingsScrollView(
989 			"ratings scroll view", ratingsContainerView);
990 		scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
991 			B_SIZE_UNLIMITED));
992 
993 		BLayoutBuilder::Group<>(this)
994 			.AddGroup(B_VERTICAL)
995 				.Add(fRatingSummaryView, 0.0f)
996 				.AddGlue()
997 				.SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f)
998 			.End()
999 			.AddStrut(64.0)
1000 			.Add(scrollView, 1.0f)
1001 			.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
1002 		;
1003 	}
1004 
1005 	virtual ~UserRatingsView()
1006 	{
1007 		Clear();
1008 	}
1009 
1010 	void SetPackage(const PackageInfoRef package)
1011 	{
1012 		ClearRatings();
1013 
1014 		// TODO: Re-use rating summary already used for TitleView...
1015 		fRatingSummaryView->SetToSummary(package->CalculateRatingSummary());
1016 
1017 		int count = package->CountUserRatings();
1018 		if (count == 0) {
1019 			BStringView* noRatingsView = new BStringView("no ratings",
1020 				B_TRANSLATE("No user ratings available."));
1021 			noRatingsView->SetViewUIColor(ViewUIColor(), kContentTint);
1022 			noRatingsView->SetAlignment(B_ALIGN_CENTER);
1023 			noRatingsView->SetHighColor(disable_color(ui_color(B_PANEL_TEXT_COLOR),
1024 				ViewColor()));
1025 			noRatingsView->SetExplicitMaxSize(
1026 				BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
1027 			fRatingContainerLayout->AddView(0, noRatingsView);
1028 			return;
1029 		}
1030 
1031 		for (int i = count - 1; i >= 0; i--) {
1032 			UserRatingRef rating = package->UserRatingAtIndex(i);
1033 				// was previously filtering comments just for the current
1034 				// user's language, but as there are not so many comments at
1035 				// the moment, just show all of them for now.
1036 			RatingItemView* view = new RatingItemView(rating);
1037 			fRatingContainerLayout->AddView(0, view);
1038 		}
1039 	}
1040 
1041 	void Clear()
1042 	{
1043 		fRatingSummaryView->Clear();
1044 		ClearRatings();
1045 	}
1046 
1047 	void ClearRatings()
1048 	{
1049 		for (int32 i = fRatingContainerLayout->CountItems() - 1;
1050 				BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) {
1051 			BView* view = dynamic_cast<RatingItemView*>(item->View());
1052 			if (view == NULL)
1053 				view = dynamic_cast<BStringView*>(item->View());
1054 			if (view != NULL) {
1055 				view->RemoveSelf();
1056 				delete view;
1057 			}
1058 		}
1059 	}
1060 
1061 private:
1062 	BGroupLayout*			fRatingContainerLayout;
1063 	RatingSummaryView*		fRatingSummaryView;
1064 };
1065 
1066 
1067 // #pragma mark - ContentsView
1068 
1069 
1070 class ContentsView : public BGroupView {
1071 public:
1072 	ContentsView()
1073 		:
1074 		BGroupView("package contents view", B_HORIZONTAL)
1075 	{
1076 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1077 
1078 		fPackageContents = new PackageContentsView("contents_list");
1079 		AddChild(fPackageContents);
1080 
1081 	}
1082 
1083 	virtual ~ContentsView()
1084 	{
1085 	}
1086 
1087 	virtual void Draw(BRect updateRect)
1088 	{
1089 	}
1090 
1091 	void SetPackage(const PackageInfoRef package)
1092 	{
1093 		fPackageContents->SetPackage(package);
1094 	}
1095 
1096 	void Clear()
1097 	{
1098 		fPackageContents->Clear();
1099 	}
1100 
1101 private:
1102 	PackageContentsView*	fPackageContents;
1103 };
1104 
1105 
1106 // #pragma mark - ChangelogView
1107 
1108 
1109 class ChangelogView : public BGroupView {
1110 public:
1111 	ChangelogView()
1112 		:
1113 		BGroupView("package changelog view", B_HORIZONTAL)
1114 	{
1115 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1116 
1117 		fTextView = new MarkupTextView("changelog view");
1118 		fTextView->SetLowUIColor(ViewUIColor());
1119 		fTextView->SetInsets(be_plain_font->Size());
1120 
1121 		BScrollView* scrollView = new GeneralContentScrollView(
1122 			"changelog scroll view", fTextView);
1123 
1124 		BLayoutBuilder::Group<>(this)
1125 			.Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f))
1126 			.Add(scrollView, 1.0f)
1127 			.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
1128 		;
1129 	}
1130 
1131 	virtual ~ChangelogView()
1132 	{
1133 	}
1134 
1135 	virtual void Draw(BRect updateRect)
1136 	{
1137 	}
1138 
1139 	void SetPackage(const PackageInfoRef package)
1140 	{
1141 		const BString& changelog = package->Changelog();
1142 		if (changelog.Length() > 0)
1143 			fTextView->SetText(changelog);
1144 		else
1145 			fTextView->SetDisabledText(B_TRANSLATE("No changelog available."));
1146 	}
1147 
1148 	void Clear()
1149 	{
1150 		fTextView->SetText("");
1151 	}
1152 
1153 private:
1154 	MarkupTextView*		fTextView;
1155 };
1156 
1157 
1158 // #pragma mark - PagesView
1159 
1160 
1161 class PagesView : public BTabView {
1162 public:
1163 	PagesView()
1164 		:
1165 		BTabView("pages view", B_WIDTH_FROM_WIDEST)
1166 	{
1167 		SetBorder(B_NO_BORDER);
1168 
1169 		fAboutView = new AboutView();
1170 		fUserRatingsView = new UserRatingsView();
1171 		fChangelogView = new ChangelogView();
1172 		fContentsView = new ContentsView();
1173 
1174 		AddTab(fAboutView);
1175 		AddTab(fUserRatingsView);
1176 		AddTab(fChangelogView);
1177 		AddTab(fContentsView);
1178 
1179 		TabAt(TAB_ABOUT)->SetLabel(B_TRANSLATE("About"));
1180 		TabAt(TAB_RATINGS)->SetLabel(B_TRANSLATE("Ratings"));
1181 		TabAt(TAB_CHANGELOG)->SetLabel(B_TRANSLATE("Changelog"));
1182 		TabAt(TAB_CONTENTS)->SetLabel(B_TRANSLATE("Contents"));
1183 
1184 		Select(TAB_ABOUT);
1185 	}
1186 
1187 	virtual ~PagesView()
1188 	{
1189 		Clear();
1190 	}
1191 
1192 	void SetScreenshotThumbnail(const BitmapRef& bitmap)
1193 	{
1194 		fAboutView->SetScreenshotThumbnail(bitmap);
1195 	}
1196 
1197 	void SetPackage(const PackageInfoRef package, bool switchToDefaultTab)
1198 	{
1199 		if (switchToDefaultTab)
1200 			Select(TAB_ABOUT);
1201 
1202 		TabAt(TAB_CHANGELOG)->SetEnabled(
1203 			package.IsSet() && package->HasChangelog());
1204 		TabAt(TAB_CONTENTS)->SetEnabled(
1205 			package.IsSet()
1206 				&& (package->State() == ACTIVATED || package->IsLocalFile()));
1207 		Invalidate(TabFrame(TAB_CHANGELOG));
1208 		Invalidate(TabFrame(TAB_CONTENTS));
1209 
1210 		fAboutView->SetPackage(package);
1211 		fUserRatingsView->SetPackage(package);
1212 		fChangelogView->SetPackage(package);
1213 		fContentsView->SetPackage(package);
1214 	}
1215 
1216 	void Clear()
1217 	{
1218 		fAboutView->Clear();
1219 		fUserRatingsView->Clear();
1220 		fChangelogView->Clear();
1221 		fContentsView->Clear();
1222 	}
1223 
1224 private:
1225 	AboutView*			fAboutView;
1226 	UserRatingsView*	fUserRatingsView;
1227 	ChangelogView*		fChangelogView;
1228 	ContentsView* 		fContentsView;
1229 };
1230 
1231 
1232 // #pragma mark - PackageInfoView
1233 
1234 
1235 PackageInfoView::PackageInfoView(Model* model,
1236 		ProcessCoordinatorConsumer* processCoordinatorConsumer)
1237 	:
1238 	BView("package info view", 0),
1239 	fModel(model),
1240 	fPackageListener(new(std::nothrow) OnePackageMessagePackageListener(this)),
1241 	fProcessCoordinatorConsumer(processCoordinatorConsumer)
1242 {
1243 	fCardLayout = new BCardLayout();
1244 	SetLayout(fCardLayout);
1245 
1246 	BGroupView* noPackageCard = new BGroupView("no package card", B_VERTICAL);
1247 	AddChild(noPackageCard);
1248 
1249 	BStringView* noPackageView = new BStringView("no package view",
1250 		B_TRANSLATE("Click a package to view information"));
1251 	noPackageView->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT);
1252 	noPackageView->SetExplicitAlignment(BAlignment(
1253 		B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
1254 
1255 	BLayoutBuilder::Group<>(noPackageCard)
1256 		.Add(noPackageView)
1257 		.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED))
1258 	;
1259 
1260 	BGroupView* packageCard = new BGroupView("package card", B_VERTICAL);
1261 	AddChild(packageCard);
1262 
1263 	fCardLayout->SetVisibleItem((int32)0);
1264 
1265 	fTitleView = new TitleView(fModel->GetPackageIconRepository());
1266 	fPackageActionView = new PackageActionView(processCoordinatorConsumer,
1267 		model);
1268 	fPackageActionView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
1269 		B_SIZE_UNSET));
1270 	fPagesView = new PagesView();
1271 
1272 	BLayoutBuilder::Group<>(packageCard)
1273 		.AddGroup(B_HORIZONTAL, 0.0f)
1274 			.Add(fTitleView, 6.0f)
1275 			.Add(fPackageActionView, 1.0f)
1276 			.SetInsets(
1277 				B_USE_DEFAULT_SPACING, 0.0f,
1278 				B_USE_DEFAULT_SPACING, 0.0f)
1279 		.End()
1280 		.Add(fPagesView)
1281 	;
1282 
1283 	Clear();
1284 }
1285 
1286 
1287 PackageInfoView::~PackageInfoView()
1288 {
1289 	fPackageListener->SetPackage(PackageInfoRef(NULL));
1290 	delete fPackageListener;
1291 }
1292 
1293 
1294 void
1295 PackageInfoView::AttachedToWindow()
1296 {
1297 }
1298 
1299 
1300 void
1301 PackageInfoView::MessageReceived(BMessage* message)
1302 {
1303 	switch (message->what) {
1304 		case MSG_UPDATE_PACKAGE:
1305 		{
1306 			if (!fPackageListener->Package().IsSet())
1307 				break;
1308 
1309 			BString name;
1310 			uint32 changes;
1311 			if (message->FindString("name", &name) != B_OK
1312 				|| message->FindUInt32("changes", &changes) != B_OK) {
1313 				break;
1314 			}
1315 
1316 			const PackageInfoRef& package = fPackageListener->Package();
1317 			if (package->Name() != name)
1318 				break;
1319 
1320 			BAutolock _(fModel->Lock());
1321 
1322 			if ((changes & PKG_CHANGED_SUMMARY) != 0
1323 				|| (changes & PKG_CHANGED_DESCRIPTION) != 0
1324 				|| (changes & PKG_CHANGED_SCREENSHOTS) != 0
1325 				|| (changes & PKG_CHANGED_TITLE) != 0
1326 				|| (changes & PKG_CHANGED_RATINGS) != 0
1327 				|| (changes & PKG_CHANGED_STATE) != 0
1328 				|| (changes & PKG_CHANGED_CHANGELOG) != 0) {
1329 				fPagesView->SetPackage(package, false);
1330 			}
1331 
1332 			if ((changes & PKG_CHANGED_TITLE) != 0
1333 				|| (changes & PKG_CHANGED_RATINGS) != 0) {
1334 				fTitleView->SetPackage(package);
1335 			}
1336 
1337 			if ((changes & PKG_CHANGED_STATE) != 0)
1338 				fPackageActionView->SetPackage(package);
1339 
1340 			break;
1341 		}
1342 		default:
1343 			BView::MessageReceived(message);
1344 			break;
1345 	}
1346 }
1347 
1348 
1349 void
1350 PackageInfoView::SetPackage(const PackageInfoRef& packageRef)
1351 {
1352 	BAutolock _(fModel->Lock());
1353 
1354 	if (!packageRef.IsSet()) {
1355 		Clear();
1356 		return;
1357 	}
1358 
1359 	bool switchToDefaultTab = true;
1360 	if (fPackage == packageRef) {
1361 		// When asked to display the already showing package ref,
1362 		// don't switch to the default tab.
1363 		switchToDefaultTab = false;
1364 	} else if (fPackage.IsSet() && packageRef.IsSet()
1365 		&& fPackage->Name() == packageRef->Name()) {
1366 		// When asked to display a different PackageInfo instance,
1367 		// but it has the same package title as the already showing
1368 		// instance, this probably means there was a repository
1369 		// refresh and we are in fact still requested to show the
1370 		// same package as before the refresh.
1371 		switchToDefaultTab = false;
1372 	}
1373 
1374 	fTitleView->SetPackage(packageRef);
1375 	fPackageActionView->SetPackage(packageRef);
1376 	fPagesView->SetPackage(packageRef, switchToDefaultTab);
1377 
1378 	_SetPackageScreenshotThumb(packageRef);
1379 
1380 	fCardLayout->SetVisibleItem(1);
1381 
1382 	fPackageListener->SetPackage(packageRef);
1383 
1384 	// Set the fPackage reference last, so we keep a reference to the
1385 	// previous package before switching all the views to the new package.
1386 	// Otherwise the PackageInfo instance may go away because we had the
1387 	// last reference. And some of the views, the PackageActionView for
1388 	// example, keeps references to stuff from the previous package and
1389 	// access it while switching to the new package.
1390 	fPackage = packageRef;
1391 }
1392 
1393 
1394 /*! See if the screenshot is already cached; if it is then load it
1395 	immediately. If it is not then trigger a process to start it in
1396 	the background. A message will come through later once it is
1397 	cached and ready to load.
1398 */
1399 
1400 void
1401 PackageInfoView::_SetPackageScreenshotThumb(const PackageInfoRef& package)
1402 {
1403 	ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package);
1404 	bool hasCachedBitmap = false;
1405 
1406 	if (desiredCoordinate.IsValid()) {
1407 		bool present = false;
1408 		if (fModel->GetPackageScreenshotRepository()->HasCachedScreenshot(
1409 				desiredCoordinate, &present) != B_OK) {
1410 			HDERROR("unable to ascertain if screenshot is present for pkg [%s]", package->Name().String());
1411 		} else {
1412 			if (present) {
1413 				HDDEBUG("screenshot is already cached for [%s] -- will load it", package->Name().String());
1414 				_HandleScreenshotCached(package, desiredCoordinate);
1415 				hasCachedBitmap = true;
1416 			} else {
1417 				HDDEBUG("screenshot is not cached [%s] -- will cache it", package->Name().String());
1418 				ProcessCoordinator *processCoordinator =
1419 					ProcessCoordinatorFactory::CacheScreenshotCoordinator(
1420 						fModel, desiredCoordinate);
1421 				fProcessCoordinatorConsumer->Consume(processCoordinator);
1422 			}
1423 		}
1424 	} else
1425 		HDDEBUG("no screenshot for pkg [%s]", package->Name().String());
1426 
1427 	if (!hasCachedBitmap)
1428 		fPagesView->SetScreenshotThumbnail(BitmapRef());
1429 }
1430 
1431 
1432 /*static*/ const ScreenshotCoordinate
1433 PackageInfoView::_ScreenshotThumbCoordinate(const PackageInfoRef& package)
1434 {
1435 	if (!package.IsSet())
1436 		return ScreenshotCoordinate();
1437 	if (package->CountScreenshotInfos() == 0)
1438 		return ScreenshotCoordinate();
1439 	return ScreenshotCoordinate(package->ScreenshotInfoAtIndex(0)->Code(), kScreenshotSize, kScreenshotSize);
1440 }
1441 
1442 
1443 /*! This message will arrive when the data in the screenshot cache
1444 	contains a new screenshot. This logic can check to see if the
1445 	screenshot arriving is the one that is being waited for and
1446 	would then load the screenshot from the cache and display it.
1447 */
1448 
1449 void
1450 PackageInfoView::HandleScreenshotCached(const ScreenshotCoordinate& coordinate)
1451 {
1452 	_HandleScreenshotCached(fPackage, coordinate);
1453 }
1454 
1455 
1456 void
1457 PackageInfoView::_HandleScreenshotCached(const PackageInfoRef& package,
1458 	const ScreenshotCoordinate& coordinate)
1459 {
1460 	ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package);
1461 	bool hasBitmap = false;
1462 
1463 	if (desiredCoordinate.IsValid() && desiredCoordinate == coordinate) {
1464 		HDDEBUG("screenshot [%s] has been cached and matched; will load",
1465 			coordinate.Code().String());
1466 		BitmapRef bitmapRef;
1467 		if (fModel->GetPackageScreenshotRepository()->CacheAndLoadScreenshot(
1468 				coordinate, &bitmapRef) != B_OK) {
1469 			HDERROR("unable to load the screenshot [%s]", coordinate.Code().String());
1470 		} else {
1471 			fPagesView->SetScreenshotThumbnail(bitmapRef);
1472 			hasBitmap = true;
1473 		}
1474 	}
1475 
1476 	if (!hasBitmap)
1477 		fPagesView->SetScreenshotThumbnail(BitmapRef());
1478 }
1479 
1480 
1481 void
1482 PackageInfoView::Clear()
1483 {
1484 	BAutolock _(fModel->Lock());
1485 
1486 	fTitleView->Clear();
1487 	fPackageActionView->Clear();
1488 	fPagesView->Clear();
1489 
1490 	fCardLayout->SetVisibleItem((int32)0);
1491 
1492 	fPackageListener->SetPackage(PackageInfoRef(NULL));
1493 
1494 	fPackage.Unset();
1495 }
1496