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