* 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-2024, 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 <map>
#include <vector>
#include <stdio.h>
#include <Alert.h>
#include <Autolock.h>
#include <Application.h>
#include <Button.h>
#include <Catalog.h>
#include <CardLayout.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 <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 "LocaleUtils.h"
#include "Logger.h"
#include "PackageInfoView.h"
#include "PackageListView.h"
#include "PackageManager.h"
#include "ProcessCoordinator.h"
#include "ProcessCoordinatorFactory.h"
#include "RatePackageWindow.h"
#include "ScreenshotWindow.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_CATEGORIES_LIST_CHANGED = 'clic',
MSG_PACKAGE_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_AVAILABLE_PACKAGES = 'savl',
MSG_SHOW_INSTALLED_PACKAGES = 'sins',
MSG_SHOW_SOURCE_PACKAGES = 'ssrc',
MSG_SHOW_DEVELOP_PACKAGES = 'sdvl'
};
#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 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);
}
}
private:
BMessenger fMessenger;
};
class MainWindowPackageInfoListener : public PackageInfoListener {
public:
MainWindowPackageInfoListener(MainWindow* mainWindow)
:
fMainWindow(mainWindow)
{
}
~MainWindowPackageInfoListener()
{
}
private:
virtual void PackageChanged(const PackageInfoEvent& event)
{
fMainWindow->PackageChanged(event);
}
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);
BMessage columnSettings;
if (settings.FindMessage("column settings", &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);
BPackageRoster().StartWatching(this,
B_WATCH_PACKAGE_INSTALLATION_LOCATIONS);
_AdoptModel();
_StartBulkLoad();
}
viewing an HPKG file.
*/
MainWindow::MainWindow(const BMessage& settings, 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)
{
BString title = B_TRANSLATE("HaikuDepot - %PackageName% %PackageVersion%");
title.ReplaceAll("%PackageName%", package->Name());
title.ReplaceAll("%PackageVersion%", package->Version().ToString());
SetTitle(title);
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);
DepotInfoRef depot(new DepotInfo("single-pkg-depot"), true);
depot->AddPackage(package);
fModel.MergeOrAddDepot(depot);
_RestoreNickname(settings);
_UpdateAuthorization();
_RestoreWindowFrame(settings);
fPackageInfoView->SetPackage(package);
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();
}
fModel.Clear();
}
bool
MainWindow::QuitRequested()
{
_StopProcessCoordinators();
{
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(KEY_WINDOW_SETTINGS, &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((status_t) errorStatus64);
else
HDERROR("expected [%s] value in message", KEY_ERROR_STATUS);
break;
}
case B_SIMPLE_DATA:
case B_REFS_RECEIVED:
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:
fModel.SetNickname("");
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:
_StartUserVerify();
_UpdateAuthorization();
break;
case MSG_SCREENSHOT_CACHED:
_HandleScreenshotCached(message);
break;
case MSG_CATEGORIES_LIST_CHANGED:
fFilterView->AdoptModel(fModel);
break;
case MSG_CHANGE_PACKAGE_LIST_VIEW_MODE:
_HandleChangePackageListViewMode();
break;
case MSG_SHOW_AVAILABLE_PACKAGES:
{
BAutolock locker(fModel.Lock());
PackageFilterModel* filterModel = fModel.PackageFilter();
filterModel->SetShowAvailablePackages(!filterModel->ShowAvailablePackages());
}
_AdoptModel();
break;
case MSG_SHOW_INSTALLED_PACKAGES:
{
BAutolock locker(fModel.Lock());
PackageFilterModel* filterModel = fModel.PackageFilter();
filterModel->SetShowInstalledPackages(!filterModel->ShowInstalledPackages());
}
_AdoptModel();
break;
case MSG_SHOW_SOURCE_PACKAGES:
{
BAutolock locker(fModel.Lock());
PackageFilterModel* filterModel = fModel.PackageFilter();
filterModel->SetShowSourcePackages(!filterModel->ShowSourcePackages());
}
_AdoptModel();
break;
case MSG_SHOW_DEVELOP_PACKAGES:
{
BAutolock locker(fModel.Lock());
PackageFilterModel* filterModel = fModel.PackageFilter();
filterModel->SetShowDevelopPackages(!filterModel->ShowDevelopPackages());
}
_AdoptModel();
break;
case MSG_SERVER_DATA_CHANGED:
{
BString name;
if (message->FindString("name", &name) == B_OK) {
BAutolock locker(fModel.Lock());
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("name", &name) == B_OK) {
PackageInfoRef package;
{
BAutolock locker(fModel.Lock());
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("code", &code) != B_OK)
code = "";
{
BAutolock locker(fModel.Lock());
fModel.PackageFilter()->SetCategory(code);
}
_AdoptModel();
break;
}
case MSG_DEPOT_SELECTED:
{
BString name;
if (message->FindString("name", &name) != B_OK)
name = "";
{
BAutolock locker(fModel.Lock());
fModel.PackageFilter()->SetDepotName(name);
}
_AdoptModel();
_UpdateAvailableRepositories();
break;
}
case MSG_SEARCH_TERMS_MODIFIED:
{
BString searchTerms;
if (message->FindString("search terms", &searchTerms) != B_OK)
searchTerms = "";
{
BAutolock locker(fModel.Lock());
fModel.PackageFilter()->SetSearchTerms(searchTerms);
}
_AdoptModel();
break;
}
case MSG_PACKAGE_CHANGED:
{
PackageInfo* info;
if (message->FindPointer("package", (void**)&info) == B_OK) {
PackageInfoRef ref(info, true);
fFeaturedPackagesView->BeginAddRemove();
_AddRemovePackageFromLists(ref);
fFeaturedPackagesView->EndAddRemove();
}
break;
}
case MSG_PROCESS_COORDINATOR_CHANGED:
{
ProcessCoordinatorState state(message);
_HandleProcessCoordinatorChanged(state);
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;
}
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("window frame", Frame());
BMessage columnSettings;
if (fPackageListView != NULL)
fPackageListView->SaveState(&columnSettings);
settings.AddMessage("column settings", &columnSettings);
settings.AddString(SETTING_PACKAGE_LIST_VIEW_MODE,
main_window_package_list_view_mode_str(
fModel.PackageListViewMode()));
settings.AddBool(SETTING_SHOW_AVAILABLE_PACKAGES,
fModel.PackageFilter()->ShowAvailablePackages());
settings.AddBool(SETTING_SHOW_INSTALLED_PACKAGES,
fModel.PackageFilter()->ShowInstalledPackages());
settings.AddBool(SETTING_SHOW_DEVELOP_PACKAGES,
fModel.PackageFilter()->ShowDevelopPackages());
settings.AddBool(SETTING_SHOW_SOURCE_PACKAGES,
fModel.PackageFilter()->ShowSourcePackages());
settings.AddBool(SETTING_CAN_SHARE_ANONYMOUS_USER_DATA,
fModel.CanShareAnonymousUsageData());
}
settings.AddString("username", fModel.Nickname());
}
void
MainWindow::Consume(ProcessCoordinator *item)
{
_AddProcessCoordinator(item);
}
void
MainWindow::PackageChanged(const PackageInfoEvent& event)
{
uint32 watchedChanges = PKG_CHANGED_STATE | PKG_CHANGED_PROMINENCE;
if ((event.Changes() & watchedChanges) != 0) {
PackageInfoRef ref(event.Package());
BMessage message(MSG_PACKAGE_CHANGED);
message.AddPointer("package", ref.Get());
ref.Detach();
PostMessage(&message);
}
}
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"));
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("username", &nickname) == B_OK
&& nickname.Length() > 0) {
fModel.SetNickname(nickname);
}
}
const char*
MainWindow::_WindowFrameName() const
{
if (fSinglePackageMode)
return "small window frame";
return "window frame";
}
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) {
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;
if (settings.FindBool(SETTING_SHOW_AVAILABLE_PACKAGES, &showOption) == B_OK)
fModel.PackageFilter()->SetShowAvailablePackages(showOption);
if (settings.FindBool(SETTING_SHOW_INSTALLED_PACKAGES, &showOption) == B_OK)
fModel.PackageFilter()->SetShowInstalledPackages(showOption);
if (settings.FindBool(SETTING_SHOW_DEVELOP_PACKAGES, &showOption) == B_OK)
fModel.PackageFilter()->SetShowDevelopPackages(showOption);
if (settings.FindBool(SETTING_SHOW_SOURCE_PACKAGES, &showOption) == B_OK)
fModel.PackageFilter()->SetShowSourcePackages(showOption);
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);
}
void
MainWindow::_InitPreferredLanguage()
{
LanguageRepository* repository = fModel.Languages();
LanguageRef defaultLanguage = LocaleUtils::DeriveDefaultLanguage(repository);
repository->AddLanguage(defaultLanguage);
fModel.SetPreferredLanguage(defaultLanguage);
}
void
MainWindow::_AdoptModelControls()
{
if (fSinglePackageMode)
return;
BAutolock locker(fModel.Lock());
fShowAvailablePackagesItem->SetMarked(fModel.PackageFilter()->ShowAvailablePackages());
fShowInstalledPackagesItem->SetMarked(fModel.PackageFilter()->ShowInstalledPackages());
fShowSourcePackagesItem->SetMarked(fModel.PackageFilter()->ShowSourcePackages());
fShowDevelopPackagesItem->SetMarked(fModel.PackageFilter()->ShowDevelopPackages());
if (fModel.PackageListViewMode() == PROMINENT)
fListTabs->Select(TAB_PROMINENT_PACKAGES);
else
fListTabs->Select(TAB_ALL_PACKAGES);
fFilterView->AdoptModel(fModel);
}
void
MainWindow::_AdoptModel()
{
HDTRACE("adopting model to main window ui");
if (fSinglePackageMode)
return;
std::vector<DepotInfoRef> depots = _CreateSnapshotOfDepots();
std::vector<DepotInfoRef>::iterator it;
fFeaturedPackagesView->BeginAddRemove();
for (it = depots.begin(); it != depots.end(); it++) {
DepotInfoRef depotInfoRef = *it;
for (int i = 0; i < depotInfoRef->CountPackages(); i++) {
PackageInfoRef package = depotInfoRef->PackageAtIndex(i);
_AddRemovePackageFromLists(package);
}
}
fFeaturedPackagesView->EndAddRemove();
_AdoptModelControls();
}
void
MainWindow::_AddRemovePackageFromLists(const PackageInfoRef& package)
{
bool matches;
{
AutoLocker<BLocker> modelLocker(fModel.Lock());
matches = fModel.PackageFilter()->Filter()->AcceptsPackage(package);
}
if (matches) {
if (package->IsProminent())
fFeaturedPackagesView->AddPackage(package);
fPackageListView->AddPackage(package);
} else {
fFeaturedPackagesView->RemovePackage(package);
fPackageListView->RemovePackage(package);
}
}
void
MainWindow::_SetupDelayedIncrementViewCounter(const PackageInfoRef package) {
if (fIncrementViewCounterDelayedRunner != NULL) {
fIncrementViewCounterDelayedRunner->SetCount(0);
delete fIncrementViewCounterDelayedRunner;
}
BMessage message(MSG_INCREMENT_VIEW_COUNTER);
message.SetString("name", 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("name", &name) == B_OK) {
const PackageInfoRef& viewedPackage =
fPackageInfoView->Package();
if (viewedPackage.IsSet()) {
if (viewedPackage->Name() == name)
_IncrementViewCounter(viewedPackage);
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 PackageInfoRef package)
{
bool shouldIncrementViewCounter = false;
{
AutoLocker<BLocker> modelLocker(fModel.Lock());
bool canShareAnonymousUsageData = fModel.CanShareAnonymousUsageData();
if (canShareAnonymousUsageData && !package->Viewed()) {
package->SetViewed();
shouldIncrementViewCounter = true;
}
}
if (shouldIncrementViewCounter) {
ProcessCoordinator* incrementViewCoordinator =
ProcessCoordinatorFactory::CreateIncrementViewCounter(
&fModel, package);
_AddProcessCoordinator(incrementViewCoordinator);
}
}
void
MainWindow::_AdoptPackage(const PackageInfoRef& package)
{
{
BAutolock locker(fModel.Lock());
fPackageInfoView->SetPackage(package);
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 (fFeaturedPackagesView != NULL)
fFeaturedPackagesView->Clear();
if (fPackageListView != NULL)
fPackageListView->Clear();
fPackageInfoView->Clear();
fRefreshRepositoriesItem->SetEnabled(false);
ProcessCoordinator* bulkLoadCoordinator =
ProcessCoordinatorFactory::CreateBulkLoadCoordinator(
fPackageInfoListener, &fModel, force);
_AddProcessCoordinator(bulkLoadCoordinator);
}
void
MainWindow::_BulkLoadCompleteReceived(status_t errorStatus)
{
if (errorStatus != B_OK) {
AppUtils::NotifySimpleError(
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));
}
fRefreshRepositoriesItem->SetEnabled(true);
_AdoptModel();
_UpdateAvailableRepositories();
bool hasProminentPackages = fModel.HasAnyProminentPackages();
fListTabs->TabAt(TAB_PROMINENT_PACKAGES)->SetEnabled(hasProminentPackages);
if (!hasProminentPackages
&& fListTabs->Selection() == TAB_PROMINENT_PACKAGES) {
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();
}
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(KEY_WORK_STATUS_TEXT, text);
message.AddFloat(KEY_WORK_STATUS_PROGRESS, 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();
AutoLocker<BLocker> locker(fModel.Lock());
fModel.SetStateForPackagesByName(addedPackageNames, ACTIVATED);
}
else
HDINFO("no [added package names] key in inbound message");
if (message->FindStrings("removed package names",
&removedPackageNames) == B_OK) {
removedPackageNames.Sort();
AutoLocker<BLocker> locker(fModel.Lock());
fModel.SetStateForPackagesByName(addedPackageNames, UNINSTALLED);
} else
HDINFO("no [removed package names] key in inbound message");
}
void
MainWindow::_HandleWorkStatusChangeMessageReceived(const BMessage* message)
{
if (fWorkStatusView == NULL)
return;
BString text;
float progress;
if (message->FindString(KEY_WORK_STATUS_TEXT, &text) == B_OK)
fWorkStatusView->SetText(text);
if (message->FindFloat(KEY_WORK_STATUS_PROGRESS, &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);
}
}
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();
if (package->HasChangelog() && (forcePopulate || package->Changelog().IsEmpty())) {
_AddProcessCoordinator(
ProcessCoordinatorFactory::PopulatePkgChangelogCoordinator(&fModel, package));
HDINFO("pkg [%s] will have changelog updated from server.", packageNameStr);
} else {
HDDEBUG("pkg [%s] not have changelog updated from server.", packageNameStr);
}
if (forcePopulate || !package->DidPopulateUserRatings()) {
_AddProcessCoordinator(
ProcessCoordinatorFactory::PopulatePkgUserRatingsCoordinator(&fModel, package));
HDINFO("pkg [%s] will have user ratings updated from server.", 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,
&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("name", depot->Name());
BMenuItem* item = new(std::nothrow) BMenuItem(depot->Name(), message);
if (item == NULL)
HDFATAL("memory exhaustion");
fRepositoryMenu->AddItem(item);
if (depot->Name() == fModel.PackageFilter()->DepotName()) {
item->SetMarked(true);
foundSelectedDepot = true;
}
}
}
if (!foundSelectedDepot)
fRepositoryMenu->ItemAt(0)->SetMarked(true);
}
bool
MainWindow::_SelectedPackageHasWebAppRepositoryCode()
{
const PackageInfoRef& package = fPackageInfoView->Package();
const BString depotName = package->DepotName();
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;
}
RatePackageWindow* window = new RatePackageWindow(this,
BRect(0, 0, 500, 400), fModel);
window->SetPackage(fPackageInfoView->Package());
window->Show();
}
void
MainWindow::_ShowScreenshot()
{
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(B_TRANSLATE("Login issue"),
message);
{
AutoLocker<BLocker> locker(fModel.Lock());
fModel.SetNickname("");
}
}
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();
}
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());
BMessage* message = fCoordinator->Message();
if (message != NULL) {
BMessenger messenger(this);
message->AddInt64(KEY_ERROR_STATUS,
(int64) fCoordinator->ErrorStatus());
messenger.SendMessage(message);
}
HDDEBUG("process coordinator report;\n---\n%s\n----",
fCoordinator->LogReport().String());
delete fCoordinator;
fCoordinator = NULL;
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());
}
} 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) {
BAutolock locker(fModel.Lock());
fModel.SetPackageListViewMode(tabMode);
}
}
std::vector<DepotInfoRef>
MainWindow::_CreateSnapshotOfDepots()
{
std::vector<DepotInfoRef> result;
BAutolock locker(fModel.Lock());
int32 countDepots = fModel.CountDepots();
for(int32 i = 0; i < countDepots; i++)
result.push_back(fModel.DepotAtIndex(i));
return result;
}
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);
}