root/src/apps/softwareupdater/SoftwareUpdaterWindow.cpp
/*
 * 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, false, 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);
                }

        }
}