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