/*
* Copyright 2007-2014, Haiku, Inc.
* Distributed under the terms of the MIT license.
*
* Author:
* Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "InstalledPackageInfo.h"
#include "PackageImageViewer.h"
#include "PackageTextViewer.h"
#include "PackageView.h"
#include <Alert.h>
#include <Box.h>
#include <Button.h>
#include <Catalog.h>
#include <Directory.h>
#include <FilePanel.h>
#include <FindDirectory.h>
#include <Locale.h>
#include <LayoutBuilder.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <ScrollView.h>
#include <StringForSize.h>
#include <TextView.h>
#include <Volume.h>
#include <VolumeRoster.h>
#include <Window.h>
#include <GroupLayout.h>
#include <GroupLayoutBuilder.h>
#include <GroupView.h>
#include <fs_info.h>
#include <stdio.h> // For debugging
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PackageView"
const float kMaxDescHeight = 125.0f;
const uint32 kSeparatorIndex = 3;
// #pragma mark -
PackageView::PackageView(const entry_ref* ref)
:
BView("package_view", 0),
fOpenPanel(new BFilePanel(B_OPEN_PANEL, NULL, NULL, B_DIRECTORY_NODE,
false)),
fExpectingOpenPanelResult(false),
fInfo(ref),
fInstallProcess(this)
{
_InitView();
// Check whether the package has been successfuly parsed
status_t ret = fInfo.InitCheck();
if (ret == B_OK)
_InitProfiles();
}
PackageView::~PackageView()
{
delete fOpenPanel;
}
void
PackageView::AttachedToWindow()
{
status_t ret = fInfo.InitCheck();
if (ret != B_OK && ret != B_NO_INIT) {
BAlert* warning = new BAlert("parsing_failed",
B_TRANSLATE("The package file is not readable.\nOne of the "
"possible reasons for this might be that the requested file "
"is not a valid BeOS .pkg package."), B_TRANSLATE("OK"),
NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
warning->SetFlags(warning->Flags() | B_CLOSE_ON_ESCAPE);
warning->Go();
Window()->PostMessage(B_QUIT_REQUESTED);
return;
}
// Set the window title
BWindow* parent = Window();
BString title;
BString name = fInfo.GetName();
if (name.CountChars() == 0) {
title = B_TRANSLATE("Package installer");
} else {
title = B_TRANSLATE("Install %name%");
title.ReplaceAll("%name%", name);
}
parent->SetTitle(title.String());
fBeginButton->SetTarget(this);
fOpenPanel->SetTarget(BMessenger(this));
fInstallTypes->SetTargetForItems(this);
if (ret != B_OK)
return;
// If the package is valid, we can set up the default group and all
// other things. If not, then the application will close just after
// attaching the view to the window
_InstallTypeChanged(0);
fStatusWindow = new PackageStatus(B_TRANSLATE("Installation progress"),
NULL, NULL, this);
// Show the splash screen, if present
BMallocIO* image = fInfo.GetSplashScreen();
if (image != NULL) {
PackageImageViewer* imageViewer = new PackageImageViewer(image);
imageViewer->Go();
}
// Show the disclaimer/info text popup, if present
BString disclaimer = fInfo.GetDisclaimer();
if (disclaimer.Length() != 0) {
PackageTextViewer* text = new PackageTextViewer(
disclaimer.String());
int32 selection = text->Go();
// The user didn't accept our disclaimer, this means we cannot
// continue.
if (selection == 0)
parent->Quit();
}
}
void
PackageView::MessageReceived(BMessage* message)
{
switch (message->what) {
case P_MSG_INSTALL:
{
fBeginButton->SetEnabled(false);
fInstallTypes->SetEnabled(false);
fDestination->SetEnabled(false);
fStatusWindow->Show();
fInstallProcess.Start();
break;
}
case P_MSG_PATH_CHANGED:
{
BString path;
if (message->FindString("path", &path) == B_OK)
fCurrentPath.SetTo(path.String());
break;
}
case P_MSG_OPEN_PANEL:
fExpectingOpenPanelResult = true;
fOpenPanel->Show();
break;
case P_MSG_INSTALL_TYPE_CHANGED:
{
int32 index;
if (message->FindInt32("index", &index) == B_OK)
_InstallTypeChanged(index);
break;
}
case P_MSG_I_FINISHED:
{
BAlert* notify = new BAlert("installation_success",
B_TRANSLATE("The package you requested has been successfully "
"installed on your system."),
B_TRANSLATE("OK"));
notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
notify->Go();
fStatusWindow->Hide();
fBeginButton->SetEnabled(true);
fInstallTypes->SetEnabled(true);
fDestination->SetEnabled(true);
fInstallProcess.Stop();
BWindow *parent = Window();
if (parent && parent->Lock())
parent->Quit();
break;
}
case P_MSG_I_ABORT:
{
BAlert* notify = new BAlert("installation_aborted",
B_TRANSLATE(
"The installation of the package has been aborted."),
B_TRANSLATE("OK"));
notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
notify->Go();
fStatusWindow->Hide();
fBeginButton->SetEnabled(true);
fInstallTypes->SetEnabled(true);
fDestination->SetEnabled(true);
fInstallProcess.Stop();
break;
}
case P_MSG_I_ERROR:
{
// TODO: Review this
BAlert* notify = new BAlert("installation_failed",
B_TRANSLATE("The requested package failed to install on your "
"system. This might be a problem with the target package "
"file. Please consult this issue with the package "
"distributor."),
B_TRANSLATE("OK"),
NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
fprintf(stderr,
B_TRANSLATE("Error while installing the package\n"));
notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
notify->Go();
fStatusWindow->Hide();
fBeginButton->SetEnabled(true);
fInstallTypes->SetEnabled(true);
fDestination->SetEnabled(true);
fInstallProcess.Stop();
break;
}
case P_MSG_STOP:
{
// This message is sent to us by the PackageStatus window, informing
// user interruptions.
// We actually use this message only when a post installation script
// is running and we want to kill it while it's still running
fStatusWindow->Hide();
fBeginButton->SetEnabled(true);
fInstallTypes->SetEnabled(true);
fDestination->SetEnabled(true);
fInstallProcess.Stop();
break;
}
case B_REFS_RECEIVED:
{
if (!_ValidateFilePanelMessage(message))
break;
entry_ref ref;
if (message->FindRef("refs", &ref) == B_OK) {
BPath path(&ref);
if (path.InitCheck() != B_OK)
break;
dev_t device = dev_for_path(path.Path());
BVolume volume(device);
if (volume.InitCheck() != B_OK)
break;
BMenuItem* item = fDestField->MenuItem();
BString name = _NamePlusSizeString(path.Path(),
volume.FreeBytes(), B_TRANSLATE("%name% (%size% free)"));
item->SetLabel(name.String());
fCurrentPath.SetTo(path.Path());
}
break;
}
case B_CANCEL:
{
if (!_ValidateFilePanelMessage(message))
break;
// file panel aborted, select first suitable item
for (int32 i = 0; i < fDestination->CountItems(); i++) {
BMenuItem* item = fDestination->ItemAt(i);
BMessage* message = item->Message();
if (message == NULL)
continue;
BString path;
if (message->FindString("path", &path) == B_OK) {
fCurrentPath.SetTo(path.String());
item->SetMarked(true);
break;
}
}
break;
}
case B_SIMPLE_DATA:
if (message->WasDropped()) {
uint32 type;
int32 count;
status_t ret = message->GetInfo("refs", &type, &count);
// check whether the message means someone dropped a file
// to our view
if (ret == B_OK && type == B_REF_TYPE) {
// if it is, send it along with the refs to the application
message->what = B_REFS_RECEIVED;
be_app->PostMessage(message);
}
}
// fall-through
default:
BView::MessageReceived(message);
break;
}
}
int32
PackageView::ItemExists(PackageItem& item, BPath& path, int32& policy)
{
int32 choice = P_EXISTS_NONE;
switch (policy) {
case P_EXISTS_OVERWRITE:
choice = P_EXISTS_OVERWRITE;
break;
case P_EXISTS_SKIP:
choice = P_EXISTS_SKIP;
break;
case P_EXISTS_ASK:
case P_EXISTS_NONE:
{
const char* formatString;
switch (item.ItemKind()) {
case P_KIND_SCRIPT:
formatString = B_TRANSLATE("The script named \'%s\' "
"already exists in the given path.\nReplace the script "
"with the one from this package or skip it?");
break;
case P_KIND_FILE:
formatString = B_TRANSLATE("The file named \'%s\' already "
"exists in the given path.\nReplace the file with the "
"one from this package or skip it?");
break;
case P_KIND_DIRECTORY:
formatString = B_TRANSLATE("The directory named \'%s\' "
"already exists in the given path.\nReplace the "
"directory with one from this package or skip it?");
break;
case P_KIND_SYM_LINK:
formatString = B_TRANSLATE("The symbolic link named \'%s\' "
"already exists in the given path.\nReplace the link "
"with the one from this package or skip it?");
break;
default:
formatString = B_TRANSLATE("The item named \'%s\' already "
"exists in the given path.\nReplace the item with the "
"one from this package or skip it?");
break;
}
char buffer[512];
snprintf(buffer, sizeof(buffer), formatString, path.Leaf());
BString alertString = buffer;
BAlert* alert = new BAlert("file_exists", alertString.String(),
B_TRANSLATE("Replace"),
B_TRANSLATE("Skip"),
B_TRANSLATE("Abort"));
alert->SetShortcut(2, B_ESCAPE);
choice = alert->Go();
switch (choice) {
case 0:
choice = P_EXISTS_OVERWRITE;
break;
case 1:
choice = P_EXISTS_SKIP;
break;
default:
return P_EXISTS_ABORT;
}
if (policy == P_EXISTS_NONE) {
// TODO: Maybe add 'No, but ask again' type of choice as well?
alertString = B_TRANSLATE("Do you want to remember this "
"decision for the rest of this installation?\n");
BString actionString;
if (choice == P_EXISTS_OVERWRITE) {
alertString << B_TRANSLATE(
"All existing files will be replaced?");
actionString = B_TRANSLATE("Replace all");
} else {
alertString << B_TRANSLATE(
"All existing files will be skipped?");
actionString = B_TRANSLATE("Skip all");
}
alert = new BAlert("policy_decision", alertString.String(),
actionString.String(), B_TRANSLATE("Ask again"));
int32 decision = alert->Go();
if (decision == 0)
policy = choice;
else
policy = P_EXISTS_ASK;
}
break;
}
}
return choice;
}
// #pragma mark -
class DescriptionTextView : public BTextView {
public:
DescriptionTextView(const char* name, float minHeight)
:
BTextView(name)
{
SetExplicitMinSize(BSize(B_SIZE_UNSET, minHeight));
}
virtual void AttachedToWindow()
{
BTextView::AttachedToWindow();
_UpdateScrollBarVisibility();
}
virtual void FrameResized(float width, float height)
{
BTextView::FrameResized(width, height);
_UpdateScrollBarVisibility();
}
virtual void Draw(BRect updateRect)
{
BTextView::Draw(updateRect);
_UpdateScrollBarVisibility();
}
private:
void _UpdateScrollBarVisibility()
{
BScrollBar* verticalBar = ScrollBar(B_VERTICAL);
if (verticalBar != NULL) {
float min;
float max;
verticalBar->GetRange(&min, &max);
if (min == max) {
if (!verticalBar->IsHidden(verticalBar))
verticalBar->Hide();
} else {
if (verticalBar->IsHidden(verticalBar))
verticalBar->Show();
}
}
}
};
void
PackageView::_InitView()
{
SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
float fontHeight = be_plain_font->Size();
rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
BTextView* packageDescriptionView = new DescriptionTextView(
"package description", fontHeight * 13);
packageDescriptionView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
packageDescriptionView->SetText(fInfo.GetDescription());
packageDescriptionView->MakeEditable(false);
packageDescriptionView->MakeSelectable(false);
packageDescriptionView->SetFontAndColor(be_plain_font, B_FONT_ALL,
&textColor);
BScrollView* descriptionScrollView = new BScrollView(
"package description scroll view", packageDescriptionView,
0, false, true, B_NO_BORDER);
// Install type menu field
fInstallTypes = new BPopUpMenu(B_TRANSLATE("none"));
BMenuField* installType = new BMenuField("install_type",
B_TRANSLATE("Installation type:"), fInstallTypes);
// Install type description text view
fInstallTypeDescriptionView = new DescriptionTextView(
"install type description", fontHeight * 4);
fInstallTypeDescriptionView->MakeEditable(false);
fInstallTypeDescriptionView->MakeSelectable(false);
fInstallTypeDescriptionView->SetInsets(8, 0, 0, 0);
// Left inset needs to match BMenuField text offset
fInstallTypeDescriptionView->SetText(
B_TRANSLATE("No installation type selected"));
fInstallTypeDescriptionView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
BFont font(be_plain_font);
font.SetSize(ceilf(font.Size() * 0.85));
fInstallTypeDescriptionView->SetFontAndColor(&font, B_FONT_ALL,
&textColor);
BScrollView* installTypeScrollView = new BScrollView(
"install type description scroll view", fInstallTypeDescriptionView,
0, false, true, B_NO_BORDER);
// Destination menu field
fDestination = new BPopUpMenu(B_TRANSLATE("none"));
fDestField = new BMenuField("install_to", B_TRANSLATE("Install to:"),
fDestination);
fBeginButton = new BButton("begin_button", B_TRANSLATE("Begin"),
new BMessage(P_MSG_INSTALL));
BLayoutItem* typeLabelItem = installType->CreateLabelLayoutItem();
BLayoutItem* typeMenuItem = installType->CreateMenuBarLayoutItem();
BLayoutItem* destFieldLabelItem = fDestField->CreateLabelLayoutItem();
BLayoutItem* destFieldMenuItem = fDestField->CreateMenuBarLayoutItem();
float forcedMinWidth = be_plain_font->StringWidth("XXX") * 5;
destFieldMenuItem->SetExplicitMinSize(BSize(forcedMinWidth, B_SIZE_UNSET));
typeMenuItem->SetExplicitMinSize(BSize(forcedMinWidth, B_SIZE_UNSET));
BAlignment labelAlignment(B_ALIGN_RIGHT, B_ALIGN_VERTICAL_UNSET);
typeLabelItem->SetExplicitAlignment(labelAlignment);
destFieldLabelItem->SetExplicitAlignment(labelAlignment);
// Build the layout
BLayoutBuilder::Group<>(this, B_VERTICAL)
.Add(descriptionScrollView)
.AddGrid(B_USE_SMALL_SPACING, B_USE_DEFAULT_SPACING)
.Add(typeLabelItem, 0, 0)
.Add(typeMenuItem, 1, 0)
.Add(installTypeScrollView, 1, 1)
.Add(destFieldLabelItem, 0, 2)
.Add(destFieldMenuItem, 1, 2)
.End()
.AddGroup(B_HORIZONTAL)
.AddGlue()
.Add(fBeginButton)
.End()
.SetInsets(B_USE_DEFAULT_SPACING)
;
fBeginButton->MakeDefault(true);
}
void
PackageView::_InitProfiles()
{
int count = fInfo.GetProfileCount();
if (count > 0) {
// Add the default item
pkg_profile* profile = fInfo.GetProfile(0);
BMenuItem* item = _AddInstallTypeMenuItem(profile->name,
profile->space_needed, 0);
item->SetMarked(true);
fCurrentType = 0;
}
for (int i = 1; i < count; i++) {
pkg_profile* profile = fInfo.GetProfile(i);
if (profile != NULL)
_AddInstallTypeMenuItem(profile->name, profile->space_needed, i);
else
fInstallTypes->AddSeparatorItem();
}
}
status_t
PackageView::_InstallTypeChanged(int32 index)
{
if (index < 0)
return B_ERROR;
// Clear the choice list
for (int32 i = fDestination->CountItems() - 1; i >= 0; i--) {
BMenuItem* item = fDestination->RemoveItem(i);
delete item;
}
fCurrentType = index;
pkg_profile* profile = fInfo.GetProfile(index);
if (profile == NULL)
return B_ERROR;
BString typeDescription = profile->description;
if (typeDescription.IsEmpty())
typeDescription = profile->name;
fInstallTypeDescriptionView->SetText(typeDescription.String());
BPath path;
BVolume volume;
if (profile->path_type == P_INSTALL_PATH) {
BMenuItem* item = NULL;
if (find_directory(B_SYSTEM_NONPACKAGED_DIRECTORY, &path) == B_OK) {
dev_t device = dev_for_path(path.Path());
if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()
&& path.Append("apps") == B_OK) {
item = _AddDestinationMenuItem(path.Path(), volume.FreeBytes(),
path.Path());
}
}
if (item != NULL) {
item->SetMarked(true);
fCurrentPath.SetTo(path.Path());
fDestination->AddSeparatorItem();
}
_AddMenuItem(B_TRANSLATE("Other" B_UTF8_ELLIPSIS),
new BMessage(P_MSG_OPEN_PANEL), fDestination);
fDestField->SetEnabled(true);
} else if (profile->path_type == P_USER_PATH) {
bool defaultPathSet = false;
BVolumeRoster roster;
while (roster.GetNextVolume(&volume) != B_BAD_VALUE) {
BDirectory mountPoint;
if (volume.IsReadOnly() || !volume.IsPersistent()
|| volume.GetRootDirectory(&mountPoint) != B_OK) {
continue;
}
if (path.SetTo(&mountPoint, NULL) != B_OK)
continue;
char volumeName[B_FILE_NAME_LENGTH];
volume.GetName(volumeName);
BMenuItem* item = _AddDestinationMenuItem(volumeName,
volume.FreeBytes(), path.Path());
// The first volume becomes the default element
if (!defaultPathSet) {
item->SetMarked(true);
fCurrentPath.SetTo(path.Path());
defaultPathSet = true;
}
}
fDestField->SetEnabled(true);
} else
fDestField->SetEnabled(false);
return B_OK;
}
BString
PackageView::_NamePlusSizeString(BString baseName, size_t size,
const char* format) const
{
char sizeString[48];
string_for_size(size, sizeString, sizeof(sizeString));
BString name(format);
name.ReplaceAll("%name%", baseName);
name.ReplaceAll("%size%", sizeString);
return name;
}
BMenuItem*
PackageView::_AddInstallTypeMenuItem(BString baseName, size_t size,
int32 index) const
{
BString name = _NamePlusSizeString(baseName, size,
B_TRANSLATE("%name% (%size%)"));
BMessage* message = new BMessage(P_MSG_INSTALL_TYPE_CHANGED);
message->AddInt32("index", index);
return _AddMenuItem(name, message, fInstallTypes);
}
BMenuItem*
PackageView::_AddDestinationMenuItem(BString baseName, size_t size,
const char* path) const
{
BString name = _NamePlusSizeString(baseName, size,
B_TRANSLATE("%name% (%size% free)"));
BMessage* message = new BMessage(P_MSG_PATH_CHANGED);
message->AddString("path", path);
return _AddMenuItem(name, message, fDestination);
}
BMenuItem*
PackageView::_AddMenuItem(const char* name, BMessage* message,
BMenu* menu) const
{
BMenuItem* item = new BMenuItem(name, message);
item->SetTarget(this);
menu->AddItem(item);
return item;
}
bool
PackageView::_ValidateFilePanelMessage(BMessage* message)
{
if (!fExpectingOpenPanelResult)
return false;
fExpectingOpenPanelResult = false;
return true;
}
↑ V773 Visibility scope of the 'installType' pointer was exited without releasing the memory. A memory leak is possible.
↑ V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.
↑ V773 Visibility scope of the 'text' pointer was exited without releasing the memory. A memory leak is possible.
↑ V773 The function was exited without releasing the 'warning' pointer. A memory leak is possible.