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
57enum {
58	TAB_ABOUT		= 0,
59	TAB_RATINGS		= 1,
60	TAB_CHANGELOG	= 2,
61	TAB_CONTENTS	= 3
62};
63
64
65static const float kContentTint = (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0f;
66static const uint16 kScreenshotSize = 320;
67
68
69class RatingsScrollView : public GeneralContentScrollView {
70public:
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
98class DiagramBarView : public BView {
99public:
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
153private:
154	float			fValue;
155};
156
157
158// #pragma mark - TitleView
159
160
161enum {
162	MSG_MOUSE_ENTERED_RATING	= 'menr',
163	MSG_MOUSE_EXITED_RATING		= 'mexr',
164};
165
166
167class TransitReportingButton : public BButton {
168public:
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
199private:
200	BMessage*	fTransitMessage;
201};
202
203
204class TransitReportingRatingView : public RatingView, public BInvoker {
205public:
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
227private:
228	BMessage*	fTransitMessage;
229};
230
231
232class TitleView : public BGroupView {
233public:
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
419private:
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
442class PackageActionView : public BView {
443public:
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
549private:
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
619private:
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
634enum {
635	MSG_EMAIL_PUBLISHER				= 'emlp',
636	MSG_VISIT_PUBLISHER_WEBSITE		= 'vpws',
637};
638
639
640class AboutView : public BView {
641public:
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
796private:
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
808private:
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
826class RatingItemView : public BGroupView {
827public:
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
907class RatingSummaryView : public BGridView {
908public:
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
966private:
967	BStringView*	fLabelViews[5];
968	DiagramBarView*	fDiagramBarViews[5];
969	BStringView*	fCountViews[5];
970};
971
972
973class UserRatingsView : public BGroupView {
974public:
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
1061private:
1062	BGroupLayout*			fRatingContainerLayout;
1063	RatingSummaryView*		fRatingSummaryView;
1064};
1065
1066
1067// #pragma mark - ContentsView
1068
1069
1070class ContentsView : public BGroupView {
1071public:
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
1101private:
1102	PackageContentsView*	fPackageContents;
1103};
1104
1105
1106// #pragma mark - ChangelogView
1107
1108
1109class ChangelogView : public BGroupView {
1110public:
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
1153private:
1154	MarkupTextView*		fTextView;
1155};
1156
1157
1158// #pragma mark - PagesView
1159
1160
1161class PagesView : public BTabView {
1162public:
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
1224private:
1225	AboutView*			fAboutView;
1226	UserRatingsView*	fUserRatingsView;
1227	ChangelogView*		fChangelogView;
1228	ContentsView* 		fContentsView;
1229};
1230
1231
1232// #pragma mark - PackageInfoView
1233
1234
1235PackageInfoView::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
1287PackageInfoView::~PackageInfoView()
1288{
1289	fPackageListener->SetPackage(PackageInfoRef(NULL));
1290	delete fPackageListener;
1291}
1292
1293
1294void
1295PackageInfoView::AttachedToWindow()
1296{
1297}
1298
1299
1300void
1301PackageInfoView::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
1349void
1350PackageInfoView::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
1400void
1401PackageInfoView::_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
1433PackageInfoView::_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
1449void
1450PackageInfoView::HandleScreenshotCached(const ScreenshotCoordinate& coordinate)
1451{
1452	_HandleScreenshotCached(fPackage, coordinate);
1453}
1454
1455
1456void
1457PackageInfoView::_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
1481void
1482PackageInfoView::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