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