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