xref: /haiku/src/apps/haikudepot/ui/PackageInfoView.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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 		} else {
1001 			for (int i = count - 1; i >= 0; i--) {
1002 				UserRatingRef rating = package->UserRatingAtIndex(i);
1003 					// was previously filtering comments just for the current
1004 					// user's language, but as there are not so many comments at
1005 					// the moment, just show all of them for now.
1006 				RatingItemView* view = new RatingItemView(rating);
1007 				fRatingContainerLayout->AddView(0, view);
1008 			}
1009 		}
1010 
1011 		InvalidateLayout();
1012 	}
1013 
1014 	void Clear()
1015 	{
1016 		fRatingSummaryView->Clear();
1017 		ClearRatings();
1018 	}
1019 
1020 	void ClearRatings()
1021 	{
1022 		for (int32 i = fRatingContainerLayout->CountItems() - 1;
1023 				BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) {
1024 			BView* view = dynamic_cast<RatingItemView*>(item->View());
1025 			if (view == NULL)
1026 				view = dynamic_cast<BStringView*>(item->View());
1027 			if (view != NULL) {
1028 				view->RemoveSelf();
1029 				delete view;
1030 			}
1031 		}
1032 	}
1033 
1034 private:
1035 	BGroupLayout*			fRatingContainerLayout;
1036 	RatingSummaryView*		fRatingSummaryView;
1037 };
1038 
1039 
1040 // #pragma mark - ContentsView
1041 
1042 
1043 class ContentsView : public BGroupView {
1044 public:
1045 	ContentsView()
1046 		:
1047 		BGroupView("package contents view", B_HORIZONTAL)
1048 	{
1049 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1050 
1051 		fPackageContents = new PackageContentsView("contents_list");
1052 		AddChild(fPackageContents);
1053 
1054 	}
1055 
1056 	virtual ~ContentsView()
1057 	{
1058 	}
1059 
1060 	virtual void Draw(BRect updateRect)
1061 	{
1062 	}
1063 
1064 	void SetPackage(const PackageInfoRef package)
1065 	{
1066 		fPackageContents->SetPackage(package);
1067 	}
1068 
1069 	void Clear()
1070 	{
1071 		fPackageContents->Clear();
1072 	}
1073 
1074 private:
1075 	PackageContentsView*	fPackageContents;
1076 };
1077 
1078 
1079 // #pragma mark - ChangelogView
1080 
1081 
1082 class ChangelogView : public BGroupView {
1083 public:
1084 	ChangelogView()
1085 		:
1086 		BGroupView("package changelog view", B_HORIZONTAL)
1087 	{
1088 		SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
1089 
1090 		fTextView = new MarkupTextView("changelog view");
1091 		fTextView->SetLowUIColor(ViewUIColor());
1092 		fTextView->SetInsets(be_plain_font->Size());
1093 
1094 		BScrollView* scrollView = new GeneralContentScrollView(
1095 			"changelog scroll view", fTextView);
1096 
1097 		BLayoutBuilder::Group<>(this)
1098 			.Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f))
1099 			.Add(scrollView, 1.0f)
1100 			.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f)
1101 		;
1102 	}
1103 
1104 	virtual ~ChangelogView()
1105 	{
1106 	}
1107 
1108 	virtual void Draw(BRect updateRect)
1109 	{
1110 	}
1111 
1112 	void SetPackage(const PackageInfoRef package)
1113 	{
1114 		const BString& changelog = package->Changelog();
1115 		if (changelog.Length() > 0)
1116 			fTextView->SetText(changelog);
1117 		else
1118 			fTextView->SetDisabledText(B_TRANSLATE("No changelog available."));
1119 	}
1120 
1121 	void Clear()
1122 	{
1123 		fTextView->SetText("");
1124 	}
1125 
1126 private:
1127 	MarkupTextView*		fTextView;
1128 };
1129 
1130 
1131 // #pragma mark - PagesView
1132 
1133 
1134 class PagesView : public BTabView {
1135 public:
1136 	PagesView()
1137 		:
1138 		BTabView("pages view", B_WIDTH_FROM_WIDEST)
1139 	{
1140 		SetBorder(B_NO_BORDER);
1141 
1142 		fAboutView = new AboutView();
1143 		fUserRatingsView = new UserRatingsView();
1144 		fChangelogView = new ChangelogView();
1145 		fContentsView = new ContentsView();
1146 
1147 		AddTab(fAboutView);
1148 		AddTab(fUserRatingsView);
1149 		AddTab(fChangelogView);
1150 		AddTab(fContentsView);
1151 
1152 		TabAt(TAB_ABOUT)->SetLabel(B_TRANSLATE("About"));
1153 		TabAt(TAB_RATINGS)->SetLabel(B_TRANSLATE("Ratings"));
1154 		TabAt(TAB_CHANGELOG)->SetLabel(B_TRANSLATE("Changelog"));
1155 		TabAt(TAB_CONTENTS)->SetLabel(B_TRANSLATE("Contents"));
1156 
1157 		Select(TAB_ABOUT);
1158 	}
1159 
1160 	virtual ~PagesView()
1161 	{
1162 		Clear();
1163 	}
1164 
1165 	void SetScreenshotThumbnail(const BitmapRef& bitmap)
1166 	{
1167 		fAboutView->SetScreenshotThumbnail(bitmap);
1168 	}
1169 
1170 	void SetPackage(const PackageInfoRef package, bool switchToDefaultTab)
1171 	{
1172 		if (switchToDefaultTab)
1173 			Select(TAB_ABOUT);
1174 
1175 		TabAt(TAB_CHANGELOG)->SetEnabled(
1176 			package.IsSet() && package->HasChangelog());
1177 		TabAt(TAB_CONTENTS)->SetEnabled(
1178 			package.IsSet()
1179 				&& (package->State() == ACTIVATED || package->IsLocalFile()));
1180 		Invalidate(TabFrame(TAB_CHANGELOG));
1181 		Invalidate(TabFrame(TAB_CONTENTS));
1182 
1183 		fAboutView->SetPackage(package);
1184 		fUserRatingsView->SetPackage(package);
1185 		fChangelogView->SetPackage(package);
1186 		fContentsView->SetPackage(package);
1187 	}
1188 
1189 	void Clear()
1190 	{
1191 		fAboutView->Clear();
1192 		fUserRatingsView->Clear();
1193 		fChangelogView->Clear();
1194 		fContentsView->Clear();
1195 	}
1196 
1197 private:
1198 	AboutView*			fAboutView;
1199 	UserRatingsView*	fUserRatingsView;
1200 	ChangelogView*		fChangelogView;
1201 	ContentsView* 		fContentsView;
1202 };
1203 
1204 
1205 // #pragma mark - PackageInfoView
1206 
1207 
1208 PackageInfoView::PackageInfoView(Model* model,
1209 		ProcessCoordinatorConsumer* processCoordinatorConsumer)
1210 	:
1211 	BView("package info view", 0),
1212 	fModel(model),
1213 	fPackageListener(new(std::nothrow) OnePackageMessagePackageListener(this)),
1214 	fProcessCoordinatorConsumer(processCoordinatorConsumer)
1215 {
1216 	fCardLayout = new BCardLayout();
1217 	SetLayout(fCardLayout);
1218 
1219 	BGroupView* noPackageCard = new BGroupView("no package card", B_VERTICAL);
1220 	AddChild(noPackageCard);
1221 
1222 	BStringView* noPackageView = new BStringView("no package view",
1223 		B_TRANSLATE("Click a package to view information"));
1224 	noPackageView->SetHighUIColor(B_PANEL_TEXT_COLOR, B_LIGHTEN_1_TINT);
1225 	noPackageView->SetExplicitAlignment(BAlignment(
1226 		B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
1227 
1228 	BLayoutBuilder::Group<>(noPackageCard)
1229 		.Add(noPackageView)
1230 		.SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED))
1231 	;
1232 
1233 	BGroupView* packageCard = new BGroupView("package card", B_VERTICAL);
1234 	AddChild(packageCard);
1235 
1236 	fCardLayout->SetVisibleItem((int32)0);
1237 
1238 	fTitleView = new TitleView(fModel->GetPackageIconRepository());
1239 	fPackageActionView = new PackageActionView(processCoordinatorConsumer,
1240 		model);
1241 	fPackageActionView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
1242 		B_SIZE_UNSET));
1243 	fPagesView = new PagesView();
1244 
1245 	BLayoutBuilder::Group<>(packageCard)
1246 		.AddGroup(B_HORIZONTAL, 0.0f)
1247 			.Add(fTitleView, 6.0f)
1248 			.Add(fPackageActionView, 1.0f)
1249 			.SetInsets(
1250 				B_USE_DEFAULT_SPACING, 0.0f,
1251 				B_USE_DEFAULT_SPACING, 0.0f)
1252 		.End()
1253 		.Add(fPagesView)
1254 	;
1255 
1256 	Clear();
1257 }
1258 
1259 
1260 PackageInfoView::~PackageInfoView()
1261 {
1262 	fPackageListener->SetPackage(PackageInfoRef(NULL));
1263 	delete fPackageListener;
1264 }
1265 
1266 
1267 void
1268 PackageInfoView::AttachedToWindow()
1269 {
1270 }
1271 
1272 
1273 void
1274 PackageInfoView::MessageReceived(BMessage* message)
1275 {
1276 	switch (message->what) {
1277 		case MSG_UPDATE_PACKAGE:
1278 		{
1279 			if (!fPackageListener->Package().IsSet())
1280 				break;
1281 
1282 			BString name;
1283 			uint32 changes;
1284 			if (message->FindString("name", &name) != B_OK
1285 				|| message->FindUInt32("changes", &changes) != B_OK) {
1286 				break;
1287 			}
1288 
1289 			const PackageInfoRef& package = fPackageListener->Package();
1290 			if (package->Name() != name)
1291 				break;
1292 
1293 			BAutolock _(fModel->Lock());
1294 
1295 			if ((changes & PKG_CHANGED_SUMMARY) != 0
1296 				|| (changes & PKG_CHANGED_DESCRIPTION) != 0
1297 				|| (changes & PKG_CHANGED_SCREENSHOTS) != 0
1298 				|| (changes & PKG_CHANGED_TITLE) != 0
1299 				|| (changes & PKG_CHANGED_RATINGS) != 0
1300 				|| (changes & PKG_CHANGED_STATE) != 0
1301 				|| (changes & PKG_CHANGED_CHANGELOG) != 0) {
1302 				fPagesView->SetPackage(package, false);
1303 			}
1304 
1305 			if ((changes & PKG_CHANGED_TITLE) != 0
1306 				|| (changes & PKG_CHANGED_RATINGS) != 0) {
1307 				fTitleView->SetPackage(package);
1308 			}
1309 
1310 			if ((changes & PKG_CHANGED_STATE) != 0)
1311 				fPackageActionView->SetPackage(package);
1312 
1313 			break;
1314 		}
1315 		default:
1316 			BView::MessageReceived(message);
1317 			break;
1318 	}
1319 }
1320 
1321 
1322 void
1323 PackageInfoView::SetPackage(const PackageInfoRef& packageRef)
1324 {
1325 	BAutolock _(fModel->Lock());
1326 
1327 	if (!packageRef.IsSet()) {
1328 		Clear();
1329 		return;
1330 	}
1331 
1332 	bool switchToDefaultTab = true;
1333 	if (fPackage == packageRef) {
1334 		// When asked to display the already showing package ref,
1335 		// don't switch to the default tab.
1336 		switchToDefaultTab = false;
1337 	} else if (fPackage.IsSet() && packageRef.IsSet()
1338 		&& fPackage->Name() == packageRef->Name()) {
1339 		// When asked to display a different PackageInfo instance,
1340 		// but it has the same package title as the already showing
1341 		// instance, this probably means there was a repository
1342 		// refresh and we are in fact still requested to show the
1343 		// same package as before the refresh.
1344 		switchToDefaultTab = false;
1345 	}
1346 
1347 	fTitleView->SetPackage(packageRef);
1348 	fPackageActionView->SetPackage(packageRef);
1349 	fPagesView->SetPackage(packageRef, switchToDefaultTab);
1350 
1351 	_SetPackageScreenshotThumb(packageRef);
1352 
1353 	fCardLayout->SetVisibleItem(1);
1354 
1355 	fPackageListener->SetPackage(packageRef);
1356 
1357 	// Set the fPackage reference last, so we keep a reference to the
1358 	// previous package before switching all the views to the new package.
1359 	// Otherwise the PackageInfo instance may go away because we had the
1360 	// last reference. And some of the views, the PackageActionView for
1361 	// example, keeps references to stuff from the previous package and
1362 	// access it while switching to the new package.
1363 	fPackage = packageRef;
1364 }
1365 
1366 
1367 /*! See if the screenshot is already cached; if it is then load it
1368 	immediately. If it is not then trigger a process to start it in
1369 	the background. A message will come through later once it is
1370 	cached and ready to load.
1371 */
1372 
1373 void
1374 PackageInfoView::_SetPackageScreenshotThumb(const PackageInfoRef& package)
1375 {
1376 	ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package);
1377 	bool hasCachedBitmap = false;
1378 
1379 	if (desiredCoordinate.IsValid()) {
1380 		bool present = false;
1381 		if (fModel->GetPackageScreenshotRepository()->HasCachedScreenshot(
1382 				desiredCoordinate, &present) != B_OK) {
1383 			HDERROR("unable to ascertain if screenshot is present for pkg [%s]", package->Name().String());
1384 		} else {
1385 			if (present) {
1386 				HDDEBUG("screenshot is already cached for [%s] -- will load it", package->Name().String());
1387 				_HandleScreenshotCached(package, desiredCoordinate);
1388 				hasCachedBitmap = true;
1389 			} else {
1390 				HDDEBUG("screenshot is not cached [%s] -- will cache it", package->Name().String());
1391 				ProcessCoordinator *processCoordinator =
1392 					ProcessCoordinatorFactory::CacheScreenshotCoordinator(
1393 						fModel, desiredCoordinate);
1394 				fProcessCoordinatorConsumer->Consume(processCoordinator);
1395 			}
1396 		}
1397 	} else
1398 		HDDEBUG("no screenshot for pkg [%s]", package->Name().String());
1399 
1400 	if (!hasCachedBitmap)
1401 		fPagesView->SetScreenshotThumbnail(BitmapRef());
1402 }
1403 
1404 
1405 /*static*/ const ScreenshotCoordinate
1406 PackageInfoView::_ScreenshotThumbCoordinate(const PackageInfoRef& package)
1407 {
1408 	if (!package.IsSet())
1409 		return ScreenshotCoordinate();
1410 	if (package->CountScreenshotInfos() == 0)
1411 		return ScreenshotCoordinate();
1412 	return ScreenshotCoordinate(package->ScreenshotInfoAtIndex(0)->Code(), kScreenshotSize, kScreenshotSize);
1413 }
1414 
1415 
1416 /*! This message will arrive when the data in the screenshot cache
1417 	contains a new screenshot. This logic can check to see if the
1418 	screenshot arriving is the one that is being waited for and
1419 	would then load the screenshot from the cache and display it.
1420 */
1421 
1422 void
1423 PackageInfoView::HandleScreenshotCached(const ScreenshotCoordinate& coordinate)
1424 {
1425 	_HandleScreenshotCached(fPackage, coordinate);
1426 }
1427 
1428 
1429 void
1430 PackageInfoView::_HandleScreenshotCached(const PackageInfoRef& package,
1431 	const ScreenshotCoordinate& coordinate)
1432 {
1433 	ScreenshotCoordinate desiredCoordinate = _ScreenshotThumbCoordinate(package);
1434 	bool hasBitmap = false;
1435 
1436 	if (desiredCoordinate.IsValid() && desiredCoordinate == coordinate) {
1437 		HDDEBUG("screenshot [%s] has been cached and matched; will load",
1438 			coordinate.Code().String());
1439 		BitmapRef bitmapRef;
1440 		if (fModel->GetPackageScreenshotRepository()->CacheAndLoadScreenshot(
1441 				coordinate, &bitmapRef) != B_OK) {
1442 			HDERROR("unable to load the screenshot [%s]", coordinate.Code().String());
1443 		} else {
1444 			fPagesView->SetScreenshotThumbnail(bitmapRef);
1445 			hasBitmap = true;
1446 		}
1447 	}
1448 
1449 	if (!hasBitmap)
1450 		fPagesView->SetScreenshotThumbnail(BitmapRef());
1451 }
1452 
1453 
1454 void
1455 PackageInfoView::Clear()
1456 {
1457 	BAutolock _(fModel->Lock());
1458 
1459 	fTitleView->Clear();
1460 	fPackageActionView->Clear();
1461 	fPagesView->Clear();
1462 
1463 	fCardLayout->SetVisibleItem((int32)0);
1464 
1465 	fPackageListener->SetPackage(PackageInfoRef(NULL));
1466 
1467 	fPackage.Unset();
1468 }
1469