BTRFS: Implement BTree::Path and change _Find.
[haiku.git] / src / apps / packageinstaller / PackageView.cpp
blobdfe5b06ddfb92f7d920950a3eea36f20a25d94f4
1 /*
2 * Copyright 2007-2014, Haiku, Inc.
3 * Distributed under the terms of the MIT license.
5 * Author:
6 * Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
7 * Stephan Aßmus <superstippi@gmx.de>
8 */
11 #include "InstalledPackageInfo.h"
12 #include "PackageImageViewer.h"
13 #include "PackageTextViewer.h"
14 #include "PackageView.h"
16 #include <Alert.h>
17 #include <Box.h>
18 #include <Button.h>
19 #include <Catalog.h>
20 #include <Directory.h>
21 #include <FilePanel.h>
22 #include <FindDirectory.h>
23 #include <Locale.h>
24 #include <LayoutBuilder.h>
25 #include <MenuField.h>
26 #include <MenuItem.h>
27 #include <Path.h>
28 #include <PopUpMenu.h>
29 #include <ScrollView.h>
30 #include <StringForSize.h>
31 #include <TextView.h>
32 #include <Volume.h>
33 #include <VolumeRoster.h>
34 #include <Window.h>
36 #include <GroupLayout.h>
37 #include <GroupLayoutBuilder.h>
38 #include <GroupView.h>
40 #include <fs_info.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;
51 // #pragma mark -
54 PackageView::PackageView(const entry_ref* ref)
56 BView("package_view", 0),
57 fOpenPanel(new BFilePanel(B_OPEN_PANEL, NULL, NULL, B_DIRECTORY_NODE,
58 false)),
59 fExpectingOpenPanelResult(false),
60 fInfo(ref),
61 fInstallProcess(this)
63 _InitView();
65 // Check whether the package has been successfuly parsed
66 status_t ret = fInfo.InitCheck();
67 if (ret == B_OK)
68 _InitProfiles();
72 PackageView::~PackageView()
74 delete fOpenPanel;
78 void
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);
89 warning->Go();
91 Window()->PostMessage(B_QUIT_REQUESTED);
92 return;
95 // Set the window title
96 BWindow* parent = Window();
97 BString title;
98 BString name = fInfo.GetName();
99 if (name.CountChars() == 0) {
100 title = B_TRANSLATE("Package installer");
101 } else {
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);
111 if (ret != B_OK)
112 return;
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"),
120 NULL, NULL, this);
122 // Show the splash screen, if present
123 BMallocIO* image = fInfo.GetSplashScreen();
124 if (image != NULL) {
125 PackageImageViewer* imageViewer = new PackageImageViewer(image);
126 imageViewer->Go();
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
136 // continue.
137 if (selection == 0)
138 parent->Quit();
143 void
144 PackageView::MessageReceived(BMessage* message)
146 switch (message->what) {
147 case P_MSG_INSTALL:
149 fBeginButton->SetEnabled(false);
150 fInstallTypes->SetEnabled(false);
151 fDestination->SetEnabled(false);
152 fStatusWindow->Show();
154 fInstallProcess.Start();
155 break;
158 case P_MSG_PATH_CHANGED:
160 BString path;
161 if (message->FindString("path", &path) == B_OK)
162 fCurrentPath.SetTo(path.String());
163 break;
166 case P_MSG_OPEN_PANEL:
167 fExpectingOpenPanelResult = true;
168 fOpenPanel->Show();
169 break;
171 case P_MSG_INSTALL_TYPE_CHANGED:
173 int32 index;
174 if (message->FindInt32("index", &index) == B_OK)
175 _InstallTypeChanged(index);
176 break;
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."),
184 B_TRANSLATE("OK"));
185 notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
187 notify->Go();
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())
196 parent->Quit();
197 break;
200 case P_MSG_I_ABORT:
202 BAlert* notify = new BAlert("installation_aborted",
203 B_TRANSLATE(
204 "The installation of the package has been aborted."),
205 B_TRANSLATE("OK"));
206 notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
207 notify->Go();
208 fStatusWindow->Hide();
209 fBeginButton->SetEnabled(true);
210 fInstallTypes->SetEnabled(true);
211 fDestination->SetEnabled(true);
212 fInstallProcess.Stop();
213 break;
216 case P_MSG_I_ERROR:
218 // TODO: Review this
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 "
223 "distributor."),
224 B_TRANSLATE("OK"),
225 NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
226 fprintf(stderr,
227 B_TRANSLATE("Error while installing the package\n"));
228 notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
229 notify->Go();
230 fStatusWindow->Hide();
231 fBeginButton->SetEnabled(true);
232 fInstallTypes->SetEnabled(true);
233 fDestination->SetEnabled(true);
234 fInstallProcess.Stop();
235 break;
238 case P_MSG_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();
249 break;
252 case B_REFS_RECEIVED:
254 if (!_ValidateFilePanelMessage(message))
255 break;
257 entry_ref ref;
258 if (message->FindRef("refs", &ref) == B_OK) {
259 BPath path(&ref);
260 if (path.InitCheck() != B_OK)
261 break;
263 dev_t device = dev_for_path(path.Path());
264 BVolume volume(device);
265 if (volume.InitCheck() != B_OK)
266 break;
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());
276 break;
279 case B_CANCEL:
281 if (!_ValidateFilePanelMessage(message))
282 break;
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();
288 if (message == NULL)
289 continue;
290 BString path;
291 if (message->FindString("path", &path) == B_OK) {
292 fCurrentPath.SetTo(path.String());
293 item->SetMarked(true);
294 break;
297 break;
300 case B_SIMPLE_DATA:
301 if (message->WasDropped()) {
302 uint32 type;
303 int32 count;
304 status_t ret = message->GetInfo("refs", &type, &count);
305 // check whether the message means someone dropped a file
306 // to our view
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);
313 // fall-through
314 default:
315 BView::MessageReceived(message);
316 break;
321 int32
322 PackageView::ItemExists(PackageItem& item, BPath& path, int32& policy)
324 int32 choice = P_EXISTS_NONE;
326 switch (policy) {
327 case P_EXISTS_OVERWRITE:
328 choice = P_EXISTS_OVERWRITE;
329 break;
331 case P_EXISTS_SKIP:
332 choice = P_EXISTS_SKIP;
333 break;
335 case P_EXISTS_ASK:
336 case P_EXISTS_NONE:
338 const char* formatString;
339 switch (item.ItemKind()) {
340 case P_KIND_SCRIPT:
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?");
344 break;
345 case P_KIND_FILE:
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?");
349 break;
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?");
354 break;
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?");
359 break;
360 default:
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?");
364 break;
366 char buffer[512];
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"),
373 B_TRANSLATE("Skip"),
374 B_TRANSLATE("Abort"));
375 alert->SetShortcut(2, B_ESCAPE);
377 choice = alert->Go();
378 switch (choice) {
379 case 0:
380 choice = P_EXISTS_OVERWRITE;
381 break;
382 case 1:
383 choice = P_EXISTS_SKIP;
384 break;
385 default:
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");
399 } else {
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();
408 if (decision == 0)
409 policy = choice;
410 else
411 policy = P_EXISTS_ASK;
413 break;
417 return choice;
421 // #pragma mark -
424 class DescriptionTextView : public BTextView {
425 public:
426 DescriptionTextView(const char* name, float minHeight)
428 BTextView(name)
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();
451 private:
452 void _UpdateScrollBarVisibility()
454 BScrollBar* verticalBar = ScrollBar(B_VERTICAL);
455 if (verticalBar != NULL) {
456 float min;
457 float max;
458 verticalBar->GetRange(&min, &max);
459 if (min == max) {
460 if (!verticalBar->IsHidden(verticalBar))
461 verticalBar->Hide();
462 } else {
463 if (verticalBar->IsHidden(verticalBar))
464 verticalBar->Show();
471 void
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,
486 &textColor);
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,
510 &textColor);
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:"),
519 fDestination);
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);
538 // Build the layout
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)
547 .End()
548 .AddGroup(B_HORIZONTAL)
549 .AddGlue()
550 .Add(fBeginButton)
551 .End()
552 .SetInsets(B_USE_DEFAULT_SPACING)
555 fBeginButton->MakeDefault(true);
559 void
560 PackageView::_InitProfiles()
562 int count = fInfo.GetProfileCount();
564 if (count > 0) {
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);
570 fCurrentType = 0;
573 for (int i = 1; i < count; i++) {
574 pkg_profile* profile = fInfo.GetProfile(i);
576 if (profile != NULL)
577 _AddInstallTypeMenuItem(profile->name, profile->space_needed, i);
578 else
579 fInstallTypes->AddSeparatorItem();
584 status_t
585 PackageView::_InstallTypeChanged(int32 index)
587 if (index < 0)
588 return B_ERROR;
590 // Clear the choice list
591 for (int32 i = fDestination->CountItems() - 1; i >= 0; i--) {
592 BMenuItem* item = fDestination->RemoveItem(i);
593 delete item;
596 fCurrentType = index;
597 pkg_profile* profile = fInfo.GetProfile(index);
599 if (profile == NULL)
600 return B_ERROR;
602 BString typeDescription = profile->description;
603 if (typeDescription.IsEmpty())
604 typeDescription = profile->name;
606 fInstallTypeDescriptionView->SetText(typeDescription.String());
608 BPath path;
609 BVolume volume;
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(),
618 path.Path());
622 if (item != NULL) {
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) {
640 continue;
643 if (path.SetTo(&mountPoint, NULL) != B_OK)
644 continue;
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);
661 } else
662 fDestField->SetEnabled(false);
664 return B_OK;
668 BString
669 PackageView::_NamePlusSizeString(BString baseName, size_t size,
670 const char* format) const
672 char sizeString[48];
673 string_for_size(size, sizeString, sizeof(sizeString));
675 BString name(format);
676 name.ReplaceAll("%name%", baseName);
677 name.ReplaceAll("%size%", sizeString);
679 return name;
683 BMenuItem*
684 PackageView::_AddInstallTypeMenuItem(BString baseName, size_t size,
685 int32 index) const
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);
697 BMenuItem*
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);
711 BMenuItem*
712 PackageView::_AddMenuItem(const char* name, BMessage* message,
713 BMenu* menu) const
715 BMenuItem* item = new BMenuItem(name, message);
716 item->SetTarget(this);
717 menu->AddItem(item);
718 return item;
722 bool
723 PackageView::_ValidateFilePanelMessage(BMessage* message)
725 if (!fExpectingOpenPanelResult)
726 return false;
728 fExpectingOpenPanelResult = false;
729 return true;