/*
 * Copyright 2016-2019 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT license
 *
 * Authors:
 *		Alexander von Gluck IV <kallisti5@unixzen.com>
 *		Brian Hill <supernova@tycho.email>
 *		Jacob Secunda
 */
 
 
#include "SoftwareUpdaterWindow.h"
 
#include <Alert.h>
#include <AppDefs.h>
#include <Application.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <FindDirectory.h>
#include <LayoutBuilder.h>
#include <LayoutUtils.h>
#include <Message.h>
#include <Roster.h>
#include <RosterPrivate.h>
#include <Screen.h>
#include <String.h>
 
#include "constants.h"
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "SoftwareUpdaterWindow"
 
 
SoftwareUpdaterWindow::SoftwareUpdaterWindow()
	:
	BWindow(BRect(0, 0, 300, 10),
		B_TRANSLATE_SYSTEM_NAME("SoftwareUpdater"), B_TITLED_WINDOW,
		B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_ZOOMABLE | B_NOT_RESIZABLE),
	fStripeView(NULL),
	fHeaderView(NULL),
	fDetailView(NULL),
	fUpdateButton(NULL),
	fCancelButton(NULL),
	fStatusBar(NULL),
	fCurrentState(STATE_HEAD),
	fWaitingSem(-1),
	fWaitingForButton(false),
	fUpdateConfirmed(false),
	fUserCancelRequested(false),
	fWarningAlertCount(0),
	fSettingsReadStatus(B_ERROR),
	fSaveFrameChanges(false),
	fMessageRunner(NULL),
	fFrameChangeMessage(kMsgWindowFrameChanged)
{
	// Layout
	BBitmap icon = GetIcon(32 * icon_layout_scale());
	fStripeView = new BStripeView(icon);
 
	fUpdateButton = new BButton(B_TRANSLATE("Update now"),
		new BMessage(kMsgUpdateConfirmed));
	fUpdateButton->MakeDefault(true);
	fCancelButton = new BButton(B_TRANSLATE("Cancel"),
		new BMessage(kMsgCancel));
	fRebootButton = new BButton(B_TRANSLATE("Reboot"),
		new BMessage(kMsgReboot));
 
	fHeaderView = new BStringView("header",
		B_TRANSLATE("Checking for updates"), B_WILL_DRAW);
	fHeaderView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
	fHeaderView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
	fDetailView = new BStringView("detail", B_TRANSLATE("Contacting software "
		"repositories to check for package updates."), B_WILL_DRAW);
	fDetailView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
	fDetailView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));
	fStatusBar = new BStatusBar("progress");
	fStatusBar->SetMaxValue(100);
 
	fListView = new PackageListView();
	fScrollView = new BScrollView("scrollview", fListView, B_WILL_DRAW,
		false, true);
 
	fDetailsCheckbox = new BCheckBox("detailscheckbox",
		B_TRANSLATE("Show more details"),
		new BMessage(kMsgMoreDetailsToggle));
 
	BFont font;
	fHeaderView->GetFont(&font);
	font.SetFace(B_BOLD_FACE);
	font.SetSize(font.Size() * 1.5);
	fHeaderView->SetFont(&font,
		B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE | B_FONT_FLAGS);
 
	BLayoutBuilder::Group<>(this, B_HORIZONTAL, B_USE_ITEM_SPACING)
		.Add(fStripeView)
		.AddGroup(B_VERTICAL, 0)
			.SetInsets(0, B_USE_WINDOW_SPACING,
				B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING)
			.AddGroup(new BGroupView(B_VERTICAL, B_USE_ITEM_SPACING))
				.Add(fHeaderView)
				.Add(fDetailView)
				.Add(fStatusBar)
				.Add(fScrollView)
			.End()
			.AddStrut(B_USE_SMALL_SPACING)
			.AddGroup(new BGroupView(B_HORIZONTAL))
				.Add(fDetailsCheckbox)
				.AddGlue()
				.Add(fCancelButton)
				.Add(fUpdateButton)
				.Add(fRebootButton)
			.End()
		.End()
	.End();
 
	fDetailsLayoutItem = layout_item_for(fDetailView);
	fProgressLayoutItem = layout_item_for(fStatusBar);
	fPackagesLayoutItem = layout_item_for(fScrollView);
	fCancelButtonLayoutItem = layout_item_for(fCancelButton);
	fUpdateButtonLayoutItem = layout_item_for(fUpdateButton);
	fRebootButtonLayoutItem = layout_item_for(fRebootButton);
	fDetailsCheckboxLayoutItem = layout_item_for(fDetailsCheckbox);
 
	_SetState(STATE_DISPLAY_STATUS);
	CenterOnScreen();
	SetFlags(Flags() ^ B_AUTO_UPDATE_SIZE_LIMITS);
 
	// Prevent resizing for now
	fDefaultRect = Bounds();
	SetSizeLimits(fDefaultRect.Width(), fDefaultRect.Width(),
		fDefaultRect.Height(), fDefaultRect.Height());
 
	// Read settings file
	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &fSettingsPath);
	if (status == B_OK) {
		fSettingsPath.Append(kSettingsFilename);
		fSettingsReadStatus = _ReadSettings(fInitialSettings);
	}
	// Move to saved setting position
	if (fSettingsReadStatus == B_OK) {
		BRect windowFrame;
		status = fInitialSettings.FindRect(kKeyWindowFrame, &windowFrame);
		if (status == B_OK) {
			BScreen screen(this);
			if (screen.Frame().Contains(windowFrame.LeftTop()))
				MoveTo(windowFrame.LeftTop());
		}
	}
	Show();
 
	BMessage registerMessage(kMsgRegister);
	registerMessage.AddMessenger(kKeyMessenger, BMessenger(this));
	be_app->PostMessage(&registerMessage);
 
	fCancelAlertResponse.SetMessage(new BMessage(kMsgCancelResponse));
	fCancelAlertResponse.SetTarget(this);
	fWarningAlertDismissed.SetMessage(new BMessage(kMsgWarningDismissed));
	fWarningAlertDismissed.SetTarget(this);
 
	// Common elements used for the zoom height and width calculations
	fZoomHeightBaseline = 6
		+ be_control_look->ComposeSpacing(B_USE_SMALL_SPACING)
		+ 2 * be_control_look->ComposeSpacing(B_USE_WINDOW_SPACING);
	fZoomWidthBaseline = fStripeView->PreferredSize().Width()
		+ be_control_look->ComposeSpacing(B_USE_ITEM_SPACING)
		+ fScrollView->ScrollBar(B_VERTICAL)->PreferredSize().Width()
		+ be_control_look->ComposeSpacing(B_USE_WINDOW_SPACING);
}
 
 
bool
SoftwareUpdaterWindow::QuitRequested()
{
	PostMessage(kMsgCancel);
	return false;
}
 
 
void
SoftwareUpdaterWindow::FrameMoved(BPoint newPosition)
{
	BWindow::FrameMoved(newPosition);
 
	// Create a message runner to consolidate all function calls from a
	// move into one message post after moving has ceased for .5 seconds
	if (fSaveFrameChanges) {
		if (fMessageRunner == NULL) {
			fMessageRunner = new BMessageRunner(this, &fFrameChangeMessage,
				500000, 1);
		} else
			fMessageRunner->SetInterval(500000);
	}
}
 
 
void
SoftwareUpdaterWindow::FrameResized(float newWidth, float newHeight)
{
	BWindow::FrameResized(newWidth, newHeight);
 
	// Create a message runner to consolidate all function calls from a
	// resize into one message post after resizing has ceased for .5 seconds
	if (fSaveFrameChanges) {
		if (fMessageRunner == NULL) {
			fMessageRunner = new BMessageRunner(this, &fFrameChangeMessage,
				500000, 1);
		} else
			fMessageRunner->SetInterval(500000);
	}
}
 
 
void
SoftwareUpdaterWindow::Zoom(BPoint origin, float width, float height)
{
	// Override default zoom behavior and keep window at same position instead
	// of centering on screen
	BWindow::Zoom(Frame().LeftTop(), width, height);
}
 
 
void
SoftwareUpdaterWindow::MessageReceived(BMessage* message)
{
	switch (message->what) {
 
		case kMsgTextUpdate:
		{
			if (fCurrentState == STATE_DISPLAY_PROGRESS)
				_SetState(STATE_DISPLAY_STATUS);
			else if (fCurrentState != STATE_DISPLAY_STATUS)
				break;
 
			BString header;
			BString detail;
			Lock();
			status_t result = message->FindString(kKeyHeader, &header);
			if (result == B_OK && header != fHeaderView->Text())
				fHeaderView->SetText(header.String());
			result = message->FindString(kKeyDetail, &detail);
			if (result == B_OK)
				fDetailView->SetText(detail.String());
			Unlock();
			break;
		}
 
		case kMsgProgressUpdate:
		{
			if (fCurrentState == STATE_DISPLAY_STATUS)
				_SetState(STATE_DISPLAY_PROGRESS);
			else if (fCurrentState != STATE_DISPLAY_PROGRESS)
				break;
 
			BString packageName;
			status_t result = message->FindString(kKeyPackageName,
				&packageName);
			if (result != B_OK)
				break;
			BString packageCount;
			result = message->FindString(kKeyPackageCount, &packageCount);
			if (result != B_OK)
				break;
			float percent;
			result = message->FindFloat(kKeyPercentage, &percent);
			if (result != B_OK)
				break;
 
			BString header;
			Lock();
			result = message->FindString(kKeyHeader, &header);
			if (result == B_OK && header != fHeaderView->Text())
				fHeaderView->SetText(header.String());
			fStatusBar->SetTo(percent, packageName.String(),
				packageCount.String());
			Unlock();
 
			fListView->UpdatePackageProgress(packageName.String(), percent);
			break;
		}
 
		case kMsgCancel:
		{
			if (_GetState() == STATE_FINAL_MESSAGE) {
				be_app->PostMessage(kMsgFinalQuit);
				break;
			}
			if (!fUpdateConfirmed) {
				// Downloads have not started yet, we will request to cancel
				// without confirming
				Lock();
				fHeaderView->SetText(B_TRANSLATE("Cancelling updates"));
				fDetailView->SetText(
					B_TRANSLATE("Attempting to cancel the updates"
						B_UTF8_ELLIPSIS));
				Unlock();
				fUserCancelRequested = true;
 
				if (fWaitingForButton) {
					fButtonResult = message->what;
					delete_sem(fWaitingSem);
					fWaitingSem = -1;
				}
				break;
			}
 
			// Confirm with the user to cancel
			BAlert* alert = new BAlert("cancel request", B_TRANSLATE("Updates"
				" have not been completed, are you sure you want to quit?"),
				B_TRANSLATE("Quit"), B_TRANSLATE("Don't quit"), NULL,
				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
			alert->Go(&fCancelAlertResponse);
			break;
		}
 
		case kMsgShowReboot:
		{
			fRebootButtonLayoutItem->SetVisible(true);
			fRebootButton->SetLabel(B_TRANSLATE_COMMENT("Reboot",
				"Button label"));
			fRebootButton->MakeDefault(true);
			break;
		}
 
		case kMsgReboot:
		{
			if (_GetState() != STATE_FINAL_MESSAGE)
				break;
 
			BRoster roster;
			BRoster::Private rosterPrivate(roster);
			status_t error = rosterPrivate.ShutDown(true, true, false);
			if (error != B_OK) {
				BAlert* alert = new BAlert("reboot request", B_TRANSLATE(
					"For some reason, we could not reboot your computer."),
					B_TRANSLATE("Ok"), NULL, NULL,
					B_WIDTH_AS_USUAL, B_STOP_ALERT);
				alert->Go();
			}
			break;
		}
 
		case kMsgCancelResponse:
		{
			// Verify whether the cancel alert was confirmed
			int32 selection = -1;
			message->FindInt32("which", &selection);
			if (selection != 0)
				break;
 
			Lock();
			fHeaderView->SetText(B_TRANSLATE("Cancelling updates"));
			fDetailView->SetText(
				B_TRANSLATE("Attempting to cancel the updates"
					B_UTF8_ELLIPSIS));
			Unlock();
			fUserCancelRequested = true;
 
			if (fWaitingForButton) {
				fButtonResult = message->what;
				delete_sem(fWaitingSem);
				fWaitingSem = -1;
			}
			break;
		}
 
		case kMsgUpdateConfirmed:
		{
			if (fWaitingForButton) {
				fButtonResult = message->what;
				delete_sem(fWaitingSem);
				fWaitingSem = -1;
				fUpdateConfirmed = true;
			}
			break;
		}
 
		case kMsgMoreDetailsToggle:
			fListView->SetMoreDetails(fDetailsCheckbox->Value() != 0);
			PostMessage(kMsgSetZoomLimits);
			_WriteSettings();
			break;
 
		case kMsgSetZoomLimits:
		{
			int32 count = fListView->CountItems();
			if (count < 1)
				break;
			// Convert last item's bottom point to its layout group coordinates
			BPoint zoomPoint = fListView->ZoomPoint();
			fScrollView->ConvertToParent(&zoomPoint);
			// Determine which BControl object height to use
			float controlHeight;
			if (fUpdateButtonLayoutItem->IsVisible())
				fUpdateButton->GetPreferredSize(NULL, &controlHeight);
			else
				fDetailsCheckbox->GetPreferredSize(NULL, &controlHeight);
			// Calculate height and width values
			float zoomHeight = fZoomHeightBaseline + zoomPoint.y
				+ controlHeight;
			float zoomWidth = fZoomWidthBaseline + zoomPoint.x;
			SetZoomLimits(zoomWidth, zoomHeight);
			break;
		}
 
		case kMsgWarningDismissed:
			fWarningAlertCount--;
			break;
 
		case kMsgWindowFrameChanged:
			delete fMessageRunner;
			fMessageRunner = NULL;
			_WriteSettings();
			break;
 
		case kMsgGetUpdateType:
		{
			BString text(
				B_TRANSLATE("Please choose from these update options:\n\n"
				"Update:\n"
				"	Updates all installed packages.\n"
				"Full sync:\n"
				"	Synchronizes the installed packages with the repositories."
				));
			BAlert* alert = new BAlert("update_type",
				text,
				B_TRANSLATE_COMMENT("Cancel", "Alert button label"),
				B_TRANSLATE_COMMENT("Full sync","Alert button label"),
				B_TRANSLATE_COMMENT("Update","Alert button label"),
				B_WIDTH_AS_USUAL, B_INFO_ALERT);
			int32 result = alert->Go();
			int32 action = INVALID_SELECTION;
			switch(result) {
				case 0:
					action = CANCEL_UPDATE;
					break;
 
				case 1:
					action = FULLSYNC;
					break;
 
				case 2:
					action = UPDATE;
					break;
			}
			BMessage reply;
			reply.AddInt32(kKeyAlertResult, action);
			message->SendReply(&reply);
			break;
		}
 
		case kMsgNoRepositories:
		{
			BString text(
				B_TRANSLATE_COMMENT(
				"No remote repositories are available. Please verify that some"
				" repositories are enabled using the Repositories preflet or"
				" the \'pkgman\' command.", "Error message"));
			BAlert* alert = new BAlert("repositories", text,
				B_TRANSLATE_COMMENT("Quit", "Alert button label"),
				B_TRANSLATE_COMMENT("Open Repositories","Alert button label"),
				NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
			int32 result = alert->Go();
			BMessage reply;
			reply.AddInt32(kKeyAlertResult, result);
			message->SendReply(&reply);
			break;
		}
 
		default:
			BWindow::MessageReceived(message);
	}
}
 
 
bool
SoftwareUpdaterWindow::ConfirmUpdates()
{
	Lock();
	fHeaderView->SetText(B_TRANSLATE("Updates found"));
	fDetailView->SetText(B_TRANSLATE("The following changes will be made:"));
	fListView->SortItems();
	Unlock();
 
	uint32 priorState = _GetState();
	_SetState(STATE_GET_CONFIRMATION);
 
	_WaitForButtonClick();
	_SetState(priorState);
	return fButtonResult == kMsgUpdateConfirmed;
}
 
 
void
SoftwareUpdaterWindow::UpdatesApplying(const char* header, const char* detail)
{
	Lock();
	fHeaderView->SetText(header);
	fDetailView->SetText(detail);
	Unlock();
	_SetState(STATE_APPLY_UPDATES);
}
 
 
bool
SoftwareUpdaterWindow::UserCancelRequested()
{
	if (_GetState() > STATE_GET_CONFIRMATION)
		return false;
 
	return fUserCancelRequested;
}
 
 
void
SoftwareUpdaterWindow::AddPackageInfo(uint32 install_type,
	const char* package_name, const char* cur_ver, const char* new_ver,
	const char* summary, const char* repository, const char* file_name)
{
	Lock();
	fListView->AddPackage(install_type, package_name, cur_ver, new_ver,
		summary, repository, file_name);
	Unlock();
}
 
 
void
SoftwareUpdaterWindow::ShowWarningAlert(const char* text)
{
	BAlert* alert = new BAlert("warning", text, B_TRANSLATE("OK"), NULL, NULL,
		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
	alert->Go(&fWarningAlertDismissed);
	alert->CenterIn(Frame());
	// Offset multiple alerts
	alert->MoveBy(fWarningAlertCount * 15, fWarningAlertCount * 15);
	fWarningAlertCount++;
}
 
 
BBitmap
SoftwareUpdaterWindow::GetIcon(int32 iconSize)
{
	BBitmap icon(BRect(0, 0, iconSize - 1, iconSize - 1), 0, B_RGBA32);
	team_info teamInfo;
	get_team_info(B_CURRENT_TEAM, &teamInfo);
	app_info appInfo;
	be_roster->GetRunningAppInfo(teamInfo.team, &appInfo);
	BNodeInfo::GetTrackerIcon(&appInfo.ref, &icon, icon_size(iconSize));
	return icon;
}
 
 
void
SoftwareUpdaterWindow::FinalUpdate(const char* header, const char* detail)
{
	if (_GetState() == STATE_FINAL_MESSAGE)
		return;
 
	_SetState(STATE_FINAL_MESSAGE);
	Lock();
	fHeaderView->SetText(header);
	fDetailView->SetText(detail);
	Unlock();
}
 
 
BLayoutItem*
SoftwareUpdaterWindow::layout_item_for(BView* view)
{
	BLayout* layout = view->Parent()->GetLayout();
	int32 index = layout->IndexOfView(view);
	return layout->ItemAt(index);
}
 
 
uint32
SoftwareUpdaterWindow::_WaitForButtonClick()
{
	fButtonResult = 0;
	fWaitingForButton = true;
	fWaitingSem = create_sem(0, "WaitingSem");
	while (acquire_sem(fWaitingSem) == B_INTERRUPTED) {
	}
	fWaitingForButton = false;
	return fButtonResult;
}
 
 
void
SoftwareUpdaterWindow::_SetState(uint32 state)
{
	if (state <= STATE_HEAD || state >= STATE_MAX)
		return;
 
	Lock();
 
	// Initial settings
	if (fCurrentState == STATE_HEAD) {
		fProgressLayoutItem->SetVisible(false);
		fPackagesLayoutItem->SetVisible(false);
		fDetailsCheckboxLayoutItem->SetVisible(false);
		fCancelButtonLayoutItem->SetVisible(false);
		fRebootButtonLayoutItem->SetVisible(false);
	}
	fCurrentState = state;
 
	// Update confirmation button
	// Show only when asking for confirmation to update
	if (fCurrentState == STATE_GET_CONFIRMATION)
		fUpdateButtonLayoutItem->SetVisible(true);
	else
		fUpdateButtonLayoutItem->SetVisible(false);
 
	// View package info view and checkbox
	// Show at confirmation prompt, hide at final update
	if (fCurrentState == STATE_GET_CONFIRMATION) {
		fPackagesLayoutItem->SetVisible(true);
		fDetailsCheckboxLayoutItem->SetVisible(true);
		if (fSettingsReadStatus == B_OK) {
			bool showMoreDetails;
			status_t result = fInitialSettings.FindBool(kKeyShowDetails,
				&showMoreDetails);
			if (result == B_OK) {
				fDetailsCheckbox->SetValue(showMoreDetails ? 1 : 0);
				fListView->SetMoreDetails(showMoreDetails);
			}
		}
	} else if (fCurrentState == STATE_FINAL_MESSAGE) {
		fPackagesLayoutItem->SetVisible(false);
		fDetailsCheckboxLayoutItem->SetVisible(false);
	}
 
	// Progress bar and string view
	// Hide detail text while showing status bar
	if (fCurrentState == STATE_DISPLAY_PROGRESS) {
		fDetailsLayoutItem->SetVisible(false);
		fProgressLayoutItem->SetVisible(true);
	} else {
		fProgressLayoutItem->SetVisible(false);
		fDetailsLayoutItem->SetVisible(true);
	}
 
	// Resizing and zooming
	if (fCurrentState == STATE_GET_CONFIRMATION) {
		// Enable resizing and zooming
		float defaultWidth = fDefaultRect.Width();
		SetSizeLimits(defaultWidth, B_SIZE_UNLIMITED,
			fDefaultRect.Height() + 4 * fListView->ItemHeight(),
			B_SIZE_UNLIMITED);
		SetFlags(Flags() ^ (B_NOT_RESIZABLE | B_NOT_ZOOMABLE));
		PostMessage(kMsgSetZoomLimits);
		// Recall saved settings
		BScreen screen(this);
		BRect screenFrame = screen.Frame();
		bool windowResized = false;
		if (fSettingsReadStatus == B_OK) {
			BRect windowFrame;
			status_t result = fInitialSettings.FindRect(kKeyWindowFrame,
				&windowFrame);
			if (result == B_OK) {
				if (screenFrame.Contains(windowFrame)) {
					ResizeTo(windowFrame.Width(), windowFrame.Height());
					windowResized = true;
				}
			}
		}
		if (!windowResized)
			ResizeTo(defaultWidth, .75 * defaultWidth);
		// Check that the bottom of window is on screen
		float screenBottom = screenFrame.bottom;
		float windowBottom = DecoratorFrame().bottom;
		if (windowBottom > screenBottom)
			MoveBy(0, screenBottom - windowBottom);
		fSaveFrameChanges = true;
	} else if (fUpdateConfirmed && (fCurrentState == STATE_DISPLAY_PROGRESS
			|| fCurrentState == STATE_DISPLAY_STATUS)) {
		PostMessage(kMsgSetZoomLimits);
	} else if (fCurrentState == STATE_APPLY_UPDATES)
		fSaveFrameChanges = false;
	else if (fCurrentState == STATE_FINAL_MESSAGE) {
		// Disable resizing and zooming
		fSaveFrameChanges = false;
		ResizeTo(fDefaultRect.Width(), fDefaultRect.Height());
		SetFlags(Flags() | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_RESIZABLE
			| B_NOT_ZOOMABLE);
	}
 
	// Quit button
	if (fCurrentState == STATE_FINAL_MESSAGE) {
		fCancelButtonLayoutItem->SetVisible(true);
 		fCancelButton->SetLabel(B_TRANSLATE_COMMENT("Quit", "Button label"));
		fCancelButton->MakeDefault(true);
	}
 
	Unlock();
}
 
 
uint32
SoftwareUpdaterWindow::_GetState()
{
	return fCurrentState;
}
 
 
status_t
SoftwareUpdaterWindow::_WriteSettings()
{
	BFile file;
	status_t status = file.SetTo(fSettingsPath.Path(),
		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
	if (status == B_OK) {
		BMessage settings;
		settings.AddBool(kKeyShowDetails, fDetailsCheckbox->Value() != 0);
		settings.AddRect(kKeyWindowFrame, Frame());
		status = settings.Flatten(&file);
	}
	file.Unset();
	return status;
}
 
 
status_t
SoftwareUpdaterWindow::_ReadSettings(BMessage& settings)
{
	BFile file;
	status_t status = file.SetTo(fSettingsPath.Path(), B_READ_ONLY);
	if (status == B_OK)
		status = settings.Unflatten(&file);
	file.Unset();
	return status;
}
 
 
SuperItem::SuperItem(const char* label)
	:
	BListItem(),
	fLabel(label),
	fRegularFont(be_plain_font),
	fBoldFont(be_plain_font),
	fShowMoreDetails(false),
	fPackageLessIcon(NULL),
	fPackageMoreIcon(NULL),
	fItemCount(0)
{
	fBoldFont.SetFace(B_BOLD_FACE);
	fBoldFont.GetHeight(&fBoldFontHeight);
	font_height fontHeight;
	fRegularFont.GetHeight(&fontHeight);
	fPackageItemLineHeight = fontHeight.ascent + fontHeight.descent
		+ fontHeight.leading;
	fPackageLessIcon = _GetPackageIcon(GetPackageItemHeight(false));
	fPackageMoreIcon = _GetPackageIcon(GetPackageItemHeight(true));
}
 
 
SuperItem::~SuperItem()
{
	delete fPackageLessIcon;
	delete fPackageMoreIcon;
}
 
 
void
SuperItem::DrawItem(BView* owner, BRect item_rect, bool complete)
{
	owner->PushState();
 
	float width;
	owner->GetPreferredSize(&width, NULL);
	BString text(fItemText);
	owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
	owner->SetFont(&fBoldFont);
	owner->TruncateString(&text, B_TRUNCATE_END, width);
	owner->DrawString(text.String(), BPoint(item_rect.left,
		item_rect.bottom - fBoldFontHeight.descent));
 
	owner->PopState();
}
 
 
float
SuperItem::GetPackageItemHeight()
{
	return GetPackageItemHeight(fShowMoreDetails);
}
 
 
float
SuperItem::GetPackageItemHeight(bool showMoreDetails)
{
	int lineCount = showMoreDetails ? 3 : 2;
	return lineCount * fPackageItemLineHeight;
}
 
 
BBitmap*
SuperItem::GetIcon(bool showMoreDetails)
{
	if (showMoreDetails)
		return fPackageMoreIcon;
	else
		return fPackageLessIcon;
}
 
 
float
SuperItem::GetIconSize(bool showMoreDetails)
{
	if (showMoreDetails)
		return fPackageMoreIcon->Bounds().Height();
	else
		return fPackageLessIcon->Bounds().Height();
}
 
 
void
SuperItem::SetDetailLevel(bool showMoreDetails)
{
	fShowMoreDetails = showMoreDetails;
}
 
 
void
SuperItem::SetItemCount(int32 count)
{
	fItemCount = count;
	fItemText = fLabel;
	fItemText.Append(" (");
	fItemText << fItemCount;
	fItemText.Append(")");
}
 
 
float
SuperItem::ZoomWidth(BView *owner)
{
	owner->PushState();
	owner->SetFont(&fBoldFont);
	float width = owner->StringWidth(fItemText.String());
	owner->PopState();
	return width;
}
 
 
BBitmap*
SuperItem::_GetPackageIcon(float listItemHeight)
{
	int32 iconSize = int(listItemHeight * .8);
	status_t result = B_ERROR;
	BRect iconRect(0, 0, iconSize - 1, iconSize - 1);
	BBitmap* packageIcon = new BBitmap(iconRect, 0, B_RGBA32);
	BMimeType nodeType;
	nodeType.SetTo("application/x-vnd.haiku-package");
	result = nodeType.GetIcon(packageIcon, icon_size(iconSize));
	// Get super type icon
	if (result != B_OK) {
		BMimeType superType;
		if (nodeType.GetSupertype(&superType) == B_OK)
			result = superType.GetIcon(packageIcon, icon_size(iconSize));
	}
	if (result != B_OK) {
		delete packageIcon;
		return NULL;
	}
	return packageIcon;
}
 
 
PackageItem::PackageItem(const char* name, const char* simple_version,
	const char* detailed_version, const char* repository, const char* summary,
	const char* file_name, SuperItem* super)
	:
	BListItem(),
	fName(name),
	fSimpleVersion(simple_version),
	fDetailedVersion(detailed_version),
	fRepository(repository),
	fSummary(summary),
	fSmallFont(be_plain_font),
	fSuperItem(super),
	fFileName(file_name),
	fDownloadProgress(0),
	fDrawBarFlag(false),
	fMoreDetailsWidth(0),
	fLessDetailsWidth(0)
{
	fLabelOffset = be_control_look->DefaultLabelSpacing();
	fSmallFont.SetSize(be_plain_font->Size() - 2);
	fSmallFont.GetHeight(&fSmallFontHeight);
	fSmallTotalHeight = fSmallFontHeight.ascent + fSmallFontHeight.descent
		+ fSmallFontHeight.leading;
}
 
 
void
PackageItem::DrawItem(BView* owner, BRect item_rect, bool complete)
{
	owner->PushState();
 
	float width = owner->Frame().Width();
	float nameWidth = width / 2.0;
	float offsetWidth = 0;
	bool showMoreDetails = fSuperItem->GetDetailLevel();
 
	BBitmap* icon = fSuperItem->GetIcon(showMoreDetails);
	if (icon != NULL && icon->IsValid()) {
		float iconSize = icon->Bounds().Height();
		float offsetMarginHeight = floor((Height() - iconSize) / 2);
		owner->SetDrawingMode(B_OP_ALPHA);
		BPoint location = BPoint(item_rect.left,
			item_rect.top + offsetMarginHeight);
		owner->DrawBitmap(icon, location);
		owner->SetDrawingMode(B_OP_COPY);
		offsetWidth = iconSize + fLabelOffset;
 
		if (fDrawBarFlag)
			_DrawBar(location, owner, icon_size(iconSize));
	}
 
	owner->SetFont(be_plain_font);
	owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
 
	// Package name
	BString name(fName);
	owner->TruncateString(&name, B_TRUNCATE_END, nameWidth);
	BPoint cursor(item_rect.left + offsetWidth,
		item_rect.bottom - fSmallTotalHeight - fSmallFontHeight.descent - 2);
	if (showMoreDetails)
		cursor.y -= fSmallTotalHeight + 1;
	owner->DrawString(name.String(), cursor);
	cursor.x += owner->StringWidth(name.String()) + fLabelOffset;
 
	// Change font and color
	owner->SetFont(&fSmallFont);
	owner->SetHighColor(tint_color(ui_color(B_LIST_ITEM_TEXT_COLOR), 0.7));
 
	// Simple version or repository
	BString versionOrRepo;
	if (showMoreDetails)
		versionOrRepo.SetTo(fRepository);
	else
		versionOrRepo.SetTo(fSimpleVersion);
	owner->TruncateString(&versionOrRepo, B_TRUNCATE_END, width - cursor.x);
	owner->DrawString(versionOrRepo.String(), cursor);
 
	// Summary
	BString summary(fSummary);
	cursor.x = item_rect.left + offsetWidth;
	cursor.y += fSmallTotalHeight;
	owner->TruncateString(&summary, B_TRUNCATE_END, width - cursor.x);
	owner->DrawString(summary.String(), cursor);
 
	// Detailed version
	if (showMoreDetails) {
		BString version(fDetailedVersion);
		cursor.y += fSmallTotalHeight;
		owner->TruncateString(&version, B_TRUNCATE_END, width - cursor.x);
		owner->DrawString(version.String(), cursor);
	}
 
	owner->PopState();
}
 
 
// Modified slightly from Tracker's BPose::DrawBar
void
PackageItem::_DrawBar(BPoint where, BView* view, icon_size which)
{
	int32 yOffset;
	int32 size = which - 1;
	int32 barWidth = (int32)(7.0f / 32.0f * (float)which);
	if (barWidth < 4) {
		barWidth = 4;
		yOffset = 0;
	} else
		yOffset = 2;
	int32 barHeight = size - 3 - 2 * yOffset;
 
 
	// the black shadowed line
	view->SetHighColor(32, 32, 32, 92);
	view->MovePenTo(BPoint(where.x + size, where.y + 1 + yOffset));
	view->StrokeLine(BPoint(where.x + size, where.y + size - yOffset));
	view->StrokeLine(BPoint(where.x + size - barWidth + 1,
		where.y + size - yOffset));
 
	view->SetDrawingMode(B_OP_ALPHA);
 
	// the gray frame
	view->SetHighColor(76, 76, 76, 192);
	BRect rect(where.x + size - barWidth,where.y + yOffset,
		where.x + size - 1,where.y + size - 1 - yOffset);
	view->StrokeRect(rect);
 
	// calculate bar height
	int32 barPos = barHeight - int32(barHeight * fDownloadProgress / 100.0);
	if (barPos < 0)
		barPos = 0;
	else if (barPos > barHeight)
		barPos = barHeight;
 
	// the free space bar
	view->SetHighColor(255, 255, 255, 192);
 
	rect.InsetBy(1,1);
	BRect bar(rect);
	bar.bottom = bar.top + barPos - 1;
	if (barPos > 0)
		view->FillRect(bar);
 
	// the used space bar
	bar.top = bar.bottom + 1;
	bar.bottom = rect.bottom;
	view->SetHighColor(0, 203, 0, 192);
	view->FillRect(bar);
}
 
 
void
PackageItem::Update(BView *owner, const BFont *font)
{
	BListItem::Update(owner, font);
	SetHeight(fSuperItem->GetPackageItemHeight());
}
 
 
void
PackageItem::CalculateZoomWidths(BView *owner)
{
	owner->PushState();
 
	// More details
	float offsetWidth = 2 * be_control_look->DefaultItemSpacing()
		+ be_plain_font->Size()
		+ fSuperItem->GetIconSize(true) + fLabelOffset;
	// Name and repo
	owner->SetFont(be_plain_font);
	float stringWidth = owner->StringWidth(fName.String());
	owner->SetFont(&fSmallFont);
	stringWidth += fLabelOffset + owner->StringWidth(fRepository.String());
	// Summary
	float summaryWidth = owner->StringWidth(fSummary.String());
	if (summaryWidth > stringWidth)
		stringWidth = summaryWidth;
	// Version
	float versionWidth = owner->StringWidth(fDetailedVersion.String());
	if (versionWidth > stringWidth)
		stringWidth = versionWidth;
	fMoreDetailsWidth = offsetWidth + stringWidth;
 
	// Less details
	offsetWidth = 2 * be_control_look->DefaultItemSpacing()
		+ be_plain_font->Size()
		+ fSuperItem->GetIconSize(false) + fLabelOffset;
	// Name and version
	owner->SetFont(be_plain_font);
	stringWidth = owner->StringWidth(fName.String());
	owner->SetFont(&fSmallFont);
	stringWidth += fLabelOffset + owner->StringWidth(fSimpleVersion.String());
	// Summary
	if (summaryWidth > stringWidth)
		stringWidth = summaryWidth;
	fLessDetailsWidth = offsetWidth + stringWidth;
 
	owner->PopState();
}
 
 
int
PackageItem::NameCompare(PackageItem* item)
{
	// sort by package name
	return fName.ICompare(item->fName);
}
 
 
void
PackageItem::SetDownloadProgress(float percent)
{
	fDownloadProgress = percent;
}
 
 
int
SortPackageItems(const BListItem* item1, const BListItem* item2)
{
	PackageItem* first = (PackageItem*)item1;
	PackageItem* second = (PackageItem*)item2;
	return first->NameCompare(second);
}
 
 
PackageListView::PackageListView()
	:
	BOutlineListView("Package list"),
	fSuperUpdateItem(NULL),
	fSuperInstallItem(NULL),
	fSuperUninstallItem(NULL),
	fShowMoreDetails(false),
	fLastProgressItem(NULL),
	fLastProgressValue(-1)
{
	SetExplicitMinSize(BSize(B_SIZE_UNSET, 40));
	SetExplicitPreferredSize(BSize(B_SIZE_UNSET, 400));
}
 
 
void
PackageListView::FrameResized(float newWidth, float newHeight)
{
	BOutlineListView::FrameResized(newWidth, newHeight);
	Invalidate();
}
 
 
void
PackageListView::ExpandOrCollapse(BListItem *superItem, bool expand)
{
	BOutlineListView::ExpandOrCollapse(superItem, expand);
	Window()->PostMessage(kMsgSetZoomLimits);
}
 
 
void
PackageListView::AddPackage(uint32 install_type, const char* name,
	const char* cur_ver, const char* new_ver, const char* summary,
	const char* repository, const char* file_name)
{
	SuperItem* super;
	BString simpleVersion;
	BString detailedVersion("");
	BString repositoryText(B_TRANSLATE_COMMENT("from repository",
		"List item text"));
	repositoryText.Append(" ").Append(repository);
 
	switch (install_type) {
		case PACKAGE_UPDATE:
		{
			if (fSuperUpdateItem == NULL) {
				fSuperUpdateItem = new SuperItem(B_TRANSLATE_COMMENT(
					"Packages to be updated", "List super item label"));
				AddItem(fSuperUpdateItem);
			}
			super = fSuperUpdateItem;
 
			simpleVersion.SetTo(new_ver);
			detailedVersion.Append(B_TRANSLATE_COMMENT("Updating version",
					"List item text"))
				.Append(" ").Append(cur_ver)
				.Append(" ").Append(B_TRANSLATE_COMMENT("to",
					"List item text"))
				.Append(" ").Append(new_ver);
			break;
		}
 
		case PACKAGE_INSTALL:
		{
			if (fSuperInstallItem == NULL) {
				fSuperInstallItem = new SuperItem(B_TRANSLATE_COMMENT(
					"New packages to be installed", "List super item label"));
				AddItem(fSuperInstallItem);
			}
			super = fSuperInstallItem;
 
			simpleVersion.SetTo(new_ver);
			detailedVersion.Append(B_TRANSLATE_COMMENT("Installing version",
					"List item text"))
				.Append(" ").Append(new_ver);
			break;
		}
 
		case PACKAGE_UNINSTALL:
		{
			if (fSuperUninstallItem == NULL) {
				fSuperUninstallItem = new SuperItem(B_TRANSLATE_COMMENT(
					"Packages to be uninstalled", "List super item label"));
				AddItem(fSuperUninstallItem);
			}
			super = fSuperUninstallItem;
 
			simpleVersion.SetTo("");
			detailedVersion.Append(B_TRANSLATE_COMMENT("Uninstalling version",
					"List item text"))
				.Append(" ").Append(cur_ver);
			break;
		}
 
		default:
			return;
 
	}
	PackageItem* item = new PackageItem(name, simpleVersion.String(),
		detailedVersion.String(), repositoryText.String(), summary, file_name,
		super);
	AddUnder(item, super);
	super->SetItemCount(CountItemsUnder(super, true));
	item->CalculateZoomWidths(this);
}
 
 
void
PackageListView::UpdatePackageProgress(const char* packageName, float percent)
{
	// Update only every 1 percent change
	int16 wholePercent = int16(percent);
	if (wholePercent == fLastProgressValue)
		return;
	fLastProgressValue = wholePercent;
 
	// A new package started downloading, find the PackageItem by name
	if (percent == 0) {
		fLastProgressItem = NULL;
		int32 count = FullListCountItems();
		for (int32 i = 0; i < count; i++) {
			PackageItem* item = dynamic_cast<PackageItem*>(FullListItemAt(i));
			if (item != NULL && strcmp(item->FileName(), packageName) == 0) {
				fLastProgressItem = item;
				fLastProgressItem->ShowProgressBar();
				break;
			}
		}
	}
 
	if (fLastProgressItem != NULL) {
		fLastProgressItem->SetDownloadProgress(percent);
		Invalidate();
	}
}
 
 
void
PackageListView::SortItems()
{
	if (fSuperUpdateItem != NULL)
		SortItemsUnder(fSuperUpdateItem, true, SortPackageItems);
	if (fSuperInstallItem != NULL)
		SortItemsUnder(fSuperInstallItem, true, SortPackageItems);
	if (fSuperUninstallItem != NULL)
		SortItemsUnder(fSuperUninstallItem, true, SortPackageItems);
}
 
 
float
PackageListView::ItemHeight()
{
	if (fSuperUpdateItem != NULL)
		return fSuperUpdateItem->GetPackageItemHeight();
	if (fSuperInstallItem != NULL)
		return fSuperInstallItem->GetPackageItemHeight();
	if (fSuperUninstallItem != NULL)
		return fSuperUninstallItem->GetPackageItemHeight();
	return 0;
}
 
 
void
PackageListView::SetMoreDetails(bool showMore)
{
	if (showMore == fShowMoreDetails)
		return;
	fShowMoreDetails = showMore;
	_SetItemHeights();
	InvalidateLayout();
	ResizeToPreferred();
}
 
 
BPoint
PackageListView::ZoomPoint()
{
	BPoint zoomPoint(0, 0);
	int32 count = CountItems();
	for (int32 i = 0; i < count; i++)
	{
		BListItem* item = ItemAt(i);
		float itemWidth = 0;
		if (item->OutlineLevel() == 0) {
			SuperItem* sItem = dynamic_cast<SuperItem*>(item);
			itemWidth = sItem->ZoomWidth(this);
		} else {
			PackageItem* pItem = dynamic_cast<PackageItem*>(item);
			itemWidth = fShowMoreDetails ? pItem->MoreDetailsWidth()
				: pItem->LessDetailsWidth();
		}
		if (itemWidth > zoomPoint.x)
			zoomPoint.x = itemWidth;
	}
	if (count > 0)
		zoomPoint.y = ItemFrame(count - 1).bottom;
 
	return zoomPoint;
}
 
 
void
PackageListView::_SetItemHeights()
{
	int32 itemCount = 0;
	float itemHeight = 0;
	BListItem* item = NULL;
	if (fSuperUpdateItem != NULL) {
		fSuperUpdateItem->SetDetailLevel(fShowMoreDetails);
		itemHeight = fSuperUpdateItem->GetPackageItemHeight();
		itemCount = CountItemsUnder(fSuperUpdateItem, true);
		for (int32 i = 0; i < itemCount; i++) {
			item = ItemUnderAt(fSuperUpdateItem, true, i);
			item->SetHeight(itemHeight);
		}
	}
	if (fSuperInstallItem != NULL) {
		fSuperInstallItem->SetDetailLevel(fShowMoreDetails);
		itemHeight = fSuperInstallItem->GetPackageItemHeight();
		itemCount = CountItemsUnder(fSuperInstallItem, true);
		for (int32 i = 0; i < itemCount; i++) {
			item = ItemUnderAt(fSuperInstallItem, true, i);
			item->SetHeight(itemHeight);
		}
 
	}
	if (fSuperUninstallItem != NULL) {
		fSuperUninstallItem->SetDetailLevel(fShowMoreDetails);
		itemHeight = fSuperUninstallItem->GetPackageItemHeight();
		itemCount = CountItemsUnder(fSuperUninstallItem, true);
		for (int32 i = 0; i < itemCount; i++) {
			item = ItemUnderAt(fSuperUninstallItem, true, i);
			item->SetHeight(itemHeight);
		}
 
	}
}

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.