root/src/apps/haikudepot/ui/MainWindow.cpp
/*
 * Copyright 2015, Axel Dörfler, <axeld@pinc-software.de>.
 * Copyright 2013-2014, Stephan Aßmus <superstippi@gmx.de>.
 * Copyright 2013, Rene Gollent, rene@gollent.com.
 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2016-2026, Andrew Lindesay <apl@lindesay.co.nz>.
 * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
 * All rights reserved. Distributed under the terms of the MIT License.
 */
#include "MainWindow.h"

#include <algorithm>
#include <map>
#include <vector>

#include <stdio.h>

#include <Alert.h>
#include <Application.h>
#include <Autolock.h>
#include <Button.h>
#include <CardLayout.h>
#include <Catalog.h>
#include <LayoutBuilder.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <MessageRunner.h>
#include <Messenger.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollView.h>
#include <StopWatch.h>
#include <StringList.h>
#include <StringView.h>
#include <TabView.h>

#include "AppUtils.h"
#include "AutoDeleter.h"
#include "AutoLocker.h"
#include "DecisionProvider.h"
#include "FeaturedPackagesView.h"
#include "FilterView.h"
#include "IdentityAndAccessUtils.h"
#include "LocaleUtils.h"
#include "Logger.h"
#include "PackageInfoView.h"
#include "PackageListView.h"
#include "PackageManager.h"
#include "PackageUtils.h"
#include "ProcessCoordinator.h"
#include "ProcessCoordinatorFactory.h"
#include "RatePackageWindow.h"
#include "RatingUtils.h"
#include "ScreenshotWindow.h"
#include "ServerHelper.h"
#include "SettingsWindow.h"
#include "ShuttingDownWindow.h"
#include "ToLatestUserUsageConditionsWindow.h"
#include "UserLoginWindow.h"
#include "UserUsageConditionsWindow.h"
#include "WorkStatusView.h"
#include "support.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "MainWindow"


enum {
        MSG_REFRESH_REPOS                                                       = 'mrrp',
        MSG_MANAGE_REPOS                                                        = 'mmrp',
        MSG_SOFTWARE_UPDATER                                            = 'mswu',
        MSG_SETTINGS                                                            = 'stgs',
        MSG_LOG_IN                                                                      = 'lgin',
        MSG_AUTHORIZATION_CHANGED                                       = 'athc',
        MSG_PACKAGE_FILTER_CHANGED                                      = 'fpch',
        MSG_ICONS_CHANGED                                                       = 'icoc',
        MSG_CATEGORIES_LIST_CHANGED                                     = 'clic',
        MSG_PACKAGES_CHANGED                                            = 'pchd',
        MSG_PROCESS_COORDINATOR_CHANGED                         = 'pccd',
        MSG_WORK_STATUS_CHANGE                                          = 'wsch',
        MSG_WORK_STATUS_CLEAR                                           = 'wscl',
        MSG_INCREMENT_VIEW_COUNTER                                      = 'icrv',
        MSG_SCREENSHOT_CACHED                                           = 'ssca',

        MSG_CHANGE_PACKAGE_LIST_VIEW_MODE                       = 'cplm',
        MSG_SHOW_DESKTOP_PACKAGES                                       = 'sodk',
        MSG_SHOW_NATIVE_DESKTOP_PACKAGES                        = 'sond',
        MSG_SHOW_DESKTOP_AND_NON_DESKTOP_PACKAGES       = 'sdan',
        MSG_SHOW_AVAILABLE_PACKAGES                                     = 'savl',
        MSG_SHOW_INSTALLED_PACKAGES                                     = 'sins',
        MSG_SHOW_SOURCE_PACKAGES                                        = 'ssrc',
        MSG_SHOW_DEVELOP_PACKAGES                                       = 'sdvl'
};


const char* const main_window_keys::kKeyWindowSettings = "window_settings";

static const char* const kKeyWorkStatusText = "work_status_text";
static const char* const kKeyWorkStatusProgress = "work_status_progress";
static const char* const kKeyWindowFrame = "window frame";
        // use of spaces is historical
static const char* const kKeySinglePackageWindowFrame = "small window frame";
        // use of spaces is historical
static const char* const kKeyColumnSettings = "column settings";
        // use of spaces is historical


#define KEY_ERROR_STATUS                                "errorStatus"

const bigtime_t kIncrementViewCounterDelayMicros = 3 * 1000 * 1000;

#define TAB_PROMINENT_PACKAGES  0
#define TAB_ALL_PACKAGES                1

using namespace BPackageKit;
using namespace BPackageKit::BManager::BPrivate;


typedef std::map<BString, PackageInfoRef> PackageInfoMap;


struct RefreshWorkerParameters {
        MainWindow* window;
        bool forceRefresh;

        RefreshWorkerParameters(MainWindow* window, bool forceRefresh)
                :
                window(window),
                forceRefresh(forceRefresh)
        {
        }
};


class MainWindowModelListener : public ModelListener {
public:
        MainWindowModelListener(const BMessenger& messenger)
                :
                fMessenger(messenger)
        {
        }

        virtual void PackageFilterChanged()
        {
                if (fMessenger.IsValid())
                        fMessenger.SendMessage(MSG_PACKAGE_FILTER_CHANGED);
        }

        virtual void AuthorizationChanged()
        {
                if (fMessenger.IsValid())
                        fMessenger.SendMessage(MSG_AUTHORIZATION_CHANGED);
        }

        virtual void CategoryListChanged()
        {
                if (fMessenger.IsValid())
                        fMessenger.SendMessage(MSG_CATEGORIES_LIST_CHANGED);
        }

        virtual void ScreenshotCached(const ScreenshotCoordinate& coordinate)
        {
                if (fMessenger.IsValid()) {
                        BMessage message(MSG_SCREENSHOT_CACHED);
                        if (coordinate.Archive(&message) != B_OK)
                                debugger("unable to serialize a screenshot coordinate");
                        fMessenger.SendMessage(&message);
                }
        }

        virtual void IconsChanged()
        {
                if (fMessenger.IsValid())
                        fMessenger.SendMessage(MSG_ICONS_CHANGED);
        }

private:
        BMessenger fMessenger;
};


class MainWindowPackageInfoListener : public PackageInfoListener {
public:
        MainWindowPackageInfoListener(MainWindow* mainWindow)
                :
                fMainWindow(mainWindow)
        {
        }

        ~MainWindowPackageInfoListener() {}

private:
                // PackageInfoListener
        virtual void PackagesChanged(const PackageChangeEvents& events)
        {
                fMainWindow->PackagesChanged(events);
        }

private:
        MainWindow* fMainWindow;
};


MainWindow::MainWindow(const BMessage& settings)
        :
        BWindow(BRect(50, 50, 650, 550), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), B_DOCUMENT_WINDOW_LOOK,
                B_NORMAL_WINDOW_FEEL, B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
        fScreenshotWindow(NULL),
        fShuttingDownWindow(NULL),
        fUserMenu(NULL),
        fLogInItem(NULL),
        fLogOutItem(NULL),
        fUsersUserUsageConditionsMenuItem(NULL),
        fModelListener(new MainWindowModelListener(BMessenger(this)), true),
        fCoordinator(NULL),
        fShouldCloseWhenNoProcessesToCoordinate(false),
        fSinglePackageMode(false),
        fIncrementViewCounterDelayedRunner(NULL)
{
        if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
                debugger("unable to create the process coordinator semaphore");

        _InitPreferredLanguage();

        fPackageInfoListener = PackageInfoListenerRef(new MainWindowPackageInfoListener(this), true);

        BMenuBar* menuBar = new BMenuBar("Main Menu");
        _BuildMenu(menuBar);

        BMenuBar* userMenuBar = new BMenuBar("User Menu");
        _BuildUserMenu(userMenuBar);
        set_small_font(userMenuBar);
        userMenuBar->SetExplicitMaxSize(BSize(B_SIZE_UNSET, menuBar->MaxSize().height));

        fFilterView = new FilterView();
        fFeaturedPackagesView = new FeaturedPackagesView(fModel);
        fPackageListView = new PackageListView(&fModel);
        fPackageInfoView = new PackageInfoView(&fModel, this);

        fSplitView = new BSplitView(B_VERTICAL, 5.0f);

        fWorkStatusView = new WorkStatusView("work status");
        fPackageListView->AttachWorkStatusView(fWorkStatusView);

        fListTabs
                = new TabView(BMessenger(this), BMessage(MSG_CHANGE_PACKAGE_LIST_VIEW_MODE), "list tabs");
        fListTabs->AddTab(fFeaturedPackagesView);
        fListTabs->AddTab(fPackageListView);

        BLayoutBuilder::Group<>(this, B_VERTICAL, 0.0f)
                .AddGroup(B_HORIZONTAL, 0.0f)
                        .Add(menuBar, 1.0f)
                        .Add(userMenuBar, 0.0f)
                .End()
                .Add(fFilterView)
                .AddSplit(fSplitView)
                        .AddGroup(B_VERTICAL)
                                .Add(fListTabs)
                                .SetInsets(
                                        B_USE_DEFAULT_SPACING, 0.0f,
                                        B_USE_DEFAULT_SPACING, 0.0f)
                        .End()
                        .Add(fPackageInfoView)
                .End()
                .Add(fWorkStatusView)
        ;

        fSplitView->SetCollapsible(0, false);
        fSplitView->SetCollapsible(1, false);

        fModel.AddListener(fModelListener);
        fModel.AddPackageListener(fPackageInfoListener);

        BMessage columnSettings;
        if (settings.FindMessage(kKeyColumnSettings, &columnSettings) == B_OK)
                fPackageListView->LoadState(&columnSettings);

        _RestoreModelSettings(settings);
        _MaybePromptCanShareAnonymousUserData(settings);

        if (fModel.PackageListViewMode() == PROMINENT)
                fListTabs->Select(TAB_PROMINENT_PACKAGES);
        else
                fListTabs->Select(TAB_ALL_PACKAGES);

        _RestoreNickname(settings);
        _UpdateAuthorization();
        _RestoreWindowFrame(settings);

        // start worker threads
        BPackageRoster().StartWatching(this, B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);

        _AdoptModel();
        _StartBulkLoad();
}


/*!     This constructor is used when the application is loaded for the purpose of
        viewing an HPKG file.
*/

MainWindow::MainWindow(const BMessage& settings, const PackageInfoRef package)
        :
        BWindow(BRect(50, 50, 650, 350), B_TRANSLATE_SYSTEM_NAME("HaikuDepot"), B_DOCUMENT_WINDOW_LOOK,
                B_NORMAL_WINDOW_FEEL, B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
        fFeaturedPackagesView(NULL),
        fPackageListView(NULL),
        fWorkStatusView(NULL),
        fScreenshotWindow(NULL),
        fShuttingDownWindow(NULL),
        fUserMenu(NULL),
        fLogInItem(NULL),
        fLogOutItem(NULL),
        fUsersUserUsageConditionsMenuItem(NULL),
        fModelListener(new MainWindowModelListener(BMessenger(this)), true),
        fCoordinator(NULL),
        fShouldCloseWhenNoProcessesToCoordinate(false),
        fSinglePackageMode(true),
        fIncrementViewCounterDelayedRunner(NULL)
{
        SetTitle(_WindowTitleForPackage(package));

        if ((fCoordinatorRunningSem = create_sem(1, "ProcessCoordinatorSem")) < B_OK)
                debugger("unable to create the process coordinator semaphore");

        _InitPreferredLanguage();

        fPackageInfoListener = PackageInfoListenerRef(new MainWindowPackageInfoListener(this), true);

        fFilterView = new FilterView();
        fPackageInfoView = new PackageInfoView(&fModel, this);
        fWorkStatusView = new WorkStatusView("work status");

        BLayoutBuilder::Group<>(this, B_VERTICAL)
                .Add(fPackageInfoView)
                .Add(fWorkStatusView)
                .SetInsets(0, B_USE_WINDOW_INSETS, 0, 0);

        fModel.AddListener(fModelListener);
        fModel.AddPackageListener(fPackageInfoListener);

        // add the single package into the model so that any internal
        // business logic is able to find the package.
        DepotInfoRef depot = DepotInfoBuilder()
                                                         .WithName(SINGLE_PACKAGE_DEPOT_NAME)
                                                         .WithIdentifier(SINGLE_PACKAGE_DEPOT_IDENTIFIER)
                                                         .BuildRef();

        PackageCoreInfoRef coreInfo = PackageCoreInfoBuilder(package->CoreInfo())
                                                                          .WithDepotName(SINGLE_PACKAGE_DEPOT_IDENTIFIER)
                                                                          .BuildRef();

        PackageInfoRef packageWithDepotIdentifier
                = PackageInfoBuilder(package).WithCoreInfo(coreInfo).BuildRef();

        fModel.SetDepots(depot);
        fModel.AddPackage(packageWithDepotIdentifier);

        // Restore settings
        _RestoreNickname(settings);
        _UpdateAuthorization();
        _RestoreWindowFrame(settings);

        fPackageInfoView->SetPackage(packageWithDepotIdentifier);

        // start worker threads
        BPackageRoster().StartWatching(this, B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
}


MainWindow::~MainWindow()
{
        _SpinUntilProcessCoordinatorComplete();
        delete_sem(fCoordinatorRunningSem);
        fCoordinatorRunningSem = 0;

        BPackageRoster().StopWatching(this);

        if (fScreenshotWindow != NULL) {
                if (fScreenshotWindow->Lock())
                        fScreenshotWindow->Quit();
        }

        if (fShuttingDownWindow != NULL) {
                if (fShuttingDownWindow->Lock())
                        fShuttingDownWindow->Quit();
        }

        // We must clear the model early to release references.
        fModel.Clear();
}


bool
MainWindow::QuitRequested()
{

        _StopProcessCoordinators();

        // If there are any processes in flight we need to be careful to make
        // sure that they are cleanly completed before actually quitting.  By
        // turning on the `fShouldCloseWhenNoProcessesToCoordinate` flag, when
        // the process coordination has completed then it will detect this and
        // quit again.

        {
                AutoLocker<BLocker> lock(&fCoordinatorLock);
                fShouldCloseWhenNoProcessesToCoordinate = true;

                if (fCoordinator != NULL) {
                        HDINFO("a coordinator is running --> will wait before quitting...");

                        if (fShuttingDownWindow == NULL)
                                fShuttingDownWindow = new ShuttingDownWindow(this);
                        fShuttingDownWindow->Show();

                        return false;
                }
        }

        BMessage settings;
        StoreSettings(settings);
        BMessage message(MSG_MAIN_WINDOW_CLOSED);
        message.AddMessage(main_window_keys::kKeyWindowSettings, &settings);
        be_app->PostMessage(&message);

        if (fShuttingDownWindow != NULL) {
                if (fShuttingDownWindow->Lock())
                        fShuttingDownWindow->Quit();
                fShuttingDownWindow = NULL;
        }

        return true;
}


void
MainWindow::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case MSG_BULK_LOAD_DONE:
                {
                        int64 errorStatus64;
                        if (message->FindInt64(KEY_ERROR_STATUS, &errorStatus64) == B_OK)
                                _BulkLoadCompleteReceived(static_cast<status_t>(errorStatus64));
                        else
                                HDERROR("expected [%s] value in message", KEY_ERROR_STATUS);
                        break;
                }
                case B_SIMPLE_DATA:
                case B_REFS_RECEIVED:
                        // TODO: ?
                        break;

                case B_PACKAGE_UPDATE:
                        _HandleExternalPackageUpdateMessageReceived(message);
                        break;

                case MSG_REFRESH_REPOS:
                        _StartBulkLoad(true);
                        break;

                case MSG_WORK_STATUS_CLEAR:
                        _HandleWorkStatusClear();
                        break;

                case MSG_WORK_STATUS_CHANGE:
                        _HandleWorkStatusChangeMessageReceived(message);
                        break;

                case MSG_MANAGE_REPOS:
                        be_roster->Launch("application/x-vnd.Haiku-Repositories");
                        break;

                case MSG_SOFTWARE_UPDATER:
                        be_roster->Launch("application/x-vnd.haiku-softwareupdater");
                        break;

                case MSG_LOG_IN:
                        _OpenLoginWindow(BMessage());
                        break;

                case MSG_SETTINGS:
                        _OpenSettingsWindow();
                        break;

                case MSG_LOG_OUT:
                        if (IdentityAndAccessUtils::ClearCredentials() != B_OK)
                                HDERROR("unable to remove stored credentials");
                        fModel.SetCredentials(UserCredentials());
                        break;

                case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS:
                        _ViewUserUsageConditions(LATEST);
                        break;

                case MSG_VIEW_USERS_USER_USAGE_CONDITIONS:
                        _ViewUserUsageConditions(USER);
                        break;

                case MSG_AUTHORIZATION_CHANGED:
                        if (!fSinglePackageMode)
                                _StartUserVerify();
                        _UpdateAuthorization();
                        break;

                case MSG_ICONS_CHANGED:
                        _HandleIconsChanged();
                        break;

                case MSG_SCREENSHOT_CACHED:
                        _HandleScreenshotCached(message);
                        break;

                case MSG_CATEGORIES_LIST_CHANGED:
                        fFilterView->AdoptModel(fModel);
                        break;

                case MSG_PACKAGE_FILTER_CHANGED:
                        // This message is originating from the model via a listener and
                        // signals that specification of the package filter have changed
                        // so that the application should re-filter the packages.
                        _AdoptModel();
                        break;

                case MSG_CHANGE_PACKAGE_LIST_VIEW_MODE:
                        _HandleChangePackageListViewMode();
                        break;

                case MSG_SHOW_DESKTOP_PACKAGES:
                        _SetPackageDesktopFilterMode(DESKTOP);
                        break;

                case MSG_SHOW_NATIVE_DESKTOP_PACKAGES:
                        _SetPackageDesktopFilterMode(NATIVE_DESKTOP);
                        break;

                case MSG_SHOW_DESKTOP_AND_NON_DESKTOP_PACKAGES:
                        _SetPackageDesktopFilterMode(DESKTOP_AND_NON_DESKTOP);
                        break;

                case MSG_SHOW_AVAILABLE_PACKAGES:
                {
                        fModel.SetFilterSpecification(
                                PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                                        .WithShowAvailablePackages(!fShowAvailablePackagesItem->IsMarked())
                                        // inverse because it should be toggled
                                        .BuildRef());
                        break;
                }

                case MSG_SHOW_INSTALLED_PACKAGES:
                {
                        fModel.SetFilterSpecification(
                                PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                                        .WithShowInstalledPackages(!fShowInstalledPackagesItem->IsMarked())
                                                // inverse because it should be toggled
                                        .BuildRef());
                        break;
                }

                case MSG_SHOW_SOURCE_PACKAGES:
                {
                        fModel.SetFilterSpecification(
                                PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                                        .WithShowSourcePackages(!fShowSourcePackagesItem->IsMarked())
                                        // inverse because it should be toggled
                                        .BuildRef());
                        break;
                }

                case MSG_SHOW_DEVELOP_PACKAGES:
                {
                        fModel.SetFilterSpecification(
                                PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                                        .WithShowDevelopPackages(!fShowDevelopPackagesItem->IsMarked())
                                        // inverse because it should be toggled
                                        .BuildRef());
                        break;
                }

                        // this may be triggered by, for example, a user rating being added
                        // or having been altered.
                case MSG_SERVER_DATA_CHANGED:
                {
                        BString name;
                        if (message->FindString(shared_message_keys::kKeyPackageName, &name) == B_OK) {
                                if (fPackageInfoView->Package()->Name() == name) {
                                        _PopulatePackageAsync(true);
                                } else {
                                        HDDEBUG("pkg [%s] is updated on the server, but is not selected so will not be "
                                                        "updated.",
                                                name.String());
                                }
                        }
                        break;
                }

                case MSG_INCREMENT_VIEW_COUNTER:
                        _HandleIncrementViewCounter(message);
                        break;

                case MSG_PACKAGE_SELECTED:
                {
                        BString name;
                        if (message->FindString(shared_message_keys::kKeyPackageName, &name) == B_OK) {
                                PackageInfoRef package;
                                package = fModel.PackageForName(name);

                                if (!package.IsSet() || name != package->Name()) {
                                        debugger("unable to find the named package");
                                } else {
                                        _AdoptPackage(package);
                                        _SetupDelayedIncrementViewCounter(package);
                                }
                        } else {
                                _ClearPackage();
                        }
                        break;
                }

                case MSG_CATEGORY_SELECTED:
                {
                        BString code;
                        if (message->FindString(shared_message_keys::kKeyCode, &code) != B_OK)
                                code = "";
                        fModel.SetFilterSpecification(
                                PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                                        .WithCategory(code)
                                        .BuildRef());
                        break;
                }

                case MSG_DEPOT_SELECTED:
                {
                        BString name;
                        if (message->FindString(shared_message_keys::kKeyDepotName, &name) != B_OK)
                                name = "";
                        fModel.SetFilterSpecification(
                                PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                                        .WithDepotName(name)
                                        .BuildRef());
                        _UpdateAvailableRepositories();
                        break;
                }

                case MSG_SEARCH_TERMS_MODIFIED:
                {
                        // TODO: Do this with a delay!
                        BString searchTerms;
                        if (message->FindString(filter_view_keys::kKeySearchTerms, &searchTerms) != B_OK)
                                searchTerms = "";
                        fModel.SetFilterSpecification(
                                PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                                        .WithSearchTerms(searchTerms)
                                        .BuildRef());
                        break;
                }

                case MSG_PACKAGES_CHANGED:
                        _HandlePackagesChanged(message);
                        break;

                case MSG_PROCESS_COORDINATOR_CHANGED:
                {
                        ProcessCoordinatorState state(message);
                        _HandleProcessCoordinatorChanged(state);
                        break;
                }

                case MSG_PKG_INSTALL:
                {
                        InstallPackageAction action(message);
                        ProcessCoordinator* coordinator
                                = ProcessCoordinatorFactory::CreateInstallPackageActionCoordinator(&fModel, action);
                        _AddProcessCoordinator(coordinator);
                        break;
                }

                case MSG_PKG_UNINSTALL:
                {
                        UninstallPackageAction action(message);
                        ProcessCoordinator* coordinator
                                = ProcessCoordinatorFactory::CreateUninstallPackageActionCoordinator(&fModel,
                                        action);
                        _AddProcessCoordinator(coordinator);
                        break;
                }

                case MSG_PKG_OPEN:
                {
                        OpenPackageAction action(message);
                        ProcessCoordinator* coordinator
                                = ProcessCoordinatorFactory::CreateOpenPackageActionCoordinator(&fModel, action);
                        _AddProcessCoordinator(coordinator);
                        break;
                }

                case MSG_RATE_PACKAGE:
                        _RatePackage();
                        break;

                case MSG_SHOW_SCREENSHOT:
                        _ShowScreenshot();
                        break;

                case MSG_PACKAGE_WORKER_BUSY:
                {
                        BString reason;
                        status_t status = message->FindString("reason", &reason);
                        if (status != B_OK)
                                break;
                        fWorkStatusView->SetBusy(reason);
                        break;
                }

                case MSG_PACKAGE_WORKER_IDLE:
                        fWorkStatusView->SetIdle();
                        break;

                case MSG_USER_USAGE_CONDITIONS_NOT_LATEST:
                {
                        BMessage userDetailMsg;
                        if (message->FindMessage("userDetail", &userDetailMsg) != B_OK)
                                debugger("expected the [userDetail] data to be carried in the message.");
                        UserDetail userDetail(&userDetailMsg);
                        _HandleUserUsageConditionsNotLatest(userDetail);
                        break;
                }

                case MSG_SHOW_ALL_PACKAGES_TAB:
                        fListTabs->Select(TAB_ALL_PACKAGES);
                        break;

                default:
                        BWindow::MessageReceived(message);
                        break;
        }
}


static const char*
main_window_package_list_view_mode_str(package_list_view_mode mode)
{
        if (mode == PROMINENT)
                return "PROMINENT";
        return "ALL";
}


static package_list_view_mode
main_window_str_to_package_list_view_mode(const BString& str)
{
        if (str == "PROMINENT")
                return PROMINENT;
        return ALL;
}


void
MainWindow::StoreSettings(BMessage& settings)
{
        settings.AddRect(_WindowFrameName(), Frame());
        if (!fSinglePackageMode) {
                settings.AddRect(kKeyWindowFrame, Frame());

                BMessage columnSettings;
                if (fPackageListView != NULL)
                        fPackageListView->SaveState(&columnSettings);

                settings.AddMessage(kKeyColumnSettings, &columnSettings);

                settings.AddString(SETTING_PACKAGE_LIST_VIEW_MODE,
                        main_window_package_list_view_mode_str(fModel.PackageListViewMode()));

                settings.AddBool(SETTING_SHOW_DESKTOP_PACKAGES,
                        fModel.FilterSpecification()->ShowOnlyDesktopPackages());
                settings.AddBool(SETTING_SHOW_NATIVE_DESKTOP_PACKAGES,
                        fModel.FilterSpecification()->ShowOnlyNativeDesktopPackages());
                settings.AddBool(SETTING_SHOW_AVAILABLE_PACKAGES,
                        fModel.FilterSpecification()->ShowAvailablePackages());
                settings.AddBool(SETTING_SHOW_INSTALLED_PACKAGES,
                        fModel.FilterSpecification()->ShowInstalledPackages());
                settings.AddBool(SETTING_SHOW_DEVELOP_PACKAGES,
                        fModel.FilterSpecification()->ShowDevelopPackages());
                settings.AddBool(SETTING_SHOW_SOURCE_PACKAGES,
                        fModel.FilterSpecification()->ShowSourcePackages());

                settings.AddBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA,
                        fModel.CanShareAnonymousUsageData());
        }

        settings.AddString(SETTING_NICKNAME, fModel.Nickname());
}


void
MainWindow::Consume(ProcessCoordinator* item)
{
        _AddProcessCoordinator(item);
}


/*!     This method is invoked in the situation that a package changes in the model.

        The events are processed into a `BMessage` which is then posted to the
        window. The message is then trans-coded back into events for processing
        in the GUI.
*/
void
MainWindow::PackagesChanged(const PackageChangeEvents& events)
{
        BMessage message(MSG_PACKAGES_CHANGED);
        status_t result = events.Archive(&message);

        if (result == B_OK)
                PostMessage(&message);
        else
                HDERROR("unable to archive the package into events to a message");
}


/*!     This method is invoked as a result of the packages changing and the
        `MainWindow` finding out about this in the UI thread. From here it
        is able to inform other elements of the GUI about the change.
*/
void
MainWindow::_HandlePackagesChanged(const BMessage* message)
{
        PackageChangeEvents packageEvents(message);

        // if there are no events to process then drop.

        if (packageEvents.IsEmpty()) {
                HDINFO("window encountered an empty packages changed");
                return;
        }

        HDTRACE("window processing %" B_PRIi32 " package changes", packageEvents.CountEvents());

        // Transfer the package change events into package *info* change events so
        // that the immutable PackageInfo instances can be used in the processing
        // without having to continuously query the model causing lock overhead.

        std::vector<PackageInfoChangeEvent> packageInfoEvents;

        for (int32 i = packageEvents.CountEvents() - 1; i >= 0; i--) {
                const PackageChangeEvent packageEvent = packageEvents.EventAtIndex(i);
                const PackageInfoRef packageInfo = fModel.PackageForName(packageEvent.PackageName());

                if (packageInfo.IsSet()) {
                        const PackageInfoChangeEvent packageInfoEvent(packageInfo, packageEvent.Changes());
                        packageInfoEvents.push_back(packageInfoEvent);
                } else {
                        HDERROR("package [%s] from change event not found",
                                packageEvent.PackageName().String());
                }
        }

        // now process the messages by adding and removing them from lists.

        if (!fSinglePackageMode) {
                uint32 watchedChanges = PKG_CHANGED_LOCAL_INFO | PKG_CHANGED_CLASSIFICATION;
                PackageFilterRef filter = fModel.Filter();

                std::vector<PackageInfoRef> addedPackages;
                std::vector<PackageInfoRef> removedPackages;
                std::vector<PackageInfoRef> addedFeaturedPackages;
                std::vector<PackageInfoRef> removedFeaturedPackages;

                std::vector<PackageInfoChangeEvent>::const_iterator it;

                for (it = packageInfoEvents.begin(); it != packageInfoEvents.end(); it++) {
                        const PackageInfoChangeEvent packageInfoEvent = *it;

                        if (packageInfoEvent.Changes() & watchedChanges) {
                                const PackageInfoRef package = packageInfoEvent.Package();

                                if (package.IsSet()) {
                                        const bool isProminent = PackageUtils::IsProminent(package);

                                        if (filter->AcceptsPackage(package)) {
                                                addedPackages.push_back(package);

                                                if (isProminent)
                                                        addedFeaturedPackages.push_back(package);
                                        } else {
                                                removedPackages.push_back(package);

                                                if (isProminent)
                                                        removedFeaturedPackages.push_back(package);
                                        }
                                } else {
                                        HDFATAL("package change event for missing package");
                                                // should have checked earlier so this is an illegal state
                                }
                        }
                }

                fPackageListView->AddRemovePackages(addedPackages, removedPackages);
                fFeaturedPackagesView->AddRemovePackages(addedFeaturedPackages, removedFeaturedPackages);
        }

        // Now relay the changes to the other UI elements. This seems a bit strange
        // because we're controlling which packages are in the lists above, but then
        // updating anyway. This makes sense because the lists themselves should not
        // determine which packages are assigned.

        if (!fSinglePackageMode) {
                fFeaturedPackagesView->HandlePackagesChanged(packageInfoEvents);
                fPackageListView->HandlePackagesChanged(packageInfoEvents);
        }

        fPackageInfoView->HandlePackagesChanged(packageInfoEvents);
}


void
MainWindow::_BuildMenu(BMenuBar* menuBar)
{
        BMenu* menu = new BMenu(B_TRANSLATE_SYSTEM_NAME("HaikuDepot"));
        fRefreshRepositoriesItem
                = new BMenuItem(B_TRANSLATE("Refresh repositories"), new BMessage(MSG_REFRESH_REPOS));
        menu->AddItem(fRefreshRepositoriesItem);
        menu->AddItem(new BMenuItem(B_TRANSLATE("Manage repositories" B_UTF8_ELLIPSIS),
                new BMessage(MSG_MANAGE_REPOS)));
        menu->AddItem(new BMenuItem(B_TRANSLATE("Check for updates" B_UTF8_ELLIPSIS),
                new BMessage(MSG_SOFTWARE_UPDATER)));
        menu->AddSeparatorItem();
        menu->AddItem(
                new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), new BMessage(MSG_SETTINGS), ','));
        menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), new BMessage(B_QUIT_REQUESTED), 'Q'));
        menuBar->AddItem(menu);

        fRepositoryMenu = new BMenu(B_TRANSLATE("Repositories"));
        menuBar->AddItem(fRepositoryMenu);

        menu = new BMenu(B_TRANSLATE("Show"));

        fShowDesktopAndNonDesktopItem = new BMenuItem(B_TRANSLATE("Desktop and non-desktop packages"),
                new BMessage(MSG_SHOW_DESKTOP_AND_NON_DESKTOP_PACKAGES));
        menu->AddItem(fShowDesktopAndNonDesktopItem);

        fShowOnlyDesktopItem
                = new BMenuItem(B_TRANSLATE("Desktop packages only"), new BMessage(MSG_SHOW_DESKTOP_PACKAGES));
        menu->AddItem(fShowOnlyDesktopItem);

        fShowOnlyNativeDesktopItem = new BMenuItem(B_TRANSLATE("Native desktop packages only"),
                new BMessage(MSG_SHOW_NATIVE_DESKTOP_PACKAGES));
        menu->AddItem(fShowOnlyNativeDesktopItem);

        menu->AddSeparatorItem();

        fShowAvailablePackagesItem = new BMenuItem(B_TRANSLATE("Available packages"),
                new BMessage(MSG_SHOW_AVAILABLE_PACKAGES));
        menu->AddItem(fShowAvailablePackagesItem);

        fShowInstalledPackagesItem = new BMenuItem(B_TRANSLATE("Installed packages"),
                new BMessage(MSG_SHOW_INSTALLED_PACKAGES));
        menu->AddItem(fShowInstalledPackagesItem);

        menu->AddSeparatorItem();

        fShowDevelopPackagesItem
                = new BMenuItem(B_TRANSLATE("Develop packages"), new BMessage(MSG_SHOW_DEVELOP_PACKAGES));
        menu->AddItem(fShowDevelopPackagesItem);

        fShowSourcePackagesItem
                = new BMenuItem(B_TRANSLATE("Source packages"), new BMessage(MSG_SHOW_SOURCE_PACKAGES));
        menu->AddItem(fShowSourcePackagesItem);

        menuBar->AddItem(menu);
}


void
MainWindow::_BuildUserMenu(BMenuBar* menuBar)
{
        fUserMenu = new BMenu(B_TRANSLATE("Not logged in"));

        fLogInItem = new BMenuItem(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS), new BMessage(MSG_LOG_IN));
        fUserMenu->AddItem(fLogInItem);

        fLogOutItem = new BMenuItem(B_TRANSLATE("Log out"), new BMessage(MSG_LOG_OUT));
        fUserMenu->AddItem(fLogOutItem);

        BMenuItem* latestUserUsageConditionsMenuItem
                = new BMenuItem(B_TRANSLATE("View latest usage conditions" B_UTF8_ELLIPSIS),
                        new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS));
        fUserMenu->AddItem(latestUserUsageConditionsMenuItem);

        fUsersUserUsageConditionsMenuItem
                = new BMenuItem(B_TRANSLATE("View agreed usage conditions" B_UTF8_ELLIPSIS),
                        new BMessage(MSG_VIEW_USERS_USER_USAGE_CONDITIONS));
        fUserMenu->AddItem(fUsersUserUsageConditionsMenuItem);

        menuBar->AddItem(fUserMenu);
}


void
MainWindow::_RestoreNickname(const BMessage& settings)
{
        BString nickname;
        if (settings.FindString(SETTING_NICKNAME, &nickname) == B_OK && nickname.Length() > 0) {
                UserCredentials credentials;
                if (IdentityAndAccessUtils::RetrieveCredentials(nickname, credentials) == B_OK) {
                        fModel.SetCredentials(credentials);
                        HDINFO("credentials restored");
                } else {
                        HDERROR("the credentials are unable to be restored");
                }
        } else {
                HDINFO("no credentials to restore");
        }
}


const char*
MainWindow::_WindowFrameName() const
{
        if (fSinglePackageMode)
                return kKeySinglePackageWindowFrame;

        return kKeyWindowFrame;
}


void
MainWindow::_RestoreWindowFrame(const BMessage& settings)
{
        BRect frame = Frame();

        BRect windowFrame;
        bool fromSettings = false;
        if (settings.FindRect(_WindowFrameName(), &windowFrame) == B_OK) {
                frame = windowFrame;
                fromSettings = true;
        } else if (!fSinglePackageMode) {
                // Resize to occupy a certain screen size
                BRect screenFrame = BScreen(this).Frame();
                float width = frame.Width();
                float height = frame.Height();
                if (width < screenFrame.Width() * .666f && height < screenFrame.Height() * .666f) {
                        frame.bottom = frame.top + screenFrame.Height() * .666f;
                        frame.right = frame.left + std::min(screenFrame.Width() * .666f, height * 7 / 5);
                }
        }

        MoveTo(frame.LeftTop());
        ResizeTo(frame.Width(), frame.Height());

        if (fromSettings)
                MoveOnScreen();
        else
                CenterOnScreen();
}


void
MainWindow::_RestoreModelSettings(const BMessage& settings)
{
        BString packageListViewMode;
        if (settings.FindString(SETTING_PACKAGE_LIST_VIEW_MODE, &packageListViewMode) == B_OK) {
                fModel.SetPackageListViewMode(
                        main_window_str_to_package_list_view_mode(packageListViewMode));
        }

        bool showOption;
        PackageFilterSpecificationBuilder filterSpecificationBuilder;

        if (settings.FindBool(SETTING_SHOW_DESKTOP_PACKAGES, &showOption) == B_OK)
                filterSpecificationBuilder.WithShowOnlyDesktopPackages(showOption);
        if (settings.FindBool(SETTING_SHOW_NATIVE_DESKTOP_PACKAGES, &showOption) == B_OK)
                filterSpecificationBuilder.WithShowOnlyNativeDesktopPackages(showOption);
        if (settings.FindBool(SETTING_SHOW_AVAILABLE_PACKAGES, &showOption) == B_OK)
                filterSpecificationBuilder.WithShowAvailablePackages(showOption);
        if (settings.FindBool(SETTING_SHOW_INSTALLED_PACKAGES, &showOption) == B_OK)
                filterSpecificationBuilder.WithShowInstalledPackages(showOption);
        if (settings.FindBool(SETTING_SHOW_DEVELOP_PACKAGES, &showOption) == B_OK)
                filterSpecificationBuilder.WithShowDevelopPackages(showOption);
        if (settings.FindBool(SETTING_SHOW_SOURCE_PACKAGES, &showOption) == B_OK)
                filterSpecificationBuilder.WithShowSourcePackages(showOption);

        fModel.SetFilterSpecification(filterSpecificationBuilder.BuildRef());

        if (settings.FindBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA, &showOption) == B_OK)
                fModel.SetCanShareAnonymousUsageData(showOption);
}


void
MainWindow::_MaybePromptCanShareAnonymousUserData(const BMessage& settings)
{
        bool showOption;
        if (settings.FindBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA, &showOption) == B_NAME_NOT_FOUND)
                _PromptCanShareAnonymousUserData();
}


void
MainWindow::_PromptCanShareAnonymousUserData()
{
        BAlert* alert = new(std::nothrow) BAlert(B_TRANSLATE("Sending anonymous usage data"),
                B_TRANSLATE("Would it be acceptable to send anonymous usage data to the"
                                        " HaikuDepotServer system from this computer? You can change your"
                                        " preference in the \"Settings\" window later."),
                B_TRANSLATE("No"), B_TRANSLATE("Yes"));

        int32 result = alert->Go();
        fModel.SetCanShareAnonymousUsageData(1 == result);
}


/*!     This method will be invoked before reference data is loaded in; it is an
        initialization process.
*/
void
MainWindow::_InitPreferredLanguage()
{
        // TODO (apl) it would be nice to pre-detect the presence of a reference data
        //    file and load this early. See the logic in `ServerReferenceDataUpdateProcess`
        //    for details.
        const std::vector<LanguageRef> existingLanguages = fModel.Languages();
        LanguageRef defaultLanguage = LocaleUtils::DeriveDefaultLanguage(existingLanguages);
        std::vector<LanguageRef> languages(existingLanguages.begin(), existingLanguages.end());

        if (std::find(languages.begin(), languages.end(), defaultLanguage) == languages.end())
                languages.push_back(defaultLanguage);

        fModel.SetLanguagesAndPreferred(languages, defaultLanguage);
}


void
MainWindow::_SetPackageDesktopFilterMode(PackageDesktopFilterMode mode)
{
        fModel.SetFilterSpecification(PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                        .WithShowOnlyNativeDesktopPackages(mode == NATIVE_DESKTOP)
                        .WithShowOnlyDesktopPackages(mode == DESKTOP)
                        .BuildRef());
}


void
MainWindow::_AdoptModelControls()
{
        if (fSinglePackageMode)
                return;

        {
                PackagesSummary packagesSummary = fModel.GeneratePackagesSummary();
                fShowOnlyDesktopItem->SetEnabled(packagesSummary.HasAnyDesktop());
                fShowOnlyNativeDesktopItem->SetEnabled(packagesSummary.HasAnyNativeDesktop());
                fListTabs->TabAt(TAB_PROMINENT_PACKAGES)
                        ->SetEnabled(packagesSummary.HasAnyProminentPackages());
        }

        PackageFilterSpecificationRef packageFilterSpecification = fModel.FilterSpecification();

        fShowOnlyDesktopItem->SetMarked(packageFilterSpecification->ShowOnlyDesktopPackages());
        fShowOnlyNativeDesktopItem->SetMarked(
                packageFilterSpecification->ShowOnlyNativeDesktopPackages());
        fShowDesktopAndNonDesktopItem->SetMarked(!packageFilterSpecification->ShowOnlyDesktopPackages()
                && !packageFilterSpecification->ShowOnlyNativeDesktopPackages());

        fShowAvailablePackagesItem->SetMarked(packageFilterSpecification->ShowAvailablePackages());
        fShowInstalledPackagesItem->SetMarked(packageFilterSpecification->ShowInstalledPackages());
        fShowSourcePackagesItem->SetMarked(packageFilterSpecification->ShowSourcePackages());
        fShowDevelopPackagesItem->SetMarked(packageFilterSpecification->ShowDevelopPackages());

        if (fModel.PackageListViewMode() == PROMINENT)
                fListTabs->Select(TAB_PROMINENT_PACKAGES);
        else
                fListTabs->Select(TAB_ALL_PACKAGES);

        fFilterView->AdoptModel(fModel);
}


void
MainWindow::_AdoptModel()
{
        BStopWatch stopWatch("adopt_model", true);
        HDTRACE("adopting model to main window ui");

        if (fSinglePackageMode)
                return;

        // This list will contain all of the packages that are under consideration.
        // Create a new list which only contains the featured packages.

        const std::vector<PackageInfoRef> packages = _CreateSnapshotOfFilteredPackages();
        std::vector<PackageInfoRef> featuredPackages;
        std::vector<PackageInfoRef>::const_iterator it;

        for (it = packages.begin(); it != packages.end(); it++) {
                PackageInfoRef package = *it;
                if (PackageUtils::IsProminent(package))
                        featuredPackages.push_back(package);
        }

        // Now get the UI elements which display those packages to only remain the
        // filtered list.

        fPackageListView->RetainPackages(packages);
        fFeaturedPackagesView->RetainPackages(featuredPackages);

        _AdoptModelControls();

        double durationSeconds = ((double)stopWatch.ElapsedTime() / 1000000.0);
        HDDEBUG("did adopt model in %6.3f seconds", durationSeconds);
}


void
MainWindow::_SetupDelayedIncrementViewCounter(const PackageInfoRef package)
{
        if (fIncrementViewCounterDelayedRunner != NULL) {
                fIncrementViewCounterDelayedRunner->SetCount(0);
                delete fIncrementViewCounterDelayedRunner;
        }
        BMessage message(MSG_INCREMENT_VIEW_COUNTER);
        message.SetString(shared_message_keys::kKeyPackageName, package->Name());
        fIncrementViewCounterDelayedRunner
                = new BMessageRunner(BMessenger(this), &message, kIncrementViewCounterDelayMicros, 1);
        if (fIncrementViewCounterDelayedRunner->InitCheck() != B_OK)
                HDERROR("unable to init the increment view counter");
}


void
MainWindow::_HandleIncrementViewCounter(const BMessage* message)
{
        BString name;
        if (message->FindString(shared_message_keys::kKeyPackageName, &name) == B_OK) {
                const PackageInfoRef& viewedPackage = fPackageInfoView->Package();
                if (viewedPackage.IsSet()) {
                        const BString& viewedPackageName = viewedPackage->Name();

                        if (viewedPackageName == name)
                                _IncrementViewCounter(viewedPackageName);
                        else
                                HDINFO("incr. view counter; name mismatch");
                } else {
                        HDINFO("incr. view counter; no viewed pkg");
                }
        } else {
                HDERROR("incr. view counter; no name");
        }
        fIncrementViewCounterDelayedRunner->SetCount(0);
        delete fIncrementViewCounterDelayedRunner;
        fIncrementViewCounterDelayedRunner = NULL;
}


void
MainWindow::_IncrementViewCounter(const BString& packageName)
{
        const char* packageNameStr = packageName.String();
        const PackageInfoRef package = fModel.PackageForName(packageName);

        if (!package.IsSet()) {
                HDERROR("the package [%s] it not available -- won't increment the view counter",
                        packageNameStr);
                return;
        }

        bool canShareAnonymousUsageData = fModel.CanShareAnonymousUsageData();

        // The logic here checks to see that the user has approved that their viewing of the package
        // can be anonymously recorded as a metric. We also check that within this session, the
        // package has not already been incremented as we count a viewing of a package within a
        // session of HaikuDepot desktop application as unique.

        if (canShareAnonymousUsageData && !PackageUtils::Viewed(package)) {
                if (ServerHelper::IsNetworkAvailable()) {
                        PackageLocalInfoRef localInfoRef
                                = PackageLocalInfoBuilder(package->LocalInfo()).WithViewed().BuildRef();
                        fModel.AddPackage(PackageInfoBuilder(package).WithLocalInfo(localInfoRef).BuildRef());

                        ProcessCoordinator* incrementViewCoordinator
                                = ProcessCoordinatorFactory::CreateIncrementViewCounter(&fModel, package);
                        _AddProcessCoordinator(incrementViewCoordinator);

                        HDINFO("pkg [%s] will increment counter", packageNameStr);
                } else {
                        HDINFO("pkg [%s] won't increment counter; network unavailable", packageNameStr);
                }
        }
}


void
MainWindow::_AdoptPackage(const PackageInfoRef& package)
{
        fPackageInfoView->SetPackage(package);

        if (!fSinglePackageMode) {
                if (fFeaturedPackagesView != NULL)
                        fFeaturedPackagesView->SelectPackage(package);
                if (fPackageListView != NULL)
                        fPackageListView->SelectPackage(package);
        }

        _PopulatePackageAsync(false);
}


void
MainWindow::_ClearPackage()
{
        fPackageInfoView->Clear();
}


void
MainWindow::_StartBulkLoad(bool force)
{
        if (!fSinglePackageMode) {
                if (fFeaturedPackagesView != NULL)
                        fFeaturedPackagesView->Clear();
                if (fPackageListView != NULL)
                        fPackageListView->Clear();
        }

        fPackageInfoView->Clear();
        fFeaturedPackagesView->SetLoading(true);

        fRefreshRepositoriesItem->SetEnabled(false);
        ProcessCoordinator* bulkLoadCoordinator
                = ProcessCoordinatorFactory::CreateBulkLoadCoordinator(&fModel, force);
        _AddProcessCoordinator(bulkLoadCoordinator);
}


void
MainWindow::_BulkLoadCompleteReceived(status_t errorStatus)
{
        fFeaturedPackagesView->SetLoading(false);
        fRefreshRepositoriesItem->SetEnabled(true);
        PackagesSummary packagesSummary = fModel.GeneratePackagesSummary();

        if (errorStatus != B_OK) {
                AppUtils::NotifySimpleError(SimpleAlert(B_TRANSLATE("Package update error"),
                        B_TRANSLATE("While updating package data, a problem has arisen "
                                                "that may cause data to be outdated or missing from the "
                                                "application's display. Additional details regarding this "
                                                "problem may be able to be obtained from the application "
                                                "logs." ALERT_MSG_LOGS_USER_GUIDE)));
        }

        // after the bulk load concludes, if there are no desktop applications
        // and some error did occur during load then the choice to filter by
        // desktop applications should be disabled.

        if (errorStatus != B_OK && !packagesSummary.HasAnyDesktop()
                && fModel.FilterSpecification()->ShowOnlyDesktopPackages()) {
                fModel.SetFilterSpecification(
                        PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                                .WithShowOnlyDesktopPackages(false)
                                .BuildRef());
        }

        // after the bulk load concludes, if there are no native desktop
        // applications and some error did occur during load then the choice to
        // filter by native desktop applications should be disabled.

        if (errorStatus != B_OK && !packagesSummary.HasAnyNativeDesktop()
                && fModel.FilterSpecification()->ShowOnlyNativeDesktopPackages()) {
                HDINFO("no native desktop packages; will disable filter");
                fModel.SetFilterSpecification(
                        PackageFilterSpecificationBuilder(fModel.FilterSpecification())
                                .WithShowOnlyNativeDesktopPackages(false)
                                .BuildRef());
        }

        _AdoptModel();
        _UpdateAvailableRepositories();

        // if after loading everything in, it transpires that there are no
        // featured packages then the featured packages should be disabled
        // and the user should be switched to the "all packages" view so
        // that they are not presented with a blank window!

        // if there are no prominent packages then the disablement of the featured
        // tab is made elsewhere.

        if (!packagesSummary.HasAnyProminentPackages()
                && fListTabs->Selection() == TAB_PROMINENT_PACKAGES) {
                HDINFO("no prominent packages; will display all packages instead");
                fModel.SetPackageListViewMode(ALL);
                fListTabs->Select(TAB_ALL_PACKAGES);
        }
}


void
MainWindow::_NotifyWorkStatusClear()
{
        BMessage message(MSG_WORK_STATUS_CLEAR);
        this->PostMessage(&message, this);
}


void
MainWindow::_HandleWorkStatusClear()
{
        fWorkStatusView->SetText("");
        fWorkStatusView->SetIdle();
}


/*!     Sends off a message to the Window so that it can change the status view
        on the front-end in the UI thread.
*/

void
MainWindow::_NotifyWorkStatusChange(const BString& text, float progress)
{
        BMessage message(MSG_WORK_STATUS_CHANGE);

        if (!text.IsEmpty())
                message.AddString(kKeyWorkStatusText, text);
        message.AddFloat(kKeyWorkStatusProgress, progress);

        this->PostMessage(&message, this);
}


void
MainWindow::_HandleExternalPackageUpdateMessageReceived(const BMessage* message)
{
        BStringList addedPackageNames;
        BStringList removedPackageNames;

        if (message->FindStrings("added package names", &addedPackageNames) == B_OK) {
                addedPackageNames.Sort();
                _SetStateForPackagesByName(addedPackageNames, ACTIVATED);
        } else {
                HDINFO("no [added package names] key in inbound message");
        }

        if (message->FindStrings("removed package names", &removedPackageNames) == B_OK) {
                removedPackageNames.Sort();
                _SetStateForPackagesByName(removedPackageNames, UNINSTALLED);
        } else {
                HDINFO("no [removed package names] key in inbound message");
        }
}


void
MainWindow::_SetStateForPackagesByName(BStringList& packageNames, PackageState state)
{
        std::vector<PackageInfoRef> modifiedPackages;

        for (int32 i = 0; i < packageNames.CountStrings(); i++) {
                BString packageName = packageNames.StringAt(i);
                const PackageInfoRef package = fModel.PackageForName(packageName);

                if (package.IsSet()) {
                        PackageLocalInfoRef localInfo
                                = PackageLocalInfoBuilder(package->LocalInfo()).WithState(state)
                                        .ClearInstallationLocations().BuildRef();

                        modifiedPackages.push_back(
                                PackageInfoBuilder(package).WithLocalInfo(localInfo).BuildRef());

                        HDINFO("will update package [%s] with state [%s]", packageName.String(),
                                PackageUtils::StateToString(state));
                } else {
                        HDINFO("was unable to find package [%s] so was not possible to set the state to [%s]",
                                packageName.String(), PackageUtils::StateToString(state));
                }
        }

        fModel.AddPackagesWithChange(modifiedPackages, PKG_CHANGED_LOCAL_INFO);
}


void
MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message)
{
        if (fWorkStatusView == NULL)
                return;

        BString text;
        float progress;

        if (message->FindString(kKeyWorkStatusText, &text) == B_OK)
                fWorkStatusView->SetText(text);

        if (message->FindFloat(kKeyWorkStatusProgress, &progress) == B_OK) {
                if (progress < 0.0f)
                        fWorkStatusView->SetBusy();
                else
                        fWorkStatusView->SetProgress(progress);
        } else {
                HDERROR("work status change missing progress on update message");
                fWorkStatusView->SetProgress(0.0f);
        }
}


/*! Initially only superficial data is loaded from the server into the data
        model of the packages.  When the package is viewed, additional data needs
        to be populated including ratings.

        This method will cause the package to have its data refreshed from
        the server application.  The refresh happens in the background; this method
        is asynchronous.
*/

void
MainWindow::_PopulatePackageAsync(bool forcePopulate)
{
        const PackageInfoRef package = fPackageInfoView->Package();

        if (!fModel.CanPopulatePackage(package))
                return;

        const char* packageNameStr = package->Name().String();

        PackageLocalizedTextRef localized = package->LocalizedText();
        bool networkAvailable = ServerHelper::IsNetworkAvailable();

        if (localized.IsSet()) {
                if (forcePopulate || localized->Changelog().IsEmpty()) {
                        if (localized->HasChangelog()) {
                                if (networkAvailable) {
                                        _AddProcessCoordinator(
                                                ProcessCoordinatorFactory::PopulatePkgChangelogCoordinator(&fModel,
                                                        package->Name()));
                                        HDINFO("pkg [%s] will have changelog updated from server.", packageNameStr);
                                } else {
                                        HDINFO(
                                                "pkg [%s] will not have changelog updated from server; network unavailable",
                                                packageNameStr);
                                }
                        } else {
                                HDINFO("pkg [%s] does not have a changelog -- won't try fetch it.", packageNameStr);
                        }
                }
        }

        if (forcePopulate || RatingUtils::ShouldTryPopulateUserRatings(package->UserRatingInfo())) {
                if (networkAvailable) {
                        _AddProcessCoordinator(ProcessCoordinatorFactory::PopulatePkgUserRatingsCoordinator(
                                &fModel, package->Name()));
                        HDINFO("pkg [%s] will have user ratings updated from server.", packageNameStr);
                } else {
                        HDINFO("pkg [%s] won't have user ratings updated from server; network unavailable",
                                packageNameStr);
                }
        } else {
                HDDEBUG("pkg [%s] not have user ratings updated from server.", packageNameStr);
        }
}


void
MainWindow::_OpenSettingsWindow()
{
        SettingsWindow* window = new SettingsWindow(this, &fModel);
        window->Show();
}


void
MainWindow::_OpenLoginWindow(const BMessage& onSuccessMessage)
{
        UserLoginWindow* window = new UserLoginWindow(this, BRect(0, 0, 500, 400), fModel);

        if (onSuccessMessage.what != 0)
                window->SetOnSuccessMessage(BMessenger(this), onSuccessMessage);

        window->Show();
}


void
MainWindow::_StartUserVerify()
{
        if (!fModel.Nickname().IsEmpty()) {
                _AddProcessCoordinator(ProcessCoordinatorFactory::CreateUserDetailVerifierCoordinator(this,
                        // UserDetailVerifierListener
                        &fModel));
        }
}


void
MainWindow::_UpdateAuthorization()
{
        BString nickname(fModel.Nickname());
        bool hasUser = !nickname.IsEmpty();

        if (fLogOutItem != NULL)
                fLogOutItem->SetEnabled(hasUser);
        if (fUsersUserUsageConditionsMenuItem != NULL)
                fUsersUserUsageConditionsMenuItem->SetEnabled(hasUser);
        if (fLogInItem != NULL) {
                if (hasUser)
                        fLogInItem->SetLabel(B_TRANSLATE("Switch account" B_UTF8_ELLIPSIS));
                else
                        fLogInItem->SetLabel(B_TRANSLATE("Log in" B_UTF8_ELLIPSIS));
        }

        if (fUserMenu != NULL) {
                BString label;
                if (hasUser) {
                        label = B_TRANSLATE("Logged in as %User%");
                        label.ReplaceAll("%User%", nickname);
                } else {
                        label = B_TRANSLATE("Not logged in");
                }
                fUserMenu->Superitem()->SetLabel(label);
        }
}


void
MainWindow::_UpdateAvailableRepositories()
{
        fRepositoryMenu->RemoveItems(0, fRepositoryMenu->CountItems(), true);

        fRepositoryMenu->AddItem(
                new BMenuItem(B_TRANSLATE("All repositories"), new BMessage(MSG_DEPOT_SELECTED)));

        fRepositoryMenu->AddItem(new BSeparatorItem());

        bool foundSelectedDepot = false;
        std::vector<DepotInfoRef> depots = _CreateSnapshotOfDepots();
        std::vector<DepotInfoRef>::iterator it;

        for (it = depots.begin(); it != depots.end(); it++) {
                DepotInfoRef depot = *it;

                if (depot->Name().Length() != 0) {
                        BMessage* message = new BMessage(MSG_DEPOT_SELECTED);
                        message->AddString(shared_message_keys::kKeyDepotName, depot->Name());
                        BMenuItem* item = new(std::nothrow) BMenuItem(depot->Name(), message);

                        if (item == NULL)
                                HDFATAL("memory exhaustion");

                        fRepositoryMenu->AddItem(item);

                        if (depot->Name() == fModel.FilterSpecification()->DepotName()) {
                                item->SetMarked(true);
                                foundSelectedDepot = true;
                        }
                }
        }

        if (!foundSelectedDepot)
                fRepositoryMenu->ItemAt(0)->SetMarked(true);
}


bool
MainWindow::_SelectedPackageHasWebAppRepositoryCode()
{
        const PackageInfoRef& package = fPackageInfoView->Package();
        const BString depotName = PackageUtils::DepotName(package);

        if (depotName.IsEmpty()) {
                HDDEBUG("the package [%s] has no depot name", package->Name().String());
        } else {
                const DepotInfo* depot = fModel.DepotForName(depotName);

                if (depot == NULL) {
                        HDINFO("the depot [%s] was not able to be found", depotName.String());
                } else {
                        BString repositoryCode = depot->WebAppRepositoryCode();

                        if (repositoryCode.IsEmpty())
                                HDINFO("the depot [%s] has no web app repository code", depotName.String());
                        else
                                return true;
                }
        }

        return false;
}


void
MainWindow::_RatePackage()
{
        if (!_SelectedPackageHasWebAppRepositoryCode()) {
                BAlert* alert = new(std::nothrow) BAlert(B_TRANSLATE("Rating not possible"),
                        B_TRANSLATE("This package doesn't seem to be on the HaikuDepot Server, so it's not "
                                                "possible to create a new rating or edit an existing rating."),
                        B_TRANSLATE("OK"));
                alert->Go();
                return;
        }

        if (fModel.Nickname().IsEmpty()) {
                BAlert* alert = new(std::nothrow) BAlert(B_TRANSLATE("Not logged in"),
                        B_TRANSLATE("You need to be logged into an account before you can rate packages."),
                        B_TRANSLATE("Cancel"), B_TRANSLATE("Login or Create account"));

                if (alert == NULL)
                        return;

                int32 choice = alert->Go();
                if (choice == 1)
                        _OpenLoginWindow(BMessage(MSG_RATE_PACKAGE));
                return;
        }

        // TODO: Allow only one RatePackageWindow
        // TODO: Mechanism for remembering the window frame
        RatePackageWindow* window = new RatePackageWindow(this, BRect(0, 0, 500, 400), fModel);
        window->SetPackage(fPackageInfoView->Package());
        window->Show();
}


void
MainWindow::_ShowScreenshot()
{
        // TODO: Mechanism for remembering the window frame
        if (fScreenshotWindow == NULL)
                fScreenshotWindow = new ScreenshotWindow(this, BRect(0, 0, 500, 400), &fModel);

        if (fScreenshotWindow->LockWithTimeout(1000) != B_OK)
                return;

        fScreenshotWindow->SetPackage(fPackageInfoView->Package());

        if (fScreenshotWindow->IsHidden())
                fScreenshotWindow->Show();
        else
                fScreenshotWindow->Activate();

        fScreenshotWindow->Unlock();
}


void
MainWindow::_ViewUserUsageConditions(UserUsageConditionsSelectionMode mode)
{
        UserUsageConditionsWindow* window = new UserUsageConditionsWindow(fModel, mode);
        window->Show();
}


void
MainWindow::UserCredentialsFailed()
{
        BString message = B_TRANSLATE("The password previously "
                                                                  "supplied for the user [%Nickname%] is not currently "
                                                                  "valid. The user will be logged-out of this application "
                                                                  "and you should login again with your updated password.");
        message.ReplaceAll("%Nickname%", fModel.Nickname());

        AppUtils::NotifySimpleError(SimpleAlert(B_TRANSLATE("Login issue"), message));

        if (IdentityAndAccessUtils::ClearCredentials() != B_OK)
                HDERROR("unable to remove stored credentials");
        fModel.SetCredentials(UserCredentials());
}


/*! \brief This method is invoked from the UserDetailVerifierProcess on a
                   background thread.  For this reason it lodges a message into itself
                   which can then be handled on the main thread.
*/

void
MainWindow::UserUsageConditionsNotLatest(const UserDetail& userDetail)
{
        BMessage message(MSG_USER_USAGE_CONDITIONS_NOT_LATEST);
        BMessage detailsMessage;
        if (userDetail.Archive(&detailsMessage, true) != B_OK
                || message.AddMessage("userDetail", &detailsMessage) != B_OK) {
                HDERROR("unable to archive the user detail into a message");
        } else {
                BMessenger(this).SendMessage(&message);
        }
}


void
MainWindow::_HandleUserUsageConditionsNotLatest(const UserDetail& userDetail)
{
        ToLatestUserUsageConditionsWindow* window
                = new ToLatestUserUsageConditionsWindow(this, fModel, userDetail);
        window->Show();
}


void
MainWindow::_AddProcessCoordinator(ProcessCoordinator* item)
{
        AutoLocker<BLocker> lock(&fCoordinatorLock);

        if (fShouldCloseWhenNoProcessesToCoordinate) {
                HDINFO("system shutting down --> new process coordinator [%s] rejected",
                        item->Name().String());
                return;
        }

        item->SetListener(this);

        if (fCoordinator == NULL) {
                if (acquire_sem(fCoordinatorRunningSem) != B_OK)
                        debugger("unable to acquire the process coordinator sem");
                HDINFO("adding and starting a process coordinator [%s]", item->Name().String());
                delete fCoordinator;
                fCoordinator = item;
                fCoordinator->Start();
        } else {
                HDINFO("adding process coordinator [%s] to the queue", item->Name().String());
                fCoordinatorQueue.push(item);
        }
}


void
MainWindow::_SpinUntilProcessCoordinatorComplete()
{
        while (true) {
                if (acquire_sem(fCoordinatorRunningSem) != B_OK)
                        debugger("unable to acquire the process coordinator sem");
                if (release_sem(fCoordinatorRunningSem) != B_OK)
                        debugger("unable to release the process coordinator sem");
                {
                        AutoLocker<BLocker> lock(&fCoordinatorLock);
                        if (fCoordinator == NULL)
                                return;
                }
        }
}


void
MainWindow::_StopProcessCoordinators()
{
        HDINFO("will stop all queued process coordinators");
        AutoLocker<BLocker> lock(&fCoordinatorLock);

        while (!fCoordinatorQueue.empty()) {
                ProcessCoordinator* processCoordinator = fCoordinatorQueue.front();
                HDINFO("will drop queued process coordinator [%s]", processCoordinator->Name().String());
                fCoordinatorQueue.pop();
                delete processCoordinator;
        }

        if (fCoordinator != NULL)
                fCoordinator->RequestStop();
}


/*! This method is called when there is some change in the bulk load process
        or other process coordinator.
        A change may mean that a new process has started / stopped etc... or it
        may mean that the entire coordinator has finished.
*/

void
MainWindow::CoordinatorChanged(ProcessCoordinatorState& coordinatorState)
{
        BMessage message(MSG_PROCESS_COORDINATOR_CHANGED);
        if (coordinatorState.Archive(&message, true) != B_OK)
                HDFATAL("unable to archive message when the process coordinator has changed");
        BMessenger(this).SendMessage(&message);
}


void
MainWindow::_HandleProcessCoordinatorChanged(ProcessCoordinatorState& coordinatorState)
{
        AutoLocker<BLocker> lock(&fCoordinatorLock);

        if (fCoordinator->Identifier() == coordinatorState.ProcessCoordinatorIdentifier()) {
                if (!coordinatorState.IsRunning()) {
                        if (release_sem(fCoordinatorRunningSem) != B_OK)
                                debugger("unable to release the process coordinator sem");
                        HDINFO("process coordinator [%s] did complete", fCoordinator->Name().String());
                        // complete the last one that just finished
                        BMessage* message = fCoordinator->Message();

                        if (message != NULL) {
                                BMessenger messenger(this);
                                message->AddInt64(KEY_ERROR_STATUS,
                                        static_cast<int64>(fCoordinator->ErrorStatus()));
                                messenger.SendMessage(message);
                        }

                        HDDEBUG("process coordinator report;\n---\n%s\n----",
                                fCoordinator->LogReport().String());

                        delete fCoordinator;
                        fCoordinator = NULL;

                        // now schedule the next one.
                        if (!fCoordinatorQueue.empty()) {
                                if (acquire_sem(fCoordinatorRunningSem) != B_OK)
                                        debugger("unable to acquire the process coordinator sem");
                                fCoordinator = fCoordinatorQueue.front();
                                HDINFO("starting next process coordinator [%s]", fCoordinator->Name().String());
                                fCoordinatorQueue.pop();
                                fCoordinator->Start();
                        } else {
                                _NotifyWorkStatusClear();
                                if (fShouldCloseWhenNoProcessesToCoordinate) {
                                        HDINFO("no more processes to coord --> will quit");
                                        BMessage message(B_QUIT_REQUESTED);
                                        PostMessage(&message);
                                }
                        }
                } else {
                        _NotifyWorkStatusChange(coordinatorState.Message(), coordinatorState.Progress());
                                // show the progress to the user.
                }
        } else {
                _NotifyWorkStatusClear();
                HDINFO("! unknown process coordinator changed");
        }
}


static package_list_view_mode
main_window_tab_to_package_list_view_mode(int32 tab)
{
        if (tab == TAB_PROMINENT_PACKAGES)
                return PROMINENT;
        return ALL;
}


void
MainWindow::_HandleChangePackageListViewMode()
{
        package_list_view_mode tabMode
                = main_window_tab_to_package_list_view_mode(fListTabs->Selection());
        package_list_view_mode modelMode = fModel.PackageListViewMode();

        if (tabMode != modelMode)
                fModel.SetPackageListViewMode(tabMode);
}


/*!     This method will create a vector of packages that are filtered by the filter
        defined in the model.
*/
const std::vector<PackageInfoRef>
MainWindow::_CreateSnapshotOfFilteredPackages()
{
        return fModel.FilteredPackages();
}


std::vector<DepotInfoRef>
MainWindow::_CreateSnapshotOfDepots()
{
        return fModel.Depots();
}


/*!     This method will get invoked in the case that the icons have been
        reloaded. It applies to all packages because the icons are loaded
        all in one go.
*/

void
MainWindow::_HandleIconsChanged()
{
        if (!fSinglePackageMode) {
                fFeaturedPackagesView->HandleIconsChanged();
                fPackageListView->HandleIconsChanged();
        }

        fPackageInfoView->HandleIconsChanged();
}


/*!     This will get invoked in the case that a screenshot has been cached
        and so could now be loaded by some UI element. This method will then
        signal to other UI elements that they could load a screenshot should
        they have been waiting for it.
*/

void
MainWindow::_HandleScreenshotCached(const BMessage* message)
{
        ScreenshotCoordinate coordinate(message);
        fPackageInfoView->HandleScreenshotCached(coordinate);
}


/*static*/ const BString
MainWindow::_WindowTitleForPackage(const PackageInfoRef& pkg)
{
        PackageVersionRef version = PackageUtils::Version(pkg);
        BString versionString = "???";

        if (version.IsSet())
                versionString = version->ToString();

        BString title("%AppName% - %PackageName% %PackageVersion%");
        title.ReplaceFirst("%AppName%", B_TRANSLATE_SYSTEM_NAME("HaikuDepot"));
        title.ReplaceFirst("%PackageName%", pkg->Name());
        title.ReplaceFirst("%PackageVersion%", versionString);

        return title;
}