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