xref: /haiku/src/apps/softwareupdater/SoftwareUpdaterWindow.cpp (revision e81a954787e50e56a7f06f72705b7859b6ab06d1)
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@tycho.email>
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 <FindDirectory.h>
19 #include <LayoutBuilder.h>
20 #include <LayoutUtils.h>
21 #include <Message.h>
22 #include <Roster.h>
23 #include <Screen.h>
24 #include <String.h>
25 
26 #include "constants.h"
27 
28 #undef B_TRANSLATION_CONTEXT
29 #define B_TRANSLATION_CONTEXT "SoftwareUpdaterWindow"
30 
31 
32 SoftwareUpdaterWindow::SoftwareUpdaterWindow()
33 	:
34 	BWindow(BRect(0, 0, 300, 10),
35 		B_TRANSLATE_SYSTEM_NAME("SoftwareUpdater"), B_TITLED_WINDOW,
36 		B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_ZOOMABLE | B_NOT_RESIZABLE),
37 	fStripeView(NULL),
38 	fHeaderView(NULL),
39 	fDetailView(NULL),
40 	fUpdateButton(NULL),
41 	fCancelButton(NULL),
42 	fStatusBar(NULL),
43 	fCurrentState(STATE_HEAD),
44 	fWaitingSem(-1),
45 	fWaitingForButton(false),
46 	fUpdateConfirmed(false),
47 	fUserCancelRequested(false),
48 	fWarningAlertCount(0),
49 	fSettingsReadStatus(B_ERROR),
50 	fSaveFrameChanges(false),
51 	fMessageRunner(NULL),
52 	fFrameChangeMessage(kMsgWindowFrameChanged)
53 {
54 	// Layout
55 	BBitmap icon = GetIcon(32 * icon_layout_scale());
56 	fStripeView = new StripeView(icon);
57 
58 	fUpdateButton = new BButton(B_TRANSLATE("Update now"),
59 		new BMessage(kMsgUpdateConfirmed));
60 	fUpdateButton->MakeDefault(true);
61 	fCancelButton = new BButton(B_TRANSLATE("Cancel"),
62 		new BMessage(kMsgCancel));
63 
64 	fHeaderView = new BStringView("header",
65 		B_TRANSLATE("Checking for updates"), B_WILL_DRAW);
66 	fHeaderView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
67 	fHeaderView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
68 	fDetailView = new BStringView("detail", B_TRANSLATE("Contacting software "
69 		"repositories to check for package updates."), B_WILL_DRAW);
70 	fDetailView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
71 	fDetailView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
72 	fStatusBar = new BStatusBar("progress");
73 	fStatusBar->SetMaxValue(100);
74 
75 	fListView = new PackageListView();
76 	fScrollView = new BScrollView("scrollview", fListView, B_WILL_DRAW,
77 		false, true);
78 
79 	fDetailsCheckbox = new BCheckBox("detailscheckbox",
80 		B_TRANSLATE("Show more details"),
81 		new BMessage(kMsgMoreDetailsToggle));
82 
83 	BFont font;
84 	fHeaderView->GetFont(&font);
85 	font.SetFace(B_BOLD_FACE);
86 	font.SetSize(font.Size() * 1.5);
87 	fHeaderView->SetFont(&font,
88 		B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE | B_FONT_FLAGS);
89 
90 	BLayoutBuilder::Group<>(this, B_HORIZONTAL, B_USE_ITEM_SPACING)
91 		.Add(fStripeView)
92 		.AddGroup(B_VERTICAL, 0)
93 			.SetInsets(0, B_USE_WINDOW_SPACING,
94 				B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING)
95 			.AddGroup(new BGroupView(B_VERTICAL, B_USE_ITEM_SPACING))
96 				.Add(fHeaderView)
97 				.Add(fDetailView)
98 				.Add(fStatusBar)
99 				.Add(fScrollView)
100 			.End()
101 			.AddStrut(B_USE_SMALL_SPACING)
102 			.AddGroup(new BGroupView(B_HORIZONTAL))
103 				.Add(fDetailsCheckbox)
104 				.AddGlue()
105 				.Add(fCancelButton)
106 				.Add(fUpdateButton)
107 			.End()
108 		.End()
109 	.End();
110 
111 	fDetailsLayoutItem = layout_item_for(fDetailView);
112 	fProgressLayoutItem = layout_item_for(fStatusBar);
113 	fPackagesLayoutItem = layout_item_for(fScrollView);
114 	fCancelButtonLayoutItem = layout_item_for(fCancelButton);
115 	fUpdateButtonLayoutItem = layout_item_for(fUpdateButton);
116 	fDetailsCheckboxLayoutItem = layout_item_for(fDetailsCheckbox);
117 
118 	_SetState(STATE_DISPLAY_STATUS);
119 	CenterOnScreen();
120 	SetFlags(Flags() ^ B_AUTO_UPDATE_SIZE_LIMITS);
121 
122 	// Prevent resizing for now
123 	fDefaultRect = Bounds();
124 	SetSizeLimits(fDefaultRect.Width(), fDefaultRect.Width(),
125 		fDefaultRect.Height(), fDefaultRect.Height());
126 
127 	// Read settings file
128 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &fSettingsPath);
129 	if (status == B_OK) {
130 		fSettingsPath.Append(kSettingsFilename);
131 		fSettingsReadStatus = _ReadSettings(fInitialSettings);
132 	}
133 	// Move to saved setting position
134 	if (fSettingsReadStatus == B_OK) {
135 		BRect windowFrame;
136 		status = fInitialSettings.FindRect(kKeyWindowFrame, &windowFrame);
137 		if (status == B_OK) {
138 			BScreen screen(this);
139 			if (screen.Frame().Contains(windowFrame.LeftTop()))
140 				MoveTo(windowFrame.LeftTop());
141 		}
142 	}
143 	Show();
144 
145 	BMessage registerMessage(kMsgRegister);
146 	registerMessage.AddMessenger(kKeyMessenger, BMessenger(this));
147 	be_app->PostMessage(&registerMessage);
148 
149 	fCancelAlertResponse.SetMessage(new BMessage(kMsgCancelResponse));
150 	fCancelAlertResponse.SetTarget(this);
151 	fWarningAlertDismissed.SetMessage(new BMessage(kMsgWarningDismissed));
152 	fWarningAlertDismissed.SetTarget(this);
153 
154 	// Common elements used for the zoom height and width calculations
155 	fZoomHeightBaseline = 6
156 		+ be_control_look->ComposeSpacing(B_USE_SMALL_SPACING)
157 		+ 2 * be_control_look->ComposeSpacing(B_USE_WINDOW_SPACING);
158 	fZoomWidthBaseline = fStripeView->PreferredSize().Width()
159 		+ be_control_look->ComposeSpacing(B_USE_ITEM_SPACING)
160 		+ fScrollView->ScrollBar(B_VERTICAL)->PreferredSize().Width()
161 		+ be_control_look->ComposeSpacing(B_USE_WINDOW_SPACING);
162 }
163 
164 
165 bool
166 SoftwareUpdaterWindow::QuitRequested()
167 {
168 	PostMessage(kMsgCancel);
169 	return false;
170 }
171 
172 
173 void
174 SoftwareUpdaterWindow::FrameMoved(BPoint newPosition)
175 {
176 	BWindow::FrameMoved(newPosition);
177 
178 	// Create a message runner to consolidate all function calls from a
179 	// move into one message post after moving has ceased for .5 seconds
180 	if (fSaveFrameChanges) {
181 		if (fMessageRunner == NULL) {
182 			fMessageRunner = new BMessageRunner(this, &fFrameChangeMessage,
183 				500000, 1);
184 		} else
185 			fMessageRunner->SetInterval(500000);
186 	}
187 }
188 
189 
190 void
191 SoftwareUpdaterWindow::FrameResized(float newWidth, float newHeight)
192 {
193 	BWindow::FrameResized(newWidth, newHeight);
194 
195 	// Create a message runner to consolidate all function calls from a
196 	// resize into one message post after resizing has ceased for .5 seconds
197 	if (fSaveFrameChanges) {
198 		if (fMessageRunner == NULL) {
199 			fMessageRunner = new BMessageRunner(this, &fFrameChangeMessage,
200 				500000, 1);
201 		} else
202 			fMessageRunner->SetInterval(500000);
203 	}
204 }
205 
206 
207 void
208 SoftwareUpdaterWindow::Zoom(BPoint origin, float width, float height)
209 {
210 	// Override default zoom behavior and keep window at same position instead
211 	// of centering on screen
212 	BWindow::Zoom(Frame().LeftTop(), width, height);
213 }
214 
215 
216 void
217 SoftwareUpdaterWindow::MessageReceived(BMessage* message)
218 {
219 	switch (message->what) {
220 
221 		case kMsgTextUpdate:
222 		{
223 			if (fCurrentState == STATE_DISPLAY_PROGRESS)
224 				_SetState(STATE_DISPLAY_STATUS);
225 			else if (fCurrentState != STATE_DISPLAY_STATUS)
226 				break;
227 
228 			BString header;
229 			BString detail;
230 			Lock();
231 			status_t result = message->FindString(kKeyHeader, &header);
232 			if (result == B_OK && header != fHeaderView->Text())
233 				fHeaderView->SetText(header.String());
234 			result = message->FindString(kKeyDetail, &detail);
235 			if (result == B_OK)
236 				fDetailView->SetText(detail.String());
237 			Unlock();
238 			break;
239 		}
240 
241 		case kMsgProgressUpdate:
242 		{
243 			if (fCurrentState == STATE_DISPLAY_STATUS)
244 				_SetState(STATE_DISPLAY_PROGRESS);
245 			else if (fCurrentState != STATE_DISPLAY_PROGRESS)
246 				break;
247 
248 			BString packageName;
249 			status_t result = message->FindString(kKeyPackageName,
250 				&packageName);
251 			if (result != B_OK)
252 				break;
253 			BString packageCount;
254 			result = message->FindString(kKeyPackageCount, &packageCount);
255 			if (result != B_OK)
256 				break;
257 			float percent;
258 			result = message->FindFloat(kKeyPercentage, &percent);
259 			if (result != B_OK)
260 				break;
261 
262 			BString header;
263 			Lock();
264 			result = message->FindString(kKeyHeader, &header);
265 			if (result == B_OK && header != fHeaderView->Text())
266 				fHeaderView->SetText(header.String());
267 			fStatusBar->SetTo(percent, packageName.String(),
268 				packageCount.String());
269 			Unlock();
270 
271 			fListView->UpdatePackageProgress(packageName.String(), percent);
272 			break;
273 		}
274 
275 		case kMsgCancel:
276 		{
277 			if (_GetState() == STATE_FINAL_MESSAGE) {
278 				be_app->PostMessage(kMsgFinalQuit);
279 				break;
280 			}
281 			if (!fUpdateConfirmed) {
282 				// Downloads have not started yet, we will request to cancel
283 				// without confirming
284 				Lock();
285 				fHeaderView->SetText(B_TRANSLATE("Cancelling updates"));
286 				fDetailView->SetText(
287 					B_TRANSLATE("Attempting to cancel the updates..."));
288 				Unlock();
289 				fUserCancelRequested = true;
290 
291 				if (fWaitingForButton) {
292 					fButtonResult = message->what;
293 					delete_sem(fWaitingSem);
294 					fWaitingSem = -1;
295 				}
296 				break;
297 			}
298 
299 			// Confirm with the user to cancel
300 			BAlert* alert = new BAlert("cancel request", B_TRANSLATE("Updates"
301 				" have not been completed, are you sure you want to quit?"),
302 				B_TRANSLATE("Quit"), B_TRANSLATE("Don't quit"), NULL,
303 				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
304 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
305 			alert->Go(&fCancelAlertResponse);
306 			break;
307 		}
308 
309 		case kMsgCancelResponse:
310 		{
311 			// Verify whether the cancel alert was confirmed
312 			int32 selection = -1;
313 			message->FindInt32("which", &selection);
314 			if (selection != 0)
315 				break;
316 
317 			Lock();
318 			fHeaderView->SetText(B_TRANSLATE("Cancelling updates"));
319 			fDetailView->SetText(
320 				B_TRANSLATE("Attempting to cancel the updates..."));
321 			Unlock();
322 			fUserCancelRequested = true;
323 
324 			if (fWaitingForButton) {
325 				fButtonResult = message->what;
326 				delete_sem(fWaitingSem);
327 				fWaitingSem = -1;
328 			}
329 			break;
330 		}
331 
332 		case kMsgUpdateConfirmed:
333 		{
334 			if (fWaitingForButton) {
335 				fButtonResult = message->what;
336 				delete_sem(fWaitingSem);
337 				fWaitingSem = -1;
338 				fUpdateConfirmed = true;
339 			}
340 			break;
341 		}
342 
343 		case kMsgMoreDetailsToggle:
344 			fListView->SetMoreDetails(fDetailsCheckbox->Value() != 0);
345 			PostMessage(kMsgSetZoomLimits);
346 			_WriteSettings();
347 			break;
348 
349 		case kMsgSetZoomLimits:
350 		{
351 			int32 count = fListView->CountItems();
352 			if (count < 1)
353 				break;
354 			// Convert last item's bottom point to its layout group coordinates
355 			BPoint zoomPoint = fListView->ZoomPoint();
356 			fScrollView->ConvertToParent(&zoomPoint);
357 			// Determine which BControl object height to use
358 			float controlHeight;
359 			if (fUpdateButtonLayoutItem->IsVisible())
360 				fUpdateButton->GetPreferredSize(NULL, &controlHeight);
361 			else
362 				fDetailsCheckbox->GetPreferredSize(NULL, &controlHeight);
363 			// Calculate height and width values
364 			float zoomHeight = fZoomHeightBaseline + zoomPoint.y
365 				+ controlHeight;
366 			float zoomWidth = fZoomWidthBaseline + zoomPoint.x;
367 			SetZoomLimits(zoomWidth, zoomHeight);
368 			break;
369 		}
370 
371 		case kMsgWarningDismissed:
372 			fWarningAlertCount--;
373 			break;
374 
375 		case kMsgWindowFrameChanged:
376 			delete fMessageRunner;
377 			fMessageRunner = NULL;
378 			_WriteSettings();
379 			break;
380 
381 		case kMsgGetUpdateType:
382 		{
383 			BString text(
384 				B_TRANSLATE("Please choose from these update options:\n\n"
385 				"Update:\n"
386 				"	Updates all installed packages.\n"
387 				"Full sync:\n"
388 				"	Synchronizes the installed packages with the repositories."
389 				));
390 			BAlert* alert = new BAlert("update_type",
391 				text,
392 				B_TRANSLATE_COMMENT("Cancel", "Alert button label"),
393 				B_TRANSLATE_COMMENT("Full sync","Alert button label"),
394 				B_TRANSLATE_COMMENT("Update","Alert button label"),
395 				B_WIDTH_AS_USUAL, B_INFO_ALERT);
396 			int32 result = alert->Go();
397 			int32 action = INVALID_SELECTION;
398 			switch(result) {
399 				case 0:
400 					action = CANCEL_UPDATE;
401 					break;
402 
403 				case 1:
404 					action = FULLSYNC;
405 					break;
406 
407 				case 2:
408 					action = UPDATE;
409 					break;
410 			}
411 			BMessage reply;
412 			reply.AddInt32(kKeyAlertResult, action);
413 			message->SendReply(&reply);
414 			break;
415 		}
416 
417 		case kMsgNoRepositories:
418 		{
419 			BString text(
420 				B_TRANSLATE_COMMENT(
421 				"No remote repositories are available. Please verify that some"
422 				" repositories are enabled using the Repositories preflet or"
423 				" the \'pkgman\' command.", "Error message"));
424 			BAlert* alert = new BAlert("repositories", text,
425 				B_TRANSLATE_COMMENT("Quit", "Alert button label"),
426 				B_TRANSLATE_COMMENT("Open Repositories","Alert button label"),
427 				NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
428 			int32 result = alert->Go();
429 			BMessage reply;
430 			reply.AddInt32(kKeyAlertResult, result);
431 			message->SendReply(&reply);
432 			break;
433 		}
434 
435 		default:
436 			BWindow::MessageReceived(message);
437 	}
438 }
439 
440 
441 bool
442 SoftwareUpdaterWindow::ConfirmUpdates()
443 {
444 	Lock();
445 	fHeaderView->SetText(B_TRANSLATE("Updates found"));
446 	fDetailView->SetText(B_TRANSLATE("The following changes will be made:"));
447 	fListView->SortItems();
448 	Unlock();
449 
450 	uint32 priorState = _GetState();
451 	_SetState(STATE_GET_CONFIRMATION);
452 
453 	_WaitForButtonClick();
454 	_SetState(priorState);
455 	return fButtonResult == kMsgUpdateConfirmed;
456 }
457 
458 
459 void
460 SoftwareUpdaterWindow::UpdatesApplying(const char* header, const char* detail)
461 {
462 	Lock();
463 	fHeaderView->SetText(header);
464 	fDetailView->SetText(detail);
465 	Unlock();
466 	_SetState(STATE_APPLY_UPDATES);
467 }
468 
469 
470 bool
471 SoftwareUpdaterWindow::UserCancelRequested()
472 {
473 	if (_GetState() > STATE_GET_CONFIRMATION)
474 		return false;
475 
476 	return fUserCancelRequested;
477 }
478 
479 
480 void
481 SoftwareUpdaterWindow::AddPackageInfo(uint32 install_type,
482 	const char* package_name, const char* cur_ver, const char* new_ver,
483 	const char* summary, const char* repository, const char* file_name)
484 {
485 	Lock();
486 	fListView->AddPackage(install_type, package_name, cur_ver, new_ver,
487 		summary, repository, file_name);
488 	Unlock();
489 }
490 
491 
492 void
493 SoftwareUpdaterWindow::ShowWarningAlert(const char* text)
494 {
495 	BAlert* alert = new BAlert("warning", text, B_TRANSLATE("OK"), NULL, NULL,
496 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
497 	alert->Go(&fWarningAlertDismissed);
498 	alert->CenterIn(Frame());
499 	// Offset multiple alerts
500 	alert->MoveBy(fWarningAlertCount * 15, fWarningAlertCount * 15);
501 	fWarningAlertCount++;
502 }
503 
504 
505 BBitmap
506 SoftwareUpdaterWindow::GetIcon(int32 iconSize)
507 {
508 	BBitmap icon(BRect(0, 0, iconSize - 1, iconSize - 1), 0, B_RGBA32);
509 	team_info teamInfo;
510 	get_team_info(B_CURRENT_TEAM, &teamInfo);
511 	app_info appInfo;
512 	be_roster->GetRunningAppInfo(teamInfo.team, &appInfo);
513 	BNodeInfo::GetTrackerIcon(&appInfo.ref, &icon, icon_size(iconSize));
514 	return icon;
515 }
516 
517 
518 void
519 SoftwareUpdaterWindow::FinalUpdate(const char* header, const char* detail)
520 {
521 	if (_GetState() == STATE_FINAL_MESSAGE)
522 		return;
523 
524 	_SetState(STATE_FINAL_MESSAGE);
525 	Lock();
526 	fHeaderView->SetText(header);
527 	fDetailView->SetText(detail);
528 	Unlock();
529 }
530 
531 
532 BLayoutItem*
533 SoftwareUpdaterWindow::layout_item_for(BView* view)
534 {
535 	BLayout* layout = view->Parent()->GetLayout();
536 	int32 index = layout->IndexOfView(view);
537 	return layout->ItemAt(index);
538 }
539 
540 
541 uint32
542 SoftwareUpdaterWindow::_WaitForButtonClick()
543 {
544 	fButtonResult = 0;
545 	fWaitingForButton = true;
546 	fWaitingSem = create_sem(0, "WaitingSem");
547 	while (acquire_sem(fWaitingSem) == B_INTERRUPTED) {
548 	}
549 	fWaitingForButton = false;
550 	return fButtonResult;
551 }
552 
553 
554 void
555 SoftwareUpdaterWindow::_SetState(uint32 state)
556 {
557 	if (state <= STATE_HEAD || state >= STATE_MAX)
558 		return;
559 
560 	Lock();
561 
562 	// Initial settings
563 	if (fCurrentState == STATE_HEAD) {
564 		fProgressLayoutItem->SetVisible(false);
565 		fPackagesLayoutItem->SetVisible(false);
566 		fDetailsCheckboxLayoutItem->SetVisible(false);
567 		fCancelButtonLayoutItem->SetVisible(false);
568 	}
569 	fCurrentState = state;
570 
571 	// Update confirmation button
572 	// Show only when asking for confirmation to update
573 	if (fCurrentState == STATE_GET_CONFIRMATION)
574 		fUpdateButtonLayoutItem->SetVisible(true);
575 	else
576 		fUpdateButtonLayoutItem->SetVisible(false);
577 
578 	// View package info view and checkbox
579 	// Show at confirmation prompt, hide at final update
580 	if (fCurrentState == STATE_GET_CONFIRMATION) {
581 		fPackagesLayoutItem->SetVisible(true);
582 		fDetailsCheckboxLayoutItem->SetVisible(true);
583 		if (fSettingsReadStatus == B_OK) {
584 			bool showMoreDetails;
585 			status_t result = fInitialSettings.FindBool(kKeyShowDetails,
586 				&showMoreDetails);
587 			if (result == B_OK) {
588 				fDetailsCheckbox->SetValue(showMoreDetails ? 1 : 0);
589 				fListView->SetMoreDetails(showMoreDetails);
590 			}
591 		}
592 	} else if (fCurrentState == STATE_FINAL_MESSAGE) {
593 		fPackagesLayoutItem->SetVisible(false);
594 		fDetailsCheckboxLayoutItem->SetVisible(false);
595 	}
596 
597 	// Progress bar and string view
598 	// Hide detail text while showing status bar
599 	if (fCurrentState == STATE_DISPLAY_PROGRESS) {
600 		fDetailsLayoutItem->SetVisible(false);
601 		fProgressLayoutItem->SetVisible(true);
602 	} else {
603 		fProgressLayoutItem->SetVisible(false);
604 		fDetailsLayoutItem->SetVisible(true);
605 	}
606 
607 	// Resizing and zooming
608 	if (fCurrentState == STATE_GET_CONFIRMATION) {
609 		// Enable resizing and zooming
610 		float defaultWidth = fDefaultRect.Width();
611 		SetSizeLimits(defaultWidth, B_SIZE_UNLIMITED,
612 			fDefaultRect.Height() + 4 * fListView->ItemHeight(),
613 			B_SIZE_UNLIMITED);
614 		SetFlags(Flags() ^ (B_NOT_RESIZABLE | B_NOT_ZOOMABLE));
615 		PostMessage(kMsgSetZoomLimits);
616 		// Recall saved settings
617 		BScreen screen(this);
618 		BRect screenFrame = screen.Frame();
619 		bool windowResized = false;
620 		if (fSettingsReadStatus == B_OK) {
621 			BRect windowFrame;
622 			status_t result = fInitialSettings.FindRect(kKeyWindowFrame,
623 				&windowFrame);
624 			if (result == B_OK) {
625 				if (screenFrame.Contains(windowFrame)) {
626 					ResizeTo(windowFrame.Width(), windowFrame.Height());
627 					windowResized = true;
628 				}
629 			}
630 		}
631 		if (!windowResized)
632 			ResizeTo(defaultWidth, .75 * defaultWidth);
633 		// Check that the bottom of window is on screen
634 		float screenBottom = screenFrame.bottom;
635 		float windowBottom = DecoratorFrame().bottom;
636 		if (windowBottom > screenBottom)
637 			MoveBy(0, screenBottom - windowBottom);
638 		fSaveFrameChanges = true;
639 	} else if (fUpdateConfirmed && (fCurrentState == STATE_DISPLAY_PROGRESS
640 			|| fCurrentState == STATE_DISPLAY_STATUS)) {
641 		PostMessage(kMsgSetZoomLimits);
642 	} else if (fCurrentState == STATE_APPLY_UPDATES)
643 		fSaveFrameChanges = false;
644 	else if (fCurrentState == STATE_FINAL_MESSAGE) {
645 		// Disable resizing and zooming
646 		fSaveFrameChanges = false;
647 		ResizeTo(fDefaultRect.Width(), fDefaultRect.Height());
648 		SetFlags(Flags() | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_RESIZABLE
649 			| B_NOT_ZOOMABLE);
650 	}
651 
652 	// Quit button
653 	if (fCurrentState == STATE_FINAL_MESSAGE) {
654 		fCancelButtonLayoutItem->SetVisible(true);
655  		fCancelButton->SetLabel(B_TRANSLATE_COMMENT("Quit", "Button label"));
656  		fCancelButton->MakeDefault(true);
657 	}
658 
659 	Unlock();
660 }
661 
662 
663 uint32
664 SoftwareUpdaterWindow::_GetState()
665 {
666 	return fCurrentState;
667 }
668 
669 
670 status_t
671 SoftwareUpdaterWindow::_WriteSettings()
672 {
673 	BFile file;
674 	status_t status = file.SetTo(fSettingsPath.Path(),
675 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
676 	if (status == B_OK) {
677 		BMessage settings;
678 		settings.AddBool(kKeyShowDetails, fDetailsCheckbox->Value() != 0);
679 		settings.AddRect(kKeyWindowFrame, Frame());
680 		status = settings.Flatten(&file);
681 	}
682 	file.Unset();
683 	return status;
684 }
685 
686 
687 status_t
688 SoftwareUpdaterWindow::_ReadSettings(BMessage& settings)
689 {
690 	BFile file;
691 	status_t status = file.SetTo(fSettingsPath.Path(), B_READ_ONLY);
692 	if (status == B_OK)
693 		status = settings.Unflatten(&file);
694 	file.Unset();
695 	return status;
696 }
697 
698 
699 SuperItem::SuperItem(const char* label)
700 	:
701 	BListItem(),
702 	fLabel(label),
703 	fRegularFont(be_plain_font),
704 	fBoldFont(be_plain_font),
705 	fShowMoreDetails(false),
706 	fPackageLessIcon(NULL),
707 	fPackageMoreIcon(NULL),
708 	fItemCount(0)
709 {
710 	fBoldFont.SetFace(B_BOLD_FACE);
711 	fBoldFont.GetHeight(&fBoldFontHeight);
712 	font_height fontHeight;
713 	fRegularFont.GetHeight(&fontHeight);
714 	fPackageItemLineHeight = fontHeight.ascent + fontHeight.descent
715 		+ fontHeight.leading;
716 	fPackageLessIcon = _GetPackageIcon(GetPackageItemHeight(false));
717 	fPackageMoreIcon = _GetPackageIcon(GetPackageItemHeight(true));
718 }
719 
720 
721 SuperItem::~SuperItem()
722 {
723 	delete fPackageLessIcon;
724 	delete fPackageMoreIcon;
725 }
726 
727 
728 void
729 SuperItem::DrawItem(BView* owner, BRect item_rect, bool complete)
730 {
731 	owner->PushState();
732 
733 	float width;
734 	owner->GetPreferredSize(&width, NULL);
735 	BString text(fItemText);
736 	owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
737 	owner->SetFont(&fBoldFont);
738 	owner->TruncateString(&text, B_TRUNCATE_END, width);
739 	owner->DrawString(text.String(), BPoint(item_rect.left,
740 		item_rect.bottom - fBoldFontHeight.descent));
741 
742 	owner->PopState();
743 }
744 
745 
746 float
747 SuperItem::GetPackageItemHeight()
748 {
749 	return GetPackageItemHeight(fShowMoreDetails);
750 }
751 
752 
753 float
754 SuperItem::GetPackageItemHeight(bool showMoreDetails)
755 {
756 	int lineCount = showMoreDetails ? 3 : 2;
757 	return lineCount * fPackageItemLineHeight;
758 }
759 
760 
761 BBitmap*
762 SuperItem::GetIcon(bool showMoreDetails)
763 {
764 	if (showMoreDetails)
765 		return fPackageMoreIcon;
766 	else
767 		return fPackageLessIcon;
768 }
769 
770 
771 float
772 SuperItem::GetIconSize(bool showMoreDetails)
773 {
774 	if (showMoreDetails)
775 		return fPackageMoreIcon->Bounds().Height();
776 	else
777 		return fPackageLessIcon->Bounds().Height();
778 }
779 
780 
781 void
782 SuperItem::SetDetailLevel(bool showMoreDetails)
783 {
784 	fShowMoreDetails = showMoreDetails;
785 }
786 
787 
788 void
789 SuperItem::SetItemCount(int32 count)
790 {
791 	fItemCount = count;
792 	fItemText = fLabel;
793 	fItemText.Append(" (");
794 	fItemText << fItemCount;
795 	fItemText.Append(")");
796 }
797 
798 
799 float
800 SuperItem::ZoomWidth(BView *owner)
801 {
802 	owner->PushState();
803 	owner->SetFont(&fBoldFont);
804 	float width = owner->StringWidth(fItemText.String());
805 	owner->PopState();
806 	return width;
807 }
808 
809 
810 BBitmap*
811 SuperItem::_GetPackageIcon(float listItemHeight)
812 {
813 	int32 iconSize = int(listItemHeight * .8);
814 	status_t result = B_ERROR;
815 	BRect iconRect(0, 0, iconSize - 1, iconSize - 1);
816 	BBitmap* packageIcon = new BBitmap(iconRect, 0, B_RGBA32);
817 	BMimeType nodeType;
818 	nodeType.SetTo("application/x-vnd.haiku-package");
819 	result = nodeType.GetIcon(packageIcon, icon_size(iconSize));
820 	// Get super type icon
821 	if (result != B_OK) {
822 		BMimeType superType;
823 		if (nodeType.GetSupertype(&superType) == B_OK)
824 			result = superType.GetIcon(packageIcon, icon_size(iconSize));
825 	}
826 	if (result != B_OK) {
827 		delete packageIcon;
828 		return NULL;
829 	}
830 	return packageIcon;
831 }
832 
833 
834 PackageItem::PackageItem(const char* name, const char* simple_version,
835 	const char* detailed_version, const char* repository, const char* summary,
836 	const char* file_name, SuperItem* super)
837 	:
838 	BListItem(),
839 	fName(name),
840 	fSimpleVersion(simple_version),
841 	fDetailedVersion(detailed_version),
842 	fRepository(repository),
843 	fSummary(summary),
844 	fSmallFont(be_plain_font),
845 	fSuperItem(super),
846 	fFileName(file_name),
847 	fDownloadProgress(0),
848 	fDrawBarFlag(false),
849 	fMoreDetailsWidth(0),
850 	fLessDetailsWidth(0)
851 {
852 	fLabelOffset = be_control_look->DefaultLabelSpacing();
853 	fSmallFont.SetSize(be_plain_font->Size() - 2);
854 	fSmallFont.GetHeight(&fSmallFontHeight);
855 	fSmallTotalHeight = fSmallFontHeight.ascent + fSmallFontHeight.descent
856 		+ fSmallFontHeight.leading;
857 }
858 
859 
860 void
861 PackageItem::DrawItem(BView* owner, BRect item_rect, bool complete)
862 {
863 	owner->PushState();
864 
865 	float width = owner->Frame().Width();
866     float nameWidth = width / 2.0;
867     float offsetWidth = 0;
868     bool showMoreDetails = fSuperItem->GetDetailLevel();
869 
870 	BBitmap* icon = fSuperItem->GetIcon(showMoreDetails);
871 	if (icon != NULL && icon->IsValid()) {
872 		float iconSize = icon->Bounds().Height();
873 		float offsetMarginHeight = floor((Height() - iconSize) / 2);
874 		owner->SetDrawingMode(B_OP_ALPHA);
875 		BPoint location = BPoint(item_rect.left,
876 			item_rect.top + offsetMarginHeight);
877 		owner->DrawBitmap(icon, location);
878 		owner->SetDrawingMode(B_OP_COPY);
879 		offsetWidth = iconSize + fLabelOffset;
880 
881 		if (fDrawBarFlag)
882 			_DrawBar(location, owner, icon_size(iconSize));
883 	}
884 
885 	owner->SetFont(be_plain_font);
886 	owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
887 
888 	// Package name
889     BString name(fName);
890     owner->TruncateString(&name, B_TRUNCATE_END, nameWidth);
891 	BPoint cursor(item_rect.left + offsetWidth,
892 		item_rect.bottom - fSmallTotalHeight - fSmallFontHeight.descent - 2);
893 	if (showMoreDetails)
894 		cursor.y -= fSmallTotalHeight + 1;
895 	owner->DrawString(name.String(), cursor);
896 	cursor.x += owner->StringWidth(name.String()) + fLabelOffset;
897 
898 	// Change font and color
899 	owner->SetFont(&fSmallFont);
900 	owner->SetHighColor(tint_color(ui_color(B_LIST_ITEM_TEXT_COLOR), 0.7));
901 
902 	// Simple version or repository
903 	BString versionOrRepo;
904 	if (showMoreDetails)
905 		versionOrRepo.SetTo(fRepository);
906 	else
907 		versionOrRepo.SetTo(fSimpleVersion);
908 	owner->TruncateString(&versionOrRepo, B_TRUNCATE_END, width - cursor.x);
909 	owner->DrawString(versionOrRepo.String(), cursor);
910 
911 	// Summary
912 	BString summary(fSummary);
913 	cursor.x = item_rect.left + offsetWidth;
914 	cursor.y += fSmallTotalHeight;
915 	owner->TruncateString(&summary, B_TRUNCATE_END, width - cursor.x);
916 	owner->DrawString(summary.String(), cursor);
917 
918 	// Detailed version
919 	if (showMoreDetails) {
920 		BString version(fDetailedVersion);
921 		cursor.y += fSmallTotalHeight;
922 		owner->TruncateString(&version, B_TRUNCATE_END, width - cursor.x);
923 		owner->DrawString(version.String(), cursor);
924 	}
925 
926 	owner->PopState();
927 }
928 
929 
930 // Modified slightly from Tracker's BPose::DrawBar
931 void
932 PackageItem::_DrawBar(BPoint where, BView* view, icon_size which)
933 {
934 	int32 yOffset;
935 	int32 size = which - 1;
936 	int32 barWidth = (int32)(7.0f / 32.0f * (float)which);
937 	if (barWidth < 4) {
938 		barWidth = 4;
939 		yOffset = 0;
940 	} else
941 		yOffset = 2;
942 	int32 barHeight = size - 3 - 2 * yOffset;
943 
944 
945 	// the black shadowed line
946 	view->SetHighColor(32, 32, 32, 92);
947 	view->MovePenTo(BPoint(where.x + size, where.y + 1 + yOffset));
948 	view->StrokeLine(BPoint(where.x + size, where.y + size - yOffset));
949 	view->StrokeLine(BPoint(where.x + size - barWidth + 1,
950 		where.y + size - yOffset));
951 
952 	view->SetDrawingMode(B_OP_ALPHA);
953 
954 	// the gray frame
955 	view->SetHighColor(76, 76, 76, 192);
956 	BRect rect(where.x + size - barWidth,where.y + yOffset,
957 		where.x + size - 1,where.y + size - 1 - yOffset);
958 	view->StrokeRect(rect);
959 
960 	// calculate bar height
961 	int32 barPos = barHeight - int32(barHeight * fDownloadProgress / 100.0);
962 	if (barPos < 0)
963 		barPos = 0;
964 	else if (barPos > barHeight)
965 		barPos = barHeight;
966 
967 	// the free space bar
968 	view->SetHighColor(255, 255, 255, 192);
969 
970 	rect.InsetBy(1,1);
971 	BRect bar(rect);
972 	bar.bottom = bar.top + barPos - 1;
973 	if (barPos > 0)
974 		view->FillRect(bar);
975 
976 	// the used space bar
977 	bar.top = bar.bottom + 1;
978 	bar.bottom = rect.bottom;
979 	view->SetHighColor(0, 203, 0, 192);
980 	view->FillRect(bar);
981 }
982 
983 
984 void
985 PackageItem::Update(BView *owner, const BFont *font)
986 {
987 	BListItem::Update(owner, font);
988 	SetHeight(fSuperItem->GetPackageItemHeight());
989 }
990 
991 
992 void
993 PackageItem::CalculateZoomWidths(BView *owner)
994 {
995 	owner->PushState();
996 
997 	// More details
998 	float offsetWidth = 2 * be_control_look->DefaultItemSpacing()
999 		+ be_plain_font->Size()
1000 		+ fSuperItem->GetIconSize(true) + fLabelOffset;
1001 	// Name and repo
1002 	owner->SetFont(be_plain_font);
1003 	float stringWidth = owner->StringWidth(fName.String());
1004 	owner->SetFont(&fSmallFont);
1005 	stringWidth += fLabelOffset + owner->StringWidth(fRepository.String());
1006 	// Summary
1007 	float summaryWidth = owner->StringWidth(fSummary.String());
1008 	if (summaryWidth > stringWidth)
1009 		stringWidth = summaryWidth;
1010 	// Version
1011 	float versionWidth = owner->StringWidth(fDetailedVersion.String());
1012 	if (versionWidth > stringWidth)
1013 		stringWidth = versionWidth;
1014 	fMoreDetailsWidth = offsetWidth + stringWidth;
1015 
1016 	// Less details
1017 	offsetWidth = 2 * be_control_look->DefaultItemSpacing()
1018 		+ be_plain_font->Size()
1019 		+ fSuperItem->GetIconSize(false) + fLabelOffset;
1020 	// Name and version
1021 	owner->SetFont(be_plain_font);
1022 	stringWidth = owner->StringWidth(fName.String());
1023 	owner->SetFont(&fSmallFont);
1024 	stringWidth += fLabelOffset + owner->StringWidth(fSimpleVersion.String());
1025 	// Summary
1026 	if (summaryWidth > stringWidth)
1027 		stringWidth = summaryWidth;
1028 	fLessDetailsWidth = offsetWidth + stringWidth;
1029 
1030 	owner->PopState();
1031 }
1032 
1033 
1034 int
1035 PackageItem::NameCompare(PackageItem* item)
1036 {
1037 	// sort by package name
1038 	return fName.ICompare(item->fName);
1039 }
1040 
1041 
1042 void
1043 PackageItem::SetDownloadProgress(float percent)
1044 {
1045 	fDownloadProgress = percent;
1046 }
1047 
1048 
1049 int
1050 SortPackageItems(const BListItem* item1, const BListItem* item2)
1051 {
1052 	PackageItem* first = (PackageItem*)item1;
1053 	PackageItem* second = (PackageItem*)item2;
1054 	return first->NameCompare(second);
1055 }
1056 
1057 
1058 PackageListView::PackageListView()
1059 	:
1060 	BOutlineListView("Package list"),
1061 	fSuperUpdateItem(NULL),
1062 	fSuperInstallItem(NULL),
1063 	fSuperUninstallItem(NULL),
1064 	fShowMoreDetails(false),
1065 	fLastProgressItem(NULL),
1066 	fLastProgressValue(-1)
1067 {
1068 	SetExplicitMinSize(BSize(B_SIZE_UNSET, 40));
1069 	SetExplicitPreferredSize(BSize(B_SIZE_UNSET, 400));
1070 }
1071 
1072 
1073 void
1074 PackageListView::FrameResized(float newWidth, float newHeight)
1075 {
1076 	BOutlineListView::FrameResized(newWidth, newHeight);
1077 	Invalidate();
1078 }
1079 
1080 
1081 void
1082 PackageListView::ExpandOrCollapse(BListItem *superItem, bool expand)
1083 {
1084 	BOutlineListView::ExpandOrCollapse(superItem, expand);
1085 	Window()->PostMessage(kMsgSetZoomLimits);
1086 }
1087 
1088 
1089 void
1090 PackageListView::AddPackage(uint32 install_type, const char* name,
1091 	const char* cur_ver, const char* new_ver, const char* summary,
1092 	const char* repository, const char* file_name)
1093 {
1094 	SuperItem* super;
1095 	BString simpleVersion;
1096 	BString detailedVersion("");
1097 	BString repositoryText(B_TRANSLATE_COMMENT("from repository",
1098 		"List item text"));
1099 	repositoryText.Append(" ").Append(repository);
1100 
1101 	switch (install_type) {
1102 		case PACKAGE_UPDATE:
1103 		{
1104 			if (fSuperUpdateItem == NULL) {
1105 				fSuperUpdateItem = new SuperItem(B_TRANSLATE_COMMENT(
1106 					"Packages to be updated", "List super item label"));
1107 				AddItem(fSuperUpdateItem);
1108 			}
1109 			super = fSuperUpdateItem;
1110 
1111 			simpleVersion.SetTo(new_ver);
1112 			detailedVersion.Append(B_TRANSLATE_COMMENT("Updating version",
1113 					"List item text"))
1114 				.Append(" ").Append(cur_ver)
1115 				.Append(" ").Append(B_TRANSLATE_COMMENT("to",
1116 					"List item text"))
1117 				.Append(" ").Append(new_ver);
1118 			break;
1119 		}
1120 
1121 		case PACKAGE_INSTALL:
1122 		{
1123 			if (fSuperInstallItem == NULL) {
1124 				fSuperInstallItem = new SuperItem(B_TRANSLATE_COMMENT(
1125 					"New packages to be installed", "List super item label"));
1126 				AddItem(fSuperInstallItem);
1127 			}
1128 			super = fSuperInstallItem;
1129 
1130 			simpleVersion.SetTo(new_ver);
1131 			detailedVersion.Append(B_TRANSLATE_COMMENT("Installing version",
1132 					"List item text"))
1133 				.Append(" ").Append(new_ver);
1134 			break;
1135 		}
1136 
1137 		case PACKAGE_UNINSTALL:
1138 		{
1139 			if (fSuperUninstallItem == NULL) {
1140 				fSuperUninstallItem = new SuperItem(B_TRANSLATE_COMMENT(
1141 					"Packages to be uninstalled", "List super item label"));
1142 				AddItem(fSuperUninstallItem);
1143 			}
1144 			super = fSuperUninstallItem;
1145 
1146 			simpleVersion.SetTo("");
1147 			detailedVersion.Append(B_TRANSLATE_COMMENT("Uninstalling version",
1148 					"List item text"))
1149 				.Append(" ").Append(cur_ver);
1150 			break;
1151 		}
1152 
1153 		default:
1154 			return;
1155 
1156 	}
1157 	PackageItem* item = new PackageItem(name, simpleVersion.String(),
1158 		detailedVersion.String(), repositoryText.String(), summary, file_name,
1159 		super);
1160 	AddUnder(item, super);
1161 	super->SetItemCount(CountItemsUnder(super, true));
1162 	item->CalculateZoomWidths(this);
1163 }
1164 
1165 
1166 void
1167 PackageListView::UpdatePackageProgress(const char* packageName, float percent)
1168 {
1169 	// Update only every 1 percent change
1170 	int16 wholePercent = int16(percent);
1171 	if (wholePercent == fLastProgressValue)
1172 		return;
1173 	fLastProgressValue = wholePercent;
1174 
1175 	// A new package started downloading, find the PackageItem by name
1176 	if (percent == 0) {
1177 		fLastProgressItem = NULL;
1178 		int32 count = FullListCountItems();
1179 		for (int32 i = 0; i < count; i++) {
1180 			PackageItem* item = dynamic_cast<PackageItem*>(FullListItemAt(i));
1181 			if (item != NULL && strcmp(item->FileName(), packageName) == 0) {
1182 				fLastProgressItem = item;
1183 				fLastProgressItem->ShowProgressBar();
1184 				break;
1185 			}
1186 		}
1187 	}
1188 
1189 	if (fLastProgressItem != NULL) {
1190 		fLastProgressItem->SetDownloadProgress(percent);
1191 		Invalidate();
1192 	}
1193 }
1194 
1195 
1196 void
1197 PackageListView::SortItems()
1198 {
1199 	if (fSuperUpdateItem != NULL)
1200 		SortItemsUnder(fSuperUpdateItem, true, SortPackageItems);
1201 	if (fSuperInstallItem != NULL)
1202 		SortItemsUnder(fSuperInstallItem, true, SortPackageItems);
1203 	if (fSuperUninstallItem != NULL)
1204 		SortItemsUnder(fSuperUninstallItem, true, SortPackageItems);
1205 }
1206 
1207 
1208 float
1209 PackageListView::ItemHeight()
1210 {
1211 	if (fSuperUpdateItem != NULL)
1212 		return fSuperUpdateItem->GetPackageItemHeight();
1213 	if (fSuperInstallItem != NULL)
1214 		return fSuperInstallItem->GetPackageItemHeight();
1215 	if (fSuperUninstallItem != NULL)
1216 		return fSuperUninstallItem->GetPackageItemHeight();
1217 	return 0;
1218 }
1219 
1220 
1221 void
1222 PackageListView::SetMoreDetails(bool showMore)
1223 {
1224 	if (showMore == fShowMoreDetails)
1225 		return;
1226 	fShowMoreDetails = showMore;
1227 	_SetItemHeights();
1228 	InvalidateLayout();
1229 	ResizeToPreferred();
1230 }
1231 
1232 
1233 BPoint
1234 PackageListView::ZoomPoint()
1235 {
1236 	BPoint zoomPoint(0, 0);
1237 	int32 count = CountItems();
1238 	for (int32 i = 0; i < count; i++)
1239 	{
1240 		BListItem* item = ItemAt(i);
1241 		float itemWidth = 0;
1242 		if (item->OutlineLevel() == 0) {
1243 			SuperItem* sItem = dynamic_cast<SuperItem*>(item);
1244 			itemWidth = sItem->ZoomWidth(this);
1245 		} else {
1246 			PackageItem* pItem = dynamic_cast<PackageItem*>(item);
1247 			itemWidth = fShowMoreDetails ? pItem->MoreDetailsWidth()
1248 				: pItem->LessDetailsWidth();
1249 		}
1250 		if (itemWidth > zoomPoint.x)
1251 			zoomPoint.x = itemWidth;
1252 	}
1253 	if (count > 0)
1254 		zoomPoint.y = ItemFrame(count - 1).bottom;
1255 
1256 	return zoomPoint;
1257 }
1258 
1259 
1260 void
1261 PackageListView::_SetItemHeights()
1262 {
1263 	int32 itemCount = 0;
1264 	float itemHeight = 0;
1265 	BListItem* item = NULL;
1266 	if (fSuperUpdateItem != NULL) {
1267 		fSuperUpdateItem->SetDetailLevel(fShowMoreDetails);
1268 		itemHeight = fSuperUpdateItem->GetPackageItemHeight();
1269 		itemCount = CountItemsUnder(fSuperUpdateItem, true);
1270 		for (int32 i = 0; i < itemCount; i++) {
1271 			item = ItemUnderAt(fSuperUpdateItem, true, i);
1272 			item->SetHeight(itemHeight);
1273 		}
1274 	}
1275 	if (fSuperInstallItem != NULL) {
1276 		fSuperInstallItem->SetDetailLevel(fShowMoreDetails);
1277 		itemHeight = fSuperInstallItem->GetPackageItemHeight();
1278 		itemCount = CountItemsUnder(fSuperInstallItem, true);
1279 		for (int32 i = 0; i < itemCount; i++) {
1280 			item = ItemUnderAt(fSuperInstallItem, true, i);
1281 			item->SetHeight(itemHeight);
1282 		}
1283 
1284 	}
1285 	if (fSuperUninstallItem != NULL) {
1286 		fSuperUninstallItem->SetDetailLevel(fShowMoreDetails);
1287 		itemHeight = fSuperUninstallItem->GetPackageItemHeight();
1288 		itemCount = CountItemsUnder(fSuperUninstallItem, true);
1289 		for (int32 i = 0; i < itemCount; i++) {
1290 			item = ItemUnderAt(fSuperUninstallItem, true, i);
1291 			item->SetHeight(itemHeight);
1292 		}
1293 
1294 	}
1295 }
1296