xref: /haiku/src/apps/softwareupdater/SoftwareUpdaterWindow.cpp (revision 6889394848e2dc9f41ff53b12141d572822ca0c6)
1 /*
2  * Copyright 2016-2017 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT license
4  *
5  * Authors:
6  *		Alexander von Gluck IV <kallisti5@unixzen.com>
7  *		Brian Hill <supernova@warpmail.net>
8  */
9 
10 
11 #include "SoftwareUpdaterWindow.h"
12 
13 #include <Alert.h>
14 #include <AppDefs.h>
15 #include <Application.h>
16 #include <Catalog.h>
17 #include <ControlLook.h>
18 #include <LayoutBuilder.h>
19 #include <LayoutUtils.h>
20 #include <Message.h>
21 #include <Roster.h>
22 #include <String.h>
23 
24 #include "constants.h"
25 
26 #undef B_TRANSLATION_CONTEXT
27 #define B_TRANSLATION_CONTEXT "SoftwareUpdaterWindow"
28 
29 
30 SoftwareUpdaterWindow::SoftwareUpdaterWindow()
31 	:
32 	BWindow(BRect(0, 0, 300, 100),
33 		B_TRANSLATE_SYSTEM_NAME("SoftwareUpdater"), B_TITLED_WINDOW,
34 		B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_ZOOMABLE | B_NOT_CLOSABLE),
35 	fStripeView(NULL),
36 	fHeaderView(NULL),
37 	fDetailView(NULL),
38 	fUpdateButton(NULL),
39 	fCancelButton(NULL),
40 	fStatusBar(NULL),
41 	fCurrentState(STATE_HEAD),
42 	fWaitingSem(-1),
43 	fWaitingForButton(false),
44 	fUpdateConfirmed(false),
45 	fUserCancelRequested(false),
46 	fWarningAlertCount(0)
47 {
48 	BBitmap icon = GetIcon(32 * icon_layout_scale());
49 	fStripeView = new StripeView(icon);
50 
51 	fUpdateButton = new BButton(B_TRANSLATE("Update now"),
52 		new BMessage(kMsgUpdateConfirmed));
53 	fUpdateButton->MakeDefault(true);
54 	fCancelButton = new BButton(B_TRANSLATE("Cancel"),
55 		new BMessage(kMsgCancel));
56 
57 	fHeaderView = new BStringView("header",
58 		B_TRANSLATE("Checking for updates"), B_WILL_DRAW);
59 	fHeaderView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
60 	fHeaderView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
61 	fDetailView = new BStringView("detail", B_TRANSLATE("Contacting software "
62 		"repositories to check for package updates."), B_WILL_DRAW);
63 	fDetailView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
64 	fDetailView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
65 	fStatusBar = new BStatusBar("progress");
66 	fStatusBar->SetMaxValue(100);
67 
68 	fListView = new PackageListView();
69 	fScrollView = new BScrollView("scrollview", fListView, B_WILL_DRAW,
70 		false, true);
71 
72 	fDetailsCheckbox = new BCheckBox("detailscheckbox",
73 		B_TRANSLATE("Show more details"),
74 		new BMessage(kMsgMoreDetailsToggle));
75 
76 	BFont font;
77 	fHeaderView->GetFont(&font);
78 	font.SetFace(B_BOLD_FACE);
79 	font.SetSize(font.Size() * 1.5);
80 	fHeaderView->SetFont(&font,
81 		B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE | B_FONT_FLAGS);
82 
83 	BLayoutBuilder::Group<>(this, B_HORIZONTAL, B_USE_ITEM_SPACING)
84 		.Add(fStripeView)
85 		.AddGroup(B_VERTICAL, 0)
86 			.SetInsets(0, B_USE_WINDOW_SPACING,
87 				B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING)
88 			.AddGroup(new BGroupView(B_VERTICAL, B_USE_ITEM_SPACING))
89 				.Add(fHeaderView)
90 				.Add(fDetailView)
91 				.Add(fStatusBar)
92 				.Add(fScrollView)
93 			.End()
94 			.AddStrut(B_USE_SMALL_SPACING)
95 			.AddGroup(new BGroupView(B_HORIZONTAL))
96 				.Add(fDetailsCheckbox)
97 				.AddGlue()
98 			.End()
99 			.AddGroup(new BGroupView(B_HORIZONTAL))
100 				.AddGlue()
101 				.Add(fCancelButton)
102 				.Add(fUpdateButton)
103 			.End()
104 		.End()
105 	.End();
106 
107 	fDetailsLayoutItem = layout_item_for(fDetailView);
108 	fProgressLayoutItem = layout_item_for(fStatusBar);
109 	fPackagesLayoutItem = layout_item_for(fScrollView);
110 	fUpdateButtonLayoutItem = layout_item_for(fUpdateButton);
111 	fDetailsCheckboxLayoutItem = layout_item_for(fDetailsCheckbox);
112 
113 	_SetState(STATE_DISPLAY_STATUS);
114 	CenterOnScreen();
115 	Show();
116 	SetFlags(Flags() ^ B_AUTO_UPDATE_SIZE_LIMITS);
117 
118 	// Prevent resizing for now
119 	fDefaultRect = Bounds();
120 	SetSizeLimits(fDefaultRect.Width(), fDefaultRect.Width(),
121 		fDefaultRect.Height(), fDefaultRect.Height());
122 
123 	BMessage registerMessage(kMsgRegister);
124 	registerMessage.AddMessenger(kKeyMessenger, BMessenger(this));
125 	be_app->PostMessage(&registerMessage);
126 
127 	fCancelAlertResponse.SetMessage(new BMessage(kMsgCancelResponse));
128 	fCancelAlertResponse.SetTarget(this);
129 	fWarningAlertDismissed.SetMessage(new BMessage(kMsgWarningDismissed));
130 	fWarningAlertDismissed.SetTarget(this);
131 }
132 
133 
134 void
135 SoftwareUpdaterWindow::MessageReceived(BMessage* message)
136 {
137 	switch (message->what) {
138 
139 		case kMsgTextUpdate:
140 		{
141 			if (fCurrentState == STATE_DISPLAY_PROGRESS)
142 				_SetState(STATE_DISPLAY_STATUS);
143 			else if (fCurrentState != STATE_DISPLAY_STATUS)
144 				break;
145 
146 			BString header;
147 			BString detail;
148 			Lock();
149 			status_t result = message->FindString(kKeyHeader, &header);
150 			if (result == B_OK && header != fHeaderView->Text())
151 				fHeaderView->SetText(header.String());
152 			result = message->FindString(kKeyDetail, &detail);
153 			if (result == B_OK)
154 				fDetailView->SetText(detail.String());
155 			Unlock();
156 			break;
157 		}
158 
159 		case kMsgProgressUpdate:
160 		{
161 			if (fCurrentState == STATE_DISPLAY_STATUS)
162 				_SetState(STATE_DISPLAY_PROGRESS);
163 			else if (fCurrentState != STATE_DISPLAY_PROGRESS)
164 				break;
165 
166 			BString packageName;
167 			status_t result = message->FindString(kKeyPackageName, &packageName);
168 			if (result != B_OK)
169 				break;
170 			BString packageCount;
171 			result = message->FindString(kKeyPackageCount, &packageCount);
172 			if (result != B_OK)
173 				break;
174 			float percent;
175 			result = message->FindFloat(kKeyPercentage, &percent);
176 			if (result != B_OK)
177 				break;
178 
179 			BString header;
180 			Lock();
181 			result = message->FindString(kKeyHeader, &header);
182 			if (result == B_OK && header != fHeaderView->Text())
183 				fHeaderView->SetText(header.String());
184 			fStatusBar->SetTo(percent, packageName.String(),
185 				packageCount.String());
186 			Unlock();
187 
188 			fListView->UpdatePackageProgress(packageName.String(), percent);
189 			break;
190 		}
191 
192 		case kMsgCancel:
193 		{
194 			if (_GetState() == STATE_FINAL_MESSAGE) {
195 				PostMessage(B_QUIT_REQUESTED);
196 				be_app->PostMessage(kMsgFinalQuit);
197 				break;
198 			}
199 			if (!fUpdateConfirmed) {
200 				// Downloads have not started yet, we will request to cancel
201 				// without confirming
202 				Lock();
203 				fHeaderView->SetText(B_TRANSLATE("Cancelling updates"));
204 				fDetailView->SetText(
205 					B_TRANSLATE("Attempting to cancel the updates..."));
206 				fCancelButton->SetEnabled(false);
207 				Unlock();
208 				fUserCancelRequested = true;
209 
210 				if (fWaitingForButton) {
211 					fButtonResult = message->what;
212 					delete_sem(fWaitingSem);
213 					fWaitingSem = -1;
214 				}
215 				break;
216 			}
217 
218 			// Confirm with the user to cancel
219 			BAlert* alert = new BAlert("cancel request", B_TRANSLATE("Updates"
220 				" have not been completed, are you sure you want to quit?"),
221 				B_TRANSLATE("Quit"), B_TRANSLATE("Don't quit"), NULL,
222 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
223 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
224 			alert->Go(&fCancelAlertResponse);
225 			break;
226 		}
227 
228 		case kMsgCancelResponse:
229 		{
230 			// Verify whether the cancel alert was confirmed
231 			int32 selection = -1;
232 			message->FindInt32("which", &selection);
233 			if (selection != 0)
234 				break;
235 
236 			Lock();
237 			fHeaderView->SetText(B_TRANSLATE("Cancelling updates"));
238 			fDetailView->SetText(
239 				B_TRANSLATE("Attempting to cancel the updates..."));
240 			fCancelButton->SetEnabled(false);
241 			Unlock();
242 			fUserCancelRequested = true;
243 
244 			if (fWaitingForButton) {
245 				fButtonResult = message->what;
246 				delete_sem(fWaitingSem);
247 				fWaitingSem = -1;
248 			}
249 			break;
250 		}
251 
252 		case kMsgUpdateConfirmed:
253 		{
254 			if (fWaitingForButton) {
255 				fButtonResult = message->what;
256 				delete_sem(fWaitingSem);
257 				fWaitingSem = -1;
258 				fUpdateConfirmed = true;
259 			}
260 			break;
261 		}
262 
263 		case kMsgMoreDetailsToggle:
264 			fListView->SetMoreDetails(fDetailsCheckbox->Value() != 0);
265 			break;
266 
267 		case kMsgWarningDismissed:
268 			fWarningAlertCount--;
269 			break;
270 
271 		case kMsgGetUpdateType:
272 		{
273 			BString text(
274 				B_TRANSLATE("Please choose from these update options:\n\n"
275 				"Update:\n"
276 				"	Updates all installed packages.\n"
277 				"Full sync:\n"
278 				"	Synchronizes the installed packages with the repositories."
279 				));
280 			BAlert* alert = new BAlert("update_type",
281 				text,
282 				B_TRANSLATE_COMMENT("Cancel", "Alert button label"),
283 				B_TRANSLATE_COMMENT("Full sync","Alert button label"),
284 				B_TRANSLATE_COMMENT("Update","Alert button label"),
285 				B_WIDTH_AS_USUAL, B_INFO_ALERT);
286 			int32 result = alert->Go();
287 			int32 action = INVALID_SELECTION;
288 			switch(result) {
289 				case 0:
290 					action = CANCEL_UPDATE;
291 					break;
292 
293 				case 1:
294 					action = FULLSYNC;
295 					break;
296 
297 				case 2:
298 					action = UPDATE;
299 					break;
300 			}
301 			BMessage reply;
302 			reply.AddInt32(kKeyAlertResult, action);
303 			message->SendReply(&reply);
304 			break;
305 		}
306 
307 		case kMsgNoRepositories:
308 		{
309 			BString text(
310 				B_TRANSLATE_COMMENT(
311 				"No remote repositories are available. Please verify that some"
312 				" repositories are enabled using the Repositories preflet or"
313 				" the \'pkgman\' command.", "Error message"));
314 			BAlert* alert = new BAlert("repositories", text,
315 				B_TRANSLATE_COMMENT("Quit", "Alert button label"),
316 				B_TRANSLATE_COMMENT("Open Repositories","Alert button label"),
317 				NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
318 			int32 result = alert->Go();
319 			BMessage reply;
320 			reply.AddInt32(kKeyAlertResult, result);
321 			message->SendReply(&reply);
322 			break;
323 		}
324 
325 		default:
326 			BWindow::MessageReceived(message);
327 	}
328 }
329 
330 
331 bool
332 SoftwareUpdaterWindow::ConfirmUpdates()
333 {
334 	Lock();
335 	fHeaderView->SetText(B_TRANSLATE("Updates found"));
336 	fDetailView->SetText(B_TRANSLATE("The following changes will be made:"));
337 	fListView->SortItems();
338 	Unlock();
339 
340 	uint32 priorState = _GetState();
341 	_SetState(STATE_GET_CONFIRMATION);
342 
343 	_WaitForButtonClick();
344 	_SetState(priorState);
345 	return fButtonResult == kMsgUpdateConfirmed;
346 }
347 
348 
349 void
350 SoftwareUpdaterWindow::UpdatesApplying(const char* header, const char* detail)
351 {
352 	Lock();
353 	fHeaderView->SetText(header);
354 	fDetailView->SetText(detail);
355 	Unlock();
356 	_SetState(STATE_APPLY_UPDATES);
357 }
358 
359 
360 bool
361 SoftwareUpdaterWindow::UserCancelRequested()
362 {
363 	if (_GetState() > STATE_GET_CONFIRMATION)
364 		return false;
365 
366 	return fUserCancelRequested;
367 }
368 
369 
370 void
371 SoftwareUpdaterWindow::AddPackageInfo(uint32 install_type,
372 	const char* package_name, const char* cur_ver, const char* new_ver,
373 	const char* summary, const char* repository, const char* file_name)
374 {
375 	Lock();
376 	fListView->AddPackage(install_type, package_name, cur_ver, new_ver,
377 		summary, repository, file_name);
378 	Unlock();
379 }
380 
381 
382 void
383 SoftwareUpdaterWindow::ShowWarningAlert(const char* text)
384 {
385 	BAlert* alert = new BAlert("warning", text, B_TRANSLATE("OK"), NULL, NULL,
386 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
387 	alert->Go(&fWarningAlertDismissed);
388 	alert->CenterIn(Frame());
389 	// Offset multiple alerts
390 	alert->MoveBy(fWarningAlertCount * 15, fWarningAlertCount * 15);
391 	fWarningAlertCount++;
392 }
393 
394 
395 BBitmap
396 SoftwareUpdaterWindow::GetIcon(int32 iconSize)
397 {
398 	BBitmap icon(BRect(0, 0, iconSize - 1, iconSize - 1), 0, B_RGBA32);
399 	team_info teamInfo;
400 	get_team_info(B_CURRENT_TEAM, &teamInfo);
401 	app_info appInfo;
402 	be_roster->GetRunningAppInfo(teamInfo.team, &appInfo);
403 	BNodeInfo::GetTrackerIcon(&appInfo.ref, &icon, icon_size(iconSize));
404 	return icon;
405 }
406 
407 
408 BBitmap
409 SoftwareUpdaterWindow::GetNotificationIcon()
410 {
411 	return GetIcon(B_LARGE_ICON);
412 }
413 
414 
415 void
416 SoftwareUpdaterWindow::FinalUpdate(const char* header, const char* detail)
417 {
418 	if (_GetState() == STATE_FINAL_MESSAGE)
419 		return;
420 
421 	_SetState(STATE_FINAL_MESSAGE);
422 	Lock();
423 	fHeaderView->SetText(header);
424 	fDetailView->SetText(detail);
425 	Unlock();
426 }
427 
428 
429 BLayoutItem*
430 SoftwareUpdaterWindow::layout_item_for(BView* view)
431 {
432 	BLayout* layout = view->Parent()->GetLayout();
433 	int32 index = layout->IndexOfView(view);
434 	return layout->ItemAt(index);
435 }
436 
437 
438 uint32
439 SoftwareUpdaterWindow::_WaitForButtonClick()
440 {
441 	fButtonResult = 0;
442 	fWaitingForButton = true;
443 	fWaitingSem = create_sem(0, "WaitingSem");
444 	while (acquire_sem(fWaitingSem) == B_INTERRUPTED) {
445 	}
446 	fWaitingForButton = false;
447 	return fButtonResult;
448 }
449 
450 
451 void
452 SoftwareUpdaterWindow::_SetState(uint32 state)
453 {
454 	if (state <= STATE_HEAD || state >= STATE_MAX)
455 		return;
456 
457 	Lock();
458 
459 	// Initial settings
460 	if (fCurrentState == STATE_HEAD) {
461 		fProgressLayoutItem->SetVisible(false);
462 		fPackagesLayoutItem->SetVisible(false);
463 		fDetailsCheckboxLayoutItem->SetVisible(false);
464 	}
465 	fCurrentState = state;
466 
467 	// Update confirmation button
468 	// Show only when asking for confirmation to update
469 	if (fCurrentState == STATE_GET_CONFIRMATION)
470 		fUpdateButtonLayoutItem->SetVisible(true);
471 	else
472 		fUpdateButtonLayoutItem->SetVisible(false);
473 
474 	// View package info view
475 	// Show at confirmation prompt, hide at final update
476 	if (fCurrentState == STATE_GET_CONFIRMATION) {
477 		fPackagesLayoutItem->SetVisible(true);
478 		fDetailsCheckboxLayoutItem->SetVisible(true);
479 		// Re-enable resizing
480 		float defaultWidth = fDefaultRect.Width();
481 		SetSizeLimits(defaultWidth, 9999,
482 			fDefaultRect.Height() + 4 * fListView->ItemHeight(), 9999);
483 		ResizeTo(defaultWidth, .75 * defaultWidth);
484 	} else if (fCurrentState == STATE_FINAL_MESSAGE) {
485 		fPackagesLayoutItem->SetVisible(false);
486 		fDetailsCheckboxLayoutItem->SetVisible(false);
487 		float defaultWidth = fDefaultRect.Width();
488 		float defaultHeight = fDefaultRect.Height();
489 		SetSizeLimits(defaultWidth, defaultWidth, defaultHeight,
490 			defaultHeight);
491 		ResizeTo(defaultWidth, defaultHeight);
492 	}
493 
494 	// Progress bar and string view
495 	// Hide detail text while showing status bar
496 	if (fCurrentState == STATE_DISPLAY_PROGRESS) {
497 		fDetailsLayoutItem->SetVisible(false);
498 		fProgressLayoutItem->SetVisible(true);
499 	} else {
500 		fProgressLayoutItem->SetVisible(false);
501 		fDetailsLayoutItem->SetVisible(true);
502 	}
503 
504 	// Cancel button
505 	if (fCurrentState == STATE_FINAL_MESSAGE)
506  		fCancelButton->SetLabel(B_TRANSLATE("Quit"));
507 	fCancelButton->SetEnabled(fCurrentState != STATE_APPLY_UPDATES);
508 
509 	Unlock();
510 }
511 
512 
513 uint32
514 SoftwareUpdaterWindow::_GetState()
515 {
516 	return fCurrentState;
517 }
518 
519 
520 SuperItem::SuperItem(const char* label)
521 	:
522 	BListItem(),
523 	fLabel(label),
524 	fShowMoreDetails(false),
525 	fPackageIcon(NULL),
526 	fItemCount(0)
527 {
528 }
529 
530 
531 SuperItem::~SuperItem()
532 {
533 	delete fPackageIcon;
534 }
535 
536 
537 void
538 SuperItem::DrawItem(BView* owner, BRect item_rect, bool complete)
539 {
540 	owner->PushState();
541 
542 	float width;
543     owner->GetPreferredSize(&width, NULL);
544     BString label(fLabel);
545     label.Append(" (");
546     label << fItemCount;
547     label.Append(")");
548     owner->TruncateString(&label, B_TRUNCATE_END, width);
549     owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
550     owner->SetFont(&fBoldFont);
551     owner->DrawString(label.String(), BPoint(item_rect.left,
552 		item_rect.bottom - fFontHeight.descent - 1));
553 
554 	owner->PopState();
555 }
556 
557 
558 void
559 SuperItem::Update(BView *owner, const BFont *font)
560 {
561 	fRegularFont = *font;
562 	fBoldFont = *font;
563 	fBoldFont.SetFace(B_BOLD_FACE);
564 	BListItem::Update(owner, &fBoldFont);
565 	_SetHeights();
566 }
567 
568 
569 void
570 SuperItem::SetDetailLevel(bool showMoreDetails)
571 {
572 	fShowMoreDetails = showMoreDetails;
573 	_SetHeights();
574 }
575 
576 
577 void
578 SuperItem::_SetHeights()
579 {
580 	// Calculate height for PackageItem
581 	fRegularFont.GetHeight(&fFontHeight);
582 	int lineCount = fShowMoreDetails ? 3 : 2;
583 	fPackageItemHeight = lineCount * (fFontHeight.ascent + fFontHeight.descent
584 		+ fFontHeight.leading);
585 
586 	// Calculate height for this item
587 	fBoldFont.GetHeight(&fFontHeight);
588 	SetHeight(fFontHeight.ascent + fFontHeight.descent
589 		+ fFontHeight.leading + 4);
590 
591 	_GetPackageIcon();
592 }
593 
594 
595 void
596 SuperItem::_GetPackageIcon()
597 {
598 	delete fPackageIcon;
599 	fIconSize = int(fPackageItemHeight * .8);
600 
601 	status_t result = B_ERROR;
602 	BRect iconRect(0, 0, fIconSize - 1, fIconSize - 1);
603 	fPackageIcon = new BBitmap(iconRect, 0, B_RGBA32);
604 	BMimeType nodeType;
605 	nodeType.SetTo("application/x-vnd.haiku-package");
606 	result = nodeType.GetIcon(fPackageIcon, icon_size(fIconSize));
607 	// Get super type icon
608 	if (result != B_OK) {
609 		BMimeType superType;
610 		if (nodeType.GetSupertype(&superType) == B_OK)
611 			result = superType.GetIcon(fPackageIcon, icon_size(fIconSize));
612 	}
613 	if (result != B_OK) {
614 		delete fPackageIcon;
615 		fPackageIcon = NULL;
616 	}
617 }
618 
619 
620 PackageItem::PackageItem(const char* name, const char* simple_version,
621 	const char* detailed_version, const char* repository, const char* summary,
622 	const char* file_name, SuperItem* super)
623 	:
624 	BListItem(),
625 	fName(name),
626 	fSimpleVersion(simple_version),
627 	fDetailedVersion(detailed_version),
628 	fRepository(repository),
629 	fSummary(summary),
630 	fSuperItem(super),
631 	fFileName(file_name),
632 	fDownloadProgress(0),
633 	fDrawBarFlag(false)
634 {
635 	fLabelOffset = be_control_look->DefaultLabelSpacing();
636 }
637 
638 
639 void
640 PackageItem::DrawItem(BView* owner, BRect item_rect, bool complete)
641 {
642 	owner->PushState();
643 
644 	float width;
645     owner->GetPreferredSize(&width, NULL);
646     float nameWidth = width / 2.0;
647     float offset_width = 0;
648     bool showMoreDetails = fSuperItem->GetDetailLevel();
649 
650 	BBitmap* icon = fSuperItem->GetIcon();
651 	if (icon != NULL && icon->IsValid()) {
652 		int16 iconSize = fSuperItem->GetIconSize();
653 		float offsetMarginHeight = floor((Height() - iconSize) / 2);
654 
655 		//owner->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
656 		owner->SetDrawingMode(B_OP_ALPHA);
657 		BPoint location = BPoint(item_rect.left,
658 			item_rect.top + offsetMarginHeight);
659 		owner->DrawBitmap(icon, location);
660 		owner->SetDrawingMode(B_OP_COPY);
661 
662 		if (fDrawBarFlag)
663 			_DrawBar(location, owner, icon_size(iconSize));
664 
665 		offset_width += iconSize + fLabelOffset;
666 	}
667 
668 	owner->SetFont(&fRegularFont);
669 	owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
670 
671 	// Package name
672 	font_height fontHeight = fSuperItem->GetFontHeight();
673     BString name(fName);
674     owner->TruncateString(&name, B_TRUNCATE_END, nameWidth);
675 	BPoint cursor(item_rect.left + offset_width,
676 		item_rect.bottom - fSmallTotalHeight - fontHeight.descent - 1);
677 	if (showMoreDetails)
678 		cursor.y -= fSmallTotalHeight + 1;
679 	owner->DrawString(name.String(), cursor);
680 	cursor.x += owner->StringWidth(name.String()) + fLabelOffset;
681 
682 	// Change font and color
683 	owner->SetFont(&fSmallFont);
684 	owner->SetHighColor(tint_color(ui_color(B_LIST_ITEM_TEXT_COLOR), 0.7));
685 
686 	// Simple version or repository
687 	BString versionOrRepo;
688 	if (showMoreDetails)
689 		versionOrRepo.SetTo(fRepository);
690 	else
691 		versionOrRepo.SetTo(fSimpleVersion);
692 	owner->TruncateString(&versionOrRepo, B_TRUNCATE_END, width - cursor.x);
693 	owner->DrawString(versionOrRepo.String(), cursor);
694 
695 	// Summary
696 	BString summary(fSummary);
697 	cursor.x = item_rect.left + offset_width;
698 	cursor.y += fSmallTotalHeight;
699 	owner->TruncateString(&summary, B_TRUNCATE_END, width - cursor.x);
700 	owner->DrawString(summary.String(), cursor);
701 
702 	// Detailed version
703 	if (showMoreDetails) {
704 		BString version(fDetailedVersion);
705 		cursor.y += fSmallTotalHeight;
706 		owner->TruncateString(&version, B_TRUNCATE_END, width - cursor.x);
707 		owner->DrawString(version.String(), cursor);
708 	}
709 
710 	owner->PopState();
711 }
712 
713 
714 // Modified slightly from Tracker's BPose::DrawBar
715 void
716 PackageItem::_DrawBar(BPoint where, BView* view, icon_size which)
717 {
718 	int32 yOffset;
719 	int32 size = which - 1;
720 	int32 barWidth = (int32)(7.0f / 32.0f * (float)which);
721 	if (barWidth < 4) {
722 		barWidth = 4;
723 		yOffset = 0;
724 	} else
725 		yOffset = 2;
726 	int32 barHeight = size - 3 - 2 * yOffset;
727 
728 
729 	// the black shadowed line
730 	view->SetHighColor(32, 32, 32, 92);
731 	view->MovePenTo(BPoint(where.x + size, where.y + 1 + yOffset));
732 	view->StrokeLine(BPoint(where.x + size, where.y + size - yOffset));
733 	view->StrokeLine(BPoint(where.x + size - barWidth + 1,
734 		where.y + size - yOffset));
735 
736 	view->SetDrawingMode(B_OP_ALPHA);
737 
738 	// the gray frame
739 	view->SetHighColor(76, 76, 76, 192);
740 	BRect rect(where.x + size - barWidth,where.y + yOffset,
741 		where.x + size - 1,where.y + size - 1 - yOffset);
742 	view->StrokeRect(rect);
743 
744 	// calculate bar height
745 	int32 barPos = barHeight - int32(barHeight * fDownloadProgress / 100.0);
746 	if (barPos < 0)
747 		barPos = 0;
748 	else if (barPos > barHeight)
749 		barPos = barHeight;
750 
751 	// the free space bar
752 	view->SetHighColor(255, 255, 255, 192);
753 
754 	rect.InsetBy(1,1);
755 	BRect bar(rect);
756 	bar.bottom = bar.top + barPos - 1;
757 	if (barPos > 0)
758 		view->FillRect(bar);
759 
760 	// the used space bar
761 	bar.top = bar.bottom + 1;
762 	bar.bottom = rect.bottom;
763 	view->SetHighColor(0, 203, 0, 192);
764 	view->FillRect(bar);
765 }
766 
767 
768 void
769 PackageItem::Update(BView *owner, const BFont *font)
770 {
771 	BListItem::Update(owner, font);
772 	SetItemHeight(font);
773 }
774 
775 
776 void
777 PackageItem::SetItemHeight(const BFont* font)
778 {
779 	SetHeight(fSuperItem->GetPackageItemHeight());
780 
781 	fRegularFont = *font;
782 	fSmallFont = *font;
783 	fSmallFont.SetSize(font->Size() - 2);
784 	fSmallFont.GetHeight(&fSmallFontHeight);
785 	fSmallTotalHeight = fSmallFontHeight.ascent + fSmallFontHeight.descent
786 		+ fSmallFontHeight.leading;
787 }
788 
789 
790 int
791 PackageItem::NameCompare(PackageItem* item)
792 {
793 	// sort by package name
794 	return fName.ICompare(item->fName);
795 }
796 
797 
798 void
799 PackageItem::SetDownloadProgress(float percent)
800 {
801 	fDownloadProgress = percent;
802 }
803 
804 
805 int
806 SortPackageItems(const BListItem* item1, const BListItem* item2)
807 {
808 	PackageItem* first = (PackageItem*)item1;
809 	PackageItem* second = (PackageItem*)item2;
810 	return first->NameCompare(second);
811 }
812 
813 
814 PackageListView::PackageListView()
815 	:
816 	BOutlineListView("Package list"),
817 	fSuperUpdateItem(NULL),
818 	fSuperInstallItem(NULL),
819 	fSuperUninstallItem(NULL),
820 	fShowMoreDetails(false),
821 	fLastProgressItem(NULL),
822 	fLastProgressValue(-1)
823 {
824 	SetExplicitMinSize(BSize(B_SIZE_UNSET, 40));
825 	SetExplicitPreferredSize(BSize(B_SIZE_UNSET, 400));
826 }
827 
828 
829 void
830 PackageListView::FrameResized(float newWidth, float newHeight)
831 {
832 	BOutlineListView::FrameResized(newWidth, newHeight);
833 
834 	float count = CountItems();
835 	for (int32 i = 0; i < count; i++) {
836 		BListItem *item = ItemAt(i);
837 		item->Update(this, be_plain_font);
838 	}
839 	Invalidate();
840 }
841 
842 
843 void
844 PackageListView::AddPackage(uint32 install_type, const char* name,
845 	const char* cur_ver, const char* new_ver, const char* summary,
846 	const char* repository, const char* file_name)
847 {
848 	SuperItem* super;
849 	BString simpleVersion;
850 	BString detailedVersion("");
851 	BString repositoryText(B_TRANSLATE_COMMENT("from repository",
852 		"List item text"));
853 	repositoryText.Append(" ").Append(repository);
854 
855 	switch (install_type) {
856 		case PACKAGE_UPDATE:
857 		{
858 			if (fSuperUpdateItem == NULL) {
859 				fSuperUpdateItem = new SuperItem(B_TRANSLATE_COMMENT(
860 					"Packages to be updated", "List super item label"));
861 				AddItem(fSuperUpdateItem);
862 			}
863 			super = fSuperUpdateItem;
864 
865 			simpleVersion.SetTo(new_ver);
866 			detailedVersion.Append(B_TRANSLATE_COMMENT("Updating version",
867 					"List item text"))
868 				.Append(" ").Append(cur_ver)
869 				.Append(" ").Append(B_TRANSLATE_COMMENT("to",
870 					"List item text"))
871 				.Append(" ").Append(new_ver);
872 			break;
873 		}
874 
875 		case PACKAGE_INSTALL:
876 		{
877 			if (fSuperInstallItem == NULL) {
878 				fSuperInstallItem = new SuperItem(B_TRANSLATE_COMMENT(
879 					"New packages to be installed", "List super item label"));
880 				AddItem(fSuperInstallItem);
881 			}
882 			super = fSuperInstallItem;
883 
884 			simpleVersion.SetTo(new_ver);
885 			detailedVersion.Append(B_TRANSLATE_COMMENT("Installing version",
886 					"List item text"))
887 				.Append(" ").Append(new_ver);
888 			break;
889 		}
890 
891 		case PACKAGE_UNINSTALL:
892 		{
893 			if (fSuperUninstallItem == NULL) {
894 				fSuperUninstallItem = new SuperItem(B_TRANSLATE_COMMENT(
895 					"Packages to be uninstalled", "List super item label"));
896 				AddItem(fSuperUninstallItem);
897 			}
898 			super = fSuperUninstallItem;
899 
900 			simpleVersion.SetTo("");
901 			detailedVersion.Append(B_TRANSLATE_COMMENT("Uninstalling version",
902 					"List item text"))
903 				.Append(" ").Append(cur_ver);
904 			break;
905 		}
906 
907 		default:
908 			return;
909 
910 	}
911 	PackageItem* item = new PackageItem(name, simpleVersion.String(),
912 		detailedVersion.String(), repositoryText.String(), summary, file_name,
913 		super);
914 	AddUnder(item, super);
915 	super->SetItemCount(CountItemsUnder(super, true));
916 }
917 
918 
919 void
920 PackageListView::UpdatePackageProgress(const char* packageName, float percent)
921 {
922 	// Update only every 1 percent change
923 	int16 wholePercent = int16(percent);
924 	if (wholePercent == fLastProgressValue)
925 		return;
926 	fLastProgressValue = wholePercent;
927 
928 	// A new package started downloading, find the PackageItem by name
929 	if (percent == 0) {
930 		fLastProgressItem = NULL;
931 		int32 count = FullListCountItems();
932 		for (int32 i = 0; i < count; i++) {
933 			PackageItem* item = dynamic_cast<PackageItem*>(FullListItemAt(i));
934 			if (item != NULL && strcmp(item->FileName(), packageName) == 0) {
935 				fLastProgressItem = item;
936 				fLastProgressItem->ShowProgressBar();
937 				break;
938 			}
939 		}
940 	}
941 
942 	if (fLastProgressItem != NULL) {
943 		fLastProgressItem->SetDownloadProgress(percent);
944 		Invalidate();
945 	}
946 }
947 
948 
949 void
950 PackageListView::SortItems()
951 {
952 	if (fSuperUpdateItem != NULL)
953 		SortItemsUnder(fSuperUpdateItem, true, SortPackageItems);
954 	if (fSuperInstallItem != NULL)
955 		SortItemsUnder(fSuperInstallItem, true, SortPackageItems);
956 	if (fSuperUninstallItem != NULL)
957 		SortItemsUnder(fSuperUninstallItem, true, SortPackageItems);
958 }
959 
960 
961 float
962 PackageListView::ItemHeight()
963 {
964 	if (fSuperUpdateItem != NULL)
965 		return fSuperUpdateItem->GetPackageItemHeight();
966 	if (fSuperInstallItem != NULL)
967 		return fSuperInstallItem->GetPackageItemHeight();
968 	if (fSuperUninstallItem != NULL)
969 		return fSuperUninstallItem->GetPackageItemHeight();
970 	return 0;
971 }
972 
973 
974 void
975 PackageListView::SetMoreDetails(bool showMore)
976 {
977 	fShowMoreDetails = showMore;
978 	_SetItemHeights();
979 	InvalidateLayout();
980 	ResizeToPreferred();
981 }
982 
983 
984 void
985 PackageListView::_SetItemHeights()
986 {
987 	int32 itemCount = 0;
988 	float itemHeight = 0;
989 	BListItem* item = NULL;
990 	if (fSuperUpdateItem != NULL) {
991 		fSuperUpdateItem->SetDetailLevel(fShowMoreDetails);
992 		itemHeight = fSuperUpdateItem->GetPackageItemHeight();
993 		itemCount = CountItemsUnder(fSuperUpdateItem, true);
994 		for (int32 i = 0; i < itemCount; i++) {
995 			item = ItemUnderAt(fSuperUpdateItem, true, i);
996 			item->SetHeight(itemHeight);
997 		}
998 	}
999 	if (fSuperInstallItem != NULL) {
1000 		fSuperInstallItem->SetDetailLevel(fShowMoreDetails);
1001 		itemHeight = fSuperInstallItem->GetPackageItemHeight();
1002 		itemCount = CountItemsUnder(fSuperInstallItem, true);
1003 		for (int32 i = 0; i < itemCount; i++) {
1004 			item = ItemUnderAt(fSuperInstallItem, true, i);
1005 			item->SetHeight(itemHeight);
1006 		}
1007 
1008 	}
1009 	if (fSuperUninstallItem != NULL) {
1010 		fSuperUninstallItem->SetDetailLevel(fShowMoreDetails);
1011 		itemHeight = fSuperUninstallItem->GetPackageItemHeight();
1012 		itemCount = CountItemsUnder(fSuperUninstallItem, true);
1013 		for (int32 i = 0; i < itemCount; i++) {
1014 			item = ItemUnderAt(fSuperUninstallItem, true, i);
1015 			item->SetHeight(itemHeight);
1016 		}
1017 
1018 	}
1019 }
1020