2 * Copyright 2007-2014, Haiku, Inc.
3 * Distributed under the terms of the MIT license.
6 * Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
7 * Stephan Aßmus <superstippi@gmx.de>
11 #include "InstalledPackageInfo.h"
12 #include "PackageImageViewer.h"
13 #include "PackageTextViewer.h"
14 #include "PackageView.h"
20 #include <Directory.h>
21 #include <FilePanel.h>
22 #include <FindDirectory.h>
24 #include <LayoutBuilder.h>
25 #include <MenuField.h>
28 #include <PopUpMenu.h>
29 #include <ScrollView.h>
30 #include <StringForSize.h>
33 #include <VolumeRoster.h>
36 #include <GroupLayout.h>
37 #include <GroupLayoutBuilder.h>
38 #include <GroupView.h>
41 #include <stdio.h> // For debugging
44 #undef B_TRANSLATION_CONTEXT
45 #define B_TRANSLATION_CONTEXT "PackageView"
47 const float kMaxDescHeight
= 125.0f
;
48 const uint32 kSeparatorIndex
= 3;
54 PackageView::PackageView(const entry_ref
* ref
)
56 BView("package_view", 0),
57 fOpenPanel(new BFilePanel(B_OPEN_PANEL
, NULL
, NULL
, B_DIRECTORY_NODE
,
59 fExpectingOpenPanelResult(false),
65 // Check whether the package has been successfuly parsed
66 status_t ret
= fInfo
.InitCheck();
72 PackageView::~PackageView()
79 PackageView::AttachedToWindow()
81 status_t ret
= fInfo
.InitCheck();
82 if (ret
!= B_OK
&& ret
!= B_NO_INIT
) {
83 BAlert
* warning
= new BAlert("parsing_failed",
84 B_TRANSLATE("The package file is not readable.\nOne of the "
85 "possible reasons for this might be that the requested file "
86 "is not a valid BeOS .pkg package."), B_TRANSLATE("OK"),
87 NULL
, NULL
, B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
88 warning
->SetFlags(warning
->Flags() | B_CLOSE_ON_ESCAPE
);
91 Window()->PostMessage(B_QUIT_REQUESTED
);
95 // Set the window title
96 BWindow
* parent
= Window();
98 BString name
= fInfo
.GetName();
99 if (name
.CountChars() == 0) {
100 title
= B_TRANSLATE("Package installer");
102 title
= B_TRANSLATE("Install %name%");
103 title
.ReplaceAll("%name%", name
);
105 parent
->SetTitle(title
.String());
106 fBeginButton
->SetTarget(this);
108 fOpenPanel
->SetTarget(BMessenger(this));
109 fInstallTypes
->SetTargetForItems(this);
114 // If the package is valid, we can set up the default group and all
115 // other things. If not, then the application will close just after
116 // attaching the view to the window
117 _InstallTypeChanged(0);
119 fStatusWindow
= new PackageStatus(B_TRANSLATE("Installation progress"),
122 // Show the splash screen, if present
123 BMallocIO
* image
= fInfo
.GetSplashScreen();
125 PackageImageViewer
* imageViewer
= new PackageImageViewer(image
);
129 // Show the disclaimer/info text popup, if present
130 BString disclaimer
= fInfo
.GetDisclaimer();
131 if (disclaimer
.Length() != 0) {
132 PackageTextViewer
* text
= new PackageTextViewer(
133 disclaimer
.String());
134 int32 selection
= text
->Go();
135 // The user didn't accept our disclaimer, this means we cannot
144 PackageView::MessageReceived(BMessage
* message
)
146 switch (message
->what
) {
149 fBeginButton
->SetEnabled(false);
150 fInstallTypes
->SetEnabled(false);
151 fDestination
->SetEnabled(false);
152 fStatusWindow
->Show();
154 fInstallProcess
.Start();
158 case P_MSG_PATH_CHANGED
:
161 if (message
->FindString("path", &path
) == B_OK
)
162 fCurrentPath
.SetTo(path
.String());
166 case P_MSG_OPEN_PANEL
:
167 fExpectingOpenPanelResult
= true;
171 case P_MSG_INSTALL_TYPE_CHANGED
:
174 if (message
->FindInt32("index", &index
) == B_OK
)
175 _InstallTypeChanged(index
);
179 case P_MSG_I_FINISHED
:
181 BAlert
* notify
= new BAlert("installation_success",
182 B_TRANSLATE("The package you requested has been successfully "
183 "installed on your system."),
185 notify
->SetFlags(notify
->Flags() | B_CLOSE_ON_ESCAPE
);
188 fStatusWindow
->Hide();
189 fBeginButton
->SetEnabled(true);
190 fInstallTypes
->SetEnabled(true);
191 fDestination
->SetEnabled(true);
192 fInstallProcess
.Stop();
194 BWindow
*parent
= Window();
195 if (parent
&& parent
->Lock())
202 BAlert
* notify
= new BAlert("installation_aborted",
204 "The installation of the package has been aborted."),
206 notify
->SetFlags(notify
->Flags() | B_CLOSE_ON_ESCAPE
);
208 fStatusWindow
->Hide();
209 fBeginButton
->SetEnabled(true);
210 fInstallTypes
->SetEnabled(true);
211 fDestination
->SetEnabled(true);
212 fInstallProcess
.Stop();
219 BAlert
* notify
= new BAlert("installation_failed",
220 B_TRANSLATE("The requested package failed to install on your "
221 "system. This might be a problem with the target package "
222 "file. Please consult this issue with the package "
225 NULL
, NULL
, B_WIDTH_AS_USUAL
, B_WARNING_ALERT
);
227 B_TRANSLATE("Error while installing the package\n"));
228 notify
->SetFlags(notify
->Flags() | B_CLOSE_ON_ESCAPE
);
230 fStatusWindow
->Hide();
231 fBeginButton
->SetEnabled(true);
232 fInstallTypes
->SetEnabled(true);
233 fDestination
->SetEnabled(true);
234 fInstallProcess
.Stop();
240 // This message is sent to us by the PackageStatus window, informing
241 // user interruptions.
242 // We actually use this message only when a post installation script
243 // is running and we want to kill it while it's still running
244 fStatusWindow
->Hide();
245 fBeginButton
->SetEnabled(true);
246 fInstallTypes
->SetEnabled(true);
247 fDestination
->SetEnabled(true);
248 fInstallProcess
.Stop();
252 case B_REFS_RECEIVED
:
254 if (!_ValidateFilePanelMessage(message
))
258 if (message
->FindRef("refs", &ref
) == B_OK
) {
260 if (path
.InitCheck() != B_OK
)
263 dev_t device
= dev_for_path(path
.Path());
264 BVolume
volume(device
);
265 if (volume
.InitCheck() != B_OK
)
268 BMenuItem
* item
= fDestField
->MenuItem();
270 BString name
= _NamePlusSizeString(path
.Path(),
271 volume
.FreeBytes(), B_TRANSLATE("%name% (%size% free)"));
273 item
->SetLabel(name
.String());
274 fCurrentPath
.SetTo(path
.Path());
281 if (!_ValidateFilePanelMessage(message
))
284 // file panel aborted, select first suitable item
285 for (int32 i
= 0; i
< fDestination
->CountItems(); i
++) {
286 BMenuItem
* item
= fDestination
->ItemAt(i
);
287 BMessage
* message
= item
->Message();
291 if (message
->FindString("path", &path
) == B_OK
) {
292 fCurrentPath
.SetTo(path
.String());
293 item
->SetMarked(true);
301 if (message
->WasDropped()) {
304 status_t ret
= message
->GetInfo("refs", &type
, &count
);
305 // check whether the message means someone dropped a file
307 if (ret
== B_OK
&& type
== B_REF_TYPE
) {
308 // if it is, send it along with the refs to the application
309 message
->what
= B_REFS_RECEIVED
;
310 be_app
->PostMessage(message
);
315 BView::MessageReceived(message
);
322 PackageView::ItemExists(PackageItem
& item
, BPath
& path
, int32
& policy
)
324 int32 choice
= P_EXISTS_NONE
;
327 case P_EXISTS_OVERWRITE
:
328 choice
= P_EXISTS_OVERWRITE
;
332 choice
= P_EXISTS_SKIP
;
338 const char* formatString
;
339 switch (item
.ItemKind()) {
341 formatString
= B_TRANSLATE("The script named \'%s\' "
342 "already exits in the given path.\nReplace the script "
343 "with the one from this package or skip it?");
346 formatString
= B_TRANSLATE("The file named \'%s\' already "
347 "exits in the given path.\nReplace the file with the "
348 "one from this package or skip it?");
350 case P_KIND_DIRECTORY
:
351 formatString
= B_TRANSLATE("The directory named \'%s\' "
352 "already exits in the given path.\nReplace the "
353 "directory with one from this package or skip it?");
355 case P_KIND_SYM_LINK
:
356 formatString
= B_TRANSLATE("The symbolic link named \'%s\' "
357 "already exists in the given path.\nReplace the link "
358 "with the one from this package or skip it?");
361 formatString
= B_TRANSLATE("The item named \'%s\' already "
362 "exits in the given path.\nReplace the item with the "
363 "one from this package or skip it?");
367 snprintf(buffer
, sizeof(buffer
), formatString
, path
.Leaf());
369 BString alertString
= buffer
;
371 BAlert
* alert
= new BAlert("file_exists", alertString
.String(),
372 B_TRANSLATE("Replace"),
374 B_TRANSLATE("Abort"));
375 alert
->SetShortcut(2, B_ESCAPE
);
377 choice
= alert
->Go();
380 choice
= P_EXISTS_OVERWRITE
;
383 choice
= P_EXISTS_SKIP
;
386 return P_EXISTS_ABORT
;
389 if (policy
== P_EXISTS_NONE
) {
390 // TODO: Maybe add 'No, but ask again' type of choice as well?
391 alertString
= B_TRANSLATE("Do you want to remember this "
392 "decision for the rest of this installation?\n");
394 BString actionString
;
395 if (choice
== P_EXISTS_OVERWRITE
) {
396 alertString
<< B_TRANSLATE(
397 "All existing files will be replaced?");
398 actionString
= B_TRANSLATE("Replace all");
400 alertString
<< B_TRANSLATE(
401 "All existing files will be skipped?");
402 actionString
= B_TRANSLATE("Skip all");
404 alert
= new BAlert("policy_decision", alertString
.String(),
405 actionString
.String(), B_TRANSLATE("Ask again"));
407 int32 decision
= alert
->Go();
411 policy
= P_EXISTS_ASK
;
424 class DescriptionTextView
: public BTextView
{
426 DescriptionTextView(const char* name
, float minHeight
)
430 SetExplicitMinSize(BSize(B_SIZE_UNSET
, minHeight
));
433 virtual void AttachedToWindow()
435 BTextView::AttachedToWindow();
436 _UpdateScrollBarVisibility();
439 virtual void FrameResized(float width
, float height
)
441 BTextView::FrameResized(width
, height
);
442 _UpdateScrollBarVisibility();
445 virtual void Draw(BRect updateRect
)
447 BTextView::Draw(updateRect
);
448 _UpdateScrollBarVisibility();
452 void _UpdateScrollBarVisibility()
454 BScrollBar
* verticalBar
= ScrollBar(B_VERTICAL
);
455 if (verticalBar
!= NULL
) {
458 verticalBar
->GetRange(&min
, &max
);
460 if (!verticalBar
->IsHidden(verticalBar
))
463 if (verticalBar
->IsHidden(verticalBar
))
472 PackageView::_InitView()
474 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
476 float fontHeight
= be_plain_font
->Size();
477 rgb_color textColor
= ui_color(B_PANEL_TEXT_COLOR
);
479 BTextView
* packageDescriptionView
= new DescriptionTextView(
480 "package description", fontHeight
* 13);
481 packageDescriptionView
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
482 packageDescriptionView
->SetText(fInfo
.GetDescription());
483 packageDescriptionView
->MakeEditable(false);
484 packageDescriptionView
->MakeSelectable(false);
485 packageDescriptionView
->SetFontAndColor(be_plain_font
, B_FONT_ALL
,
488 BScrollView
* descriptionScrollView
= new BScrollView(
489 "package description scroll view", packageDescriptionView
,
490 0, false, true, B_NO_BORDER
);
492 // Install type menu field
493 fInstallTypes
= new BPopUpMenu(B_TRANSLATE("none"));
494 BMenuField
* installType
= new BMenuField("install_type",
495 B_TRANSLATE("Installation type:"), fInstallTypes
);
497 // Install type description text view
498 fInstallTypeDescriptionView
= new DescriptionTextView(
499 "install type description", fontHeight
* 4);
500 fInstallTypeDescriptionView
->MakeEditable(false);
501 fInstallTypeDescriptionView
->MakeSelectable(false);
502 fInstallTypeDescriptionView
->SetInsets(8, 0, 0, 0);
503 // Left inset needs to match BMenuField text offset
504 fInstallTypeDescriptionView
->SetText(
505 B_TRANSLATE("No installation type selected"));
506 fInstallTypeDescriptionView
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
507 BFont
font(be_plain_font
);
508 font
.SetSize(ceilf(font
.Size() * 0.85));
509 fInstallTypeDescriptionView
->SetFontAndColor(&font
, B_FONT_ALL
,
512 BScrollView
* installTypeScrollView
= new BScrollView(
513 "install type description scroll view", fInstallTypeDescriptionView
,
514 0, false, true, B_NO_BORDER
);
516 // Destination menu field
517 fDestination
= new BPopUpMenu(B_TRANSLATE("none"));
518 fDestField
= new BMenuField("install_to", B_TRANSLATE("Install to:"),
521 fBeginButton
= new BButton("begin_button", B_TRANSLATE("Begin"),
522 new BMessage(P_MSG_INSTALL
));
524 BLayoutItem
* typeLabelItem
= installType
->CreateLabelLayoutItem();
525 BLayoutItem
* typeMenuItem
= installType
->CreateMenuBarLayoutItem();
527 BLayoutItem
* destFieldLabelItem
= fDestField
->CreateLabelLayoutItem();
528 BLayoutItem
* destFieldMenuItem
= fDestField
->CreateMenuBarLayoutItem();
530 float forcedMinWidth
= be_plain_font
->StringWidth("XXX") * 5;
531 destFieldMenuItem
->SetExplicitMinSize(BSize(forcedMinWidth
, B_SIZE_UNSET
));
532 typeMenuItem
->SetExplicitMinSize(BSize(forcedMinWidth
, B_SIZE_UNSET
));
534 BAlignment
labelAlignment(B_ALIGN_RIGHT
, B_ALIGN_VERTICAL_UNSET
);
535 typeLabelItem
->SetExplicitAlignment(labelAlignment
);
536 destFieldLabelItem
->SetExplicitAlignment(labelAlignment
);
539 BLayoutBuilder::Group
<>(this, B_VERTICAL
)
540 .Add(descriptionScrollView
)
541 .AddGrid(B_USE_SMALL_SPACING
, B_USE_DEFAULT_SPACING
)
542 .Add(typeLabelItem
, 0, 0)
543 .Add(typeMenuItem
, 1, 0)
544 .Add(installTypeScrollView
, 1, 1)
545 .Add(destFieldLabelItem
, 0, 2)
546 .Add(destFieldMenuItem
, 1, 2)
548 .AddGroup(B_HORIZONTAL
)
552 .SetInsets(B_USE_DEFAULT_SPACING
)
555 fBeginButton
->MakeDefault(true);
560 PackageView::_InitProfiles()
562 int count
= fInfo
.GetProfileCount();
565 // Add the default item
566 pkg_profile
* profile
= fInfo
.GetProfile(0);
567 BMenuItem
* item
= _AddInstallTypeMenuItem(profile
->name
,
568 profile
->space_needed
, 0);
569 item
->SetMarked(true);
573 for (int i
= 1; i
< count
; i
++) {
574 pkg_profile
* profile
= fInfo
.GetProfile(i
);
577 _AddInstallTypeMenuItem(profile
->name
, profile
->space_needed
, i
);
579 fInstallTypes
->AddSeparatorItem();
585 PackageView::_InstallTypeChanged(int32 index
)
590 // Clear the choice list
591 for (int32 i
= fDestination
->CountItems() - 1; i
>= 0; i
--) {
592 BMenuItem
* item
= fDestination
->RemoveItem(i
);
596 fCurrentType
= index
;
597 pkg_profile
* profile
= fInfo
.GetProfile(index
);
602 BString typeDescription
= profile
->description
;
603 if (typeDescription
.IsEmpty())
604 typeDescription
= profile
->name
;
606 fInstallTypeDescriptionView
->SetText(typeDescription
.String());
611 if (profile
->path_type
== P_INSTALL_PATH
) {
612 BMenuItem
* item
= NULL
;
613 if (find_directory(B_SYSTEM_NONPACKAGED_DIRECTORY
, &path
) == B_OK
) {
614 dev_t device
= dev_for_path(path
.Path());
615 if (volume
.SetTo(device
) == B_OK
&& !volume
.IsReadOnly()
616 && path
.Append("apps") == B_OK
) {
617 item
= _AddDestinationMenuItem(path
.Path(), volume
.FreeBytes(),
623 item
->SetMarked(true);
624 fCurrentPath
.SetTo(path
.Path());
625 fDestination
->AddSeparatorItem();
628 _AddMenuItem(B_TRANSLATE("Other" B_UTF8_ELLIPSIS
),
629 new BMessage(P_MSG_OPEN_PANEL
), fDestination
);
631 fDestField
->SetEnabled(true);
632 } else if (profile
->path_type
== P_USER_PATH
) {
633 bool defaultPathSet
= false;
634 BVolumeRoster roster
;
636 while (roster
.GetNextVolume(&volume
) != B_BAD_VALUE
) {
637 BDirectory mountPoint
;
638 if (volume
.IsReadOnly() || !volume
.IsPersistent()
639 || volume
.GetRootDirectory(&mountPoint
) != B_OK
) {
643 if (path
.SetTo(&mountPoint
, NULL
) != B_OK
)
646 char volumeName
[B_FILE_NAME_LENGTH
];
647 volume
.GetName(volumeName
);
649 BMenuItem
* item
= _AddDestinationMenuItem(volumeName
,
650 volume
.FreeBytes(), path
.Path());
652 // The first volume becomes the default element
653 if (!defaultPathSet
) {
654 item
->SetMarked(true);
655 fCurrentPath
.SetTo(path
.Path());
656 defaultPathSet
= true;
660 fDestField
->SetEnabled(true);
662 fDestField
->SetEnabled(false);
669 PackageView::_NamePlusSizeString(BString baseName
, size_t size
,
670 const char* format
) const
673 string_for_size(size
, sizeString
, sizeof(sizeString
));
675 BString
name(format
);
676 name
.ReplaceAll("%name%", baseName
);
677 name
.ReplaceAll("%size%", sizeString
);
684 PackageView::_AddInstallTypeMenuItem(BString baseName
, size_t size
,
687 BString name
= _NamePlusSizeString(baseName
, size
,
688 B_TRANSLATE("%name% (%size%)"));
690 BMessage
* message
= new BMessage(P_MSG_INSTALL_TYPE_CHANGED
);
691 message
->AddInt32("index", index
);
693 return _AddMenuItem(name
, message
, fInstallTypes
);
698 PackageView::_AddDestinationMenuItem(BString baseName
, size_t size
,
699 const char* path
) const
701 BString name
= _NamePlusSizeString(baseName
, size
,
702 B_TRANSLATE("%name% (%size% free)"));
704 BMessage
* message
= new BMessage(P_MSG_PATH_CHANGED
);
705 message
->AddString("path", path
);
707 return _AddMenuItem(name
, message
, fDestination
);
712 PackageView::_AddMenuItem(const char* name
, BMessage
* message
,
715 BMenuItem
* item
= new BMenuItem(name
, message
);
716 item
->SetTarget(this);
723 PackageView::_ValidateFilePanelMessage(BMessage
* message
)
725 if (!fExpectingOpenPanelResult
)
728 fExpectingOpenPanelResult
= false;