xref: /haiku/src/apps/packageinstaller/PackageView.cpp (revision 81ec973846ea4816c53ed8901822e43c8b06878d)
1 /*
2  * Copyright 2007-2014, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Author:
6  *		Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
7  *		Stephan Aßmus <superstippi@gmx.de>
8  */
9 
10 
11 #include "InstalledPackageInfo.h"
12 #include "PackageImageViewer.h"
13 #include "PackageTextViewer.h"
14 #include "PackageView.h"
15 
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 <TextView.h>
31 #include <Volume.h>
32 #include <VolumeRoster.h>
33 #include <Window.h>
34 
35 #include <GroupLayout.h>
36 #include <GroupLayoutBuilder.h>
37 #include <GroupView.h>
38 
39 #include <fs_info.h>
40 #include <stdio.h> // For debugging
41 
42 
43 #undef B_TRANSLATION_CONTEXT
44 #define B_TRANSLATION_CONTEXT "PackageView"
45 
46 const float kMaxDescHeight = 125.0f;
47 const uint32 kSeparatorIndex = 3;
48 
49 
50 static void
51 convert_size(uint64 size, char *buffer, uint32 n)
52 {
53 	if (size < 1024)
54 		snprintf(buffer, n, B_TRANSLATE("%llu bytes"), size);
55 	else if (size < 1024 * 1024)
56 		snprintf(buffer, n, B_TRANSLATE("%.1f KiB"), size / 1024.0f);
57 	else if (size < 1024 * 1024 * 1024)
58 		snprintf(buffer, n, B_TRANSLATE("%.1f MiB"),
59 			size / (1024.0f * 1024.0f));
60 	else {
61 		snprintf(buffer, n, B_TRANSLATE("%.1f GiB"),
62 			size / (1024.0f * 1024.0f * 1024.0f));
63 	}
64 }
65 
66 
67 
68 // #pragma mark -
69 
70 
71 PackageView::PackageView(const entry_ref* ref)
72 	:
73 	BView("package_view", 0),
74 	fOpenPanel(new BFilePanel(B_OPEN_PANEL, NULL, NULL, B_DIRECTORY_NODE,
75 		false)),
76 	fExpectingOpenPanelResult(false),
77 	fInfo(ref),
78 	fInstallProcess(this)
79 {
80 	_InitView();
81 
82 	// Check whether the package has been successfuly parsed
83 	status_t ret = fInfo.InitCheck();
84 	if (ret == B_OK)
85 		_InitProfiles();
86 }
87 
88 
89 PackageView::~PackageView()
90 {
91 	delete fOpenPanel;
92 }
93 
94 
95 void
96 PackageView::AttachedToWindow()
97 {
98 	status_t ret = fInfo.InitCheck();
99 	if (ret != B_OK && ret != B_NO_INIT) {
100 		BAlert* warning = new BAlert("parsing_failed",
101 				B_TRANSLATE("The package file is not readable.\nOne of the "
102 				"possible reasons for this might be that the requested file "
103 				"is not a valid BeOS .pkg package."), B_TRANSLATE("OK"),
104 				NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
105 		warning->SetFlags(warning->Flags() | B_CLOSE_ON_ESCAPE);
106 		warning->Go();
107 
108 		Window()->PostMessage(B_QUIT_REQUESTED);
109 		return;
110 	}
111 
112 	// Set the window title
113 	BWindow* parent = Window();
114 	BString title;
115 	BString name = fInfo.GetName();
116 	if (name.CountChars() == 0) {
117 		title = B_TRANSLATE("Package installer");
118 	} else {
119 		title = B_TRANSLATE("Install %name%");
120 		title.ReplaceAll("%name%", name);
121 	}
122 	parent->SetTitle(title.String());
123 	fBeginButton->SetTarget(this);
124 
125 	fOpenPanel->SetTarget(BMessenger(this));
126 	fInstallTypes->SetTargetForItems(this);
127 
128 	if (ret != B_OK)
129 		return;
130 
131 	// If the package is valid, we can set up the default group and all
132 	// other things. If not, then the application will close just after
133 	// attaching the view to the window
134 	_InstallTypeChanged(0);
135 
136 	fStatusWindow = new PackageStatus(B_TRANSLATE("Installation progress"),
137 		NULL, NULL, this);
138 
139 	// Show the splash screen, if present
140 	BMallocIO* image = fInfo.GetSplashScreen();
141 	if (image != NULL) {
142 		PackageImageViewer* imageViewer = new PackageImageViewer(image);
143 		imageViewer->Go();
144 	}
145 
146 	// Show the disclaimer/info text popup, if present
147 	BString disclaimer = fInfo.GetDisclaimer();
148 	if (disclaimer.Length() != 0) {
149 		PackageTextViewer* text = new PackageTextViewer(
150 			disclaimer.String());
151 		int32 selection = text->Go();
152 		// The user didn't accept our disclaimer, this means we cannot
153 		// continue.
154 		if (selection == 0)
155 			parent->Quit();
156 	}
157 }
158 
159 
160 void
161 PackageView::MessageReceived(BMessage* message)
162 {
163 	switch (message->what) {
164 		case P_MSG_INSTALL:
165 		{
166 			fBeginButton->SetEnabled(false);
167 			fInstallTypes->SetEnabled(false);
168 			fDestination->SetEnabled(false);
169 			fStatusWindow->Show();
170 
171 			fInstallProcess.Start();
172 			break;
173 		}
174 
175 		case P_MSG_PATH_CHANGED:
176 		{
177 			BString path;
178 			if (message->FindString("path", &path) == B_OK)
179 				fCurrentPath.SetTo(path.String());
180 			break;
181 		}
182 
183 		case P_MSG_OPEN_PANEL:
184 			fExpectingOpenPanelResult = true;
185 			fOpenPanel->Show();
186 			break;
187 
188 		case P_MSG_INSTALL_TYPE_CHANGED:
189 		{
190 			int32 index;
191 			if (message->FindInt32("index", &index) == B_OK)
192 				_InstallTypeChanged(index);
193 			break;
194 		}
195 
196 		case P_MSG_I_FINISHED:
197 		{
198 			BAlert* notify = new BAlert("installation_success",
199 				B_TRANSLATE("The package you requested has been successfully "
200 					"installed on your system."),
201 				B_TRANSLATE("OK"));
202 			notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
203 
204 			notify->Go();
205 			fStatusWindow->Hide();
206 			fBeginButton->SetEnabled(true);
207 			fInstallTypes->SetEnabled(true);
208 			fDestination->SetEnabled(true);
209 			fInstallProcess.Stop();
210 
211 			BWindow *parent = Window();
212 			if (parent && parent->Lock())
213 				parent->Quit();
214 			break;
215 		}
216 
217 		case P_MSG_I_ABORT:
218 		{
219 			BAlert* notify = new BAlert("installation_aborted",
220 				B_TRANSLATE(
221 					"The installation of the package has been aborted."),
222 				B_TRANSLATE("OK"));
223 			notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
224 			notify->Go();
225 			fStatusWindow->Hide();
226 			fBeginButton->SetEnabled(true);
227 			fInstallTypes->SetEnabled(true);
228 			fDestination->SetEnabled(true);
229 			fInstallProcess.Stop();
230 			break;
231 		}
232 
233 		case P_MSG_I_ERROR:
234 		{
235 			// TODO: Review this
236 			BAlert* notify = new BAlert("installation_failed",
237 				B_TRANSLATE("The requested package failed to install on your "
238 					"system. This might be a problem with the target package "
239 					"file. Please consult this issue with the package "
240 					"distributor."),
241 				B_TRANSLATE("OK"),
242 				NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
243 			fprintf(stderr,
244 				B_TRANSLATE("Error while installing the package\n"));
245 			notify->SetFlags(notify->Flags() | B_CLOSE_ON_ESCAPE);
246 			notify->Go();
247 			fStatusWindow->Hide();
248 			fBeginButton->SetEnabled(true);
249 			fInstallTypes->SetEnabled(true);
250 			fDestination->SetEnabled(true);
251 			fInstallProcess.Stop();
252 			break;
253 		}
254 
255 		case P_MSG_STOP:
256 		{
257 			// This message is sent to us by the PackageStatus window, informing
258 			// user interruptions.
259 			// We actually use this message only when a post installation script
260 			// is running and we want to kill it while it's still running
261 			fStatusWindow->Hide();
262 			fBeginButton->SetEnabled(true);
263 			fInstallTypes->SetEnabled(true);
264 			fDestination->SetEnabled(true);
265 			fInstallProcess.Stop();
266 			break;
267 		}
268 
269 		case B_REFS_RECEIVED:
270 		{
271 			if (!_ValidateFilePanelMessage(message))
272 				break;
273 
274 			entry_ref ref;
275 			if (message->FindRef("refs", &ref) == B_OK) {
276 				BPath path(&ref);
277 				if (path.InitCheck() != B_OK)
278 					break;
279 
280 				dev_t device = dev_for_path(path.Path());
281 				BVolume volume(device);
282 				if (volume.InitCheck() != B_OK)
283 					break;
284 
285 				BMenuItem* item = fDestField->MenuItem();
286 
287 				BString name = _NamePlusSizeString(path.Path(),
288 					volume.FreeBytes(), B_TRANSLATE("%name% (%size% free)"));
289 
290 				item->SetLabel(name.String());
291 				fCurrentPath.SetTo(path.Path());
292 			}
293 			break;
294 		}
295 
296 		case B_CANCEL:
297 		{
298 			if (!_ValidateFilePanelMessage(message))
299 				break;
300 
301 			// file panel aborted, select first suitable item
302 			for (int32 i = 0; i < fDestination->CountItems(); i++) {
303 				BMenuItem* item = fDestination->ItemAt(i);
304 				BMessage* message = item->Message();
305 				if (message == NULL)
306 					continue;
307 				BString path;
308 				if (message->FindString("path", &path) == B_OK) {
309 					fCurrentPath.SetTo(path.String());
310 					item->SetMarked(true);
311 					break;
312 				}
313 			}
314 			break;
315 		}
316 
317 		case B_SIMPLE_DATA:
318 			if (message->WasDropped()) {
319 				uint32 type;
320 				int32 count;
321 				status_t ret = message->GetInfo("refs", &type, &count);
322 				// check whether the message means someone dropped a file
323 				// to our view
324 				if (ret == B_OK && type == B_REF_TYPE) {
325 					// if it is, send it along with the refs to the application
326 					message->what = B_REFS_RECEIVED;
327 					be_app->PostMessage(message);
328 				}
329 			}
330 			// fall-through
331 		default:
332 			BView::MessageReceived(message);
333 			break;
334 	}
335 }
336 
337 
338 int32
339 PackageView::ItemExists(PackageItem& item, BPath& path, int32& policy)
340 {
341 	int32 choice = P_EXISTS_NONE;
342 
343 	switch (policy) {
344 		case P_EXISTS_OVERWRITE:
345 			choice = P_EXISTS_OVERWRITE;
346 			break;
347 
348 		case P_EXISTS_SKIP:
349 			choice = P_EXISTS_SKIP;
350 			break;
351 
352 		case P_EXISTS_ASK:
353 		case P_EXISTS_NONE:
354 		{
355 			const char* formatString;
356 			switch (item.ItemKind()) {
357 				case P_KIND_SCRIPT:
358 					formatString = B_TRANSLATE("The script named \'%s\' "
359 						"already exits in the given path.\nReplace the script "
360 						"with the one from this package or skip it?");
361 					break;
362 				case P_KIND_FILE:
363 					formatString = B_TRANSLATE("The file named \'%s\' already "
364 						"exits in the given path.\nReplace the file with the "
365 						"one from this package or skip it?");
366 					break;
367 				case P_KIND_DIRECTORY:
368 					formatString = B_TRANSLATE("The directory named \'%s\' "
369 						"already exits in the given path.\nReplace the "
370 						"directory with one from this package or skip it?");
371 					break;
372 				case P_KIND_SYM_LINK:
373 					formatString = B_TRANSLATE("The symbolic link named \'%s\' "
374 						"already exists in the given path.\nReplace the link "
375 						"with the one from this package or skip it?");
376 					break;
377 				default:
378 					formatString = B_TRANSLATE("The item named \'%s\' already "
379 						"exits in the given path.\nReplace the item with the "
380 						"one from this package or skip it?");
381 					break;
382 			}
383 			char buffer[512];
384 			snprintf(buffer, sizeof(buffer), formatString, path.Leaf());
385 
386 			BString alertString = buffer;
387 
388 			BAlert* alert = new BAlert("file_exists", alertString.String(),
389 				B_TRANSLATE("Replace"),
390 				B_TRANSLATE("Skip"),
391 				B_TRANSLATE("Abort"));
392 			alert->SetShortcut(2, B_ESCAPE);
393 
394 			choice = alert->Go();
395 			switch (choice) {
396 				case 0:
397 					choice = P_EXISTS_OVERWRITE;
398 					break;
399 				case 1:
400 					choice = P_EXISTS_SKIP;
401 					break;
402 				default:
403 					return P_EXISTS_ABORT;
404 			}
405 
406 			if (policy == P_EXISTS_NONE) {
407 				// TODO: Maybe add 'No, but ask again' type of choice as well?
408 				alertString = B_TRANSLATE("Do you want to remember this "
409 					"decision for the rest of this installation?\n");
410 
411 				BString actionString;
412 				if (choice == P_EXISTS_OVERWRITE) {
413 					alertString << B_TRANSLATE(
414 						"All existing files will be replaced?");
415 					actionString = B_TRANSLATE("Replace all");
416 				} else {
417 					alertString << B_TRANSLATE(
418 						"All existing files will be skipped?");
419 					actionString = B_TRANSLATE("Skip all");
420 				}
421 				alert = new BAlert("policy_decision", alertString.String(),
422 					actionString.String(), B_TRANSLATE("Ask again"));
423 
424 				int32 decision = alert->Go();
425 				if (decision == 0)
426 					policy = choice;
427 				else
428 					policy = P_EXISTS_ASK;
429 			}
430 			break;
431 		}
432 	}
433 
434 	return choice;
435 }
436 
437 
438 // #pragma mark -
439 
440 
441 class DescriptionTextView : public BTextView {
442 public:
443 	DescriptionTextView(const char* name, float minHeight)
444 		:
445 		BTextView(name)
446 	{
447 		SetExplicitMinSize(BSize(B_SIZE_UNSET, minHeight));
448 	}
449 
450 	virtual void AttachedToWindow()
451 	{
452 		BTextView::AttachedToWindow();
453 		_UpdateScrollBarVisibility();
454 	}
455 
456 	virtual void FrameResized(float width, float height)
457 	{
458 		BTextView::FrameResized(width, height);
459 		_UpdateScrollBarVisibility();
460 	}
461 
462 	virtual void Draw(BRect updateRect)
463 	{
464 		BTextView::Draw(updateRect);
465 		_UpdateScrollBarVisibility();
466 	}
467 
468 private:
469 	void _UpdateScrollBarVisibility()
470 	{
471 		BScrollBar* verticalBar = ScrollBar(B_VERTICAL);
472 		if (verticalBar != NULL) {
473 			float min;
474 			float max;
475 			verticalBar->GetRange(&min, &max);
476 			if (min == max) {
477 				if (!verticalBar->IsHidden(verticalBar))
478 					verticalBar->Hide();
479 			} else {
480 				if (verticalBar->IsHidden(verticalBar))
481 					verticalBar->Show();
482 			}
483 		}
484 	}
485 };
486 
487 
488 void
489 PackageView::_InitView()
490 {
491 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
492 
493 	float fontHeight = be_plain_font->Size();
494 
495 	BTextView* packageDescriptionView = new DescriptionTextView(
496 		"package description", fontHeight * 13);
497 	packageDescriptionView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
498 	packageDescriptionView->SetText(fInfo.GetDescription());
499 	packageDescriptionView->MakeEditable(false);
500 	packageDescriptionView->MakeSelectable(false);
501 
502 	BScrollView* descriptionScrollView = new BScrollView(
503 		"package description scroll view", packageDescriptionView,
504 		0, false, true, B_NO_BORDER);
505 
506 	// Install type menu field
507 	fInstallTypes = new BPopUpMenu(B_TRANSLATE("none"));
508 	BMenuField* installType = new BMenuField("install_type",
509 		B_TRANSLATE("Installation type:"), fInstallTypes);
510 
511 	// Install type description text view
512 	fInstallTypeDescriptionView = new DescriptionTextView(
513 		"install type description", fontHeight * 4);
514 	fInstallTypeDescriptionView->MakeEditable(false);
515 	fInstallTypeDescriptionView->MakeSelectable(false);
516 	fInstallTypeDescriptionView->SetInsets(8, 0, 0, 0);
517 		// Left inset needs to match BMenuField text offset
518 	fInstallTypeDescriptionView->SetText(
519 		B_TRANSLATE("No installation type selected"));
520 	fInstallTypeDescriptionView->SetViewColor(
521 		ui_color(B_PANEL_BACKGROUND_COLOR));
522 	BFont font(be_plain_font);
523 	font.SetSize(ceilf(font.Size() * 0.85));
524 	fInstallTypeDescriptionView->SetFontAndColor(&font);
525 
526 	BScrollView* installTypeScrollView = new BScrollView(
527 		"install type description scroll view", fInstallTypeDescriptionView,
528 		 0, false, true, B_NO_BORDER);
529 
530 	// Destination menu field
531 	fDestination = new BPopUpMenu(B_TRANSLATE("none"));
532 	fDestField = new BMenuField("install_to", B_TRANSLATE("Install to:"),
533 		fDestination);
534 
535 	fBeginButton = new BButton("begin_button", B_TRANSLATE("Begin"),
536 		new BMessage(P_MSG_INSTALL));
537 
538 	BLayoutItem* typeLabelItem = installType->CreateLabelLayoutItem();
539 	BLayoutItem* typeMenuItem = installType->CreateMenuBarLayoutItem();
540 
541 	BLayoutItem* destFieldLabelItem = fDestField->CreateLabelLayoutItem();
542 	BLayoutItem* destFieldMenuItem = fDestField->CreateMenuBarLayoutItem();
543 
544 	float forcedMinWidth = be_plain_font->StringWidth("XXX") * 5;
545 	destFieldMenuItem->SetExplicitMinSize(BSize(forcedMinWidth, B_SIZE_UNSET));
546 	typeMenuItem->SetExplicitMinSize(BSize(forcedMinWidth, B_SIZE_UNSET));
547 
548 	BAlignment labelAlignment(B_ALIGN_RIGHT, B_ALIGN_VERTICAL_UNSET);
549 	typeLabelItem->SetExplicitAlignment(labelAlignment);
550 	destFieldLabelItem->SetExplicitAlignment(labelAlignment);
551 
552 	// Build the layout
553 	BLayoutBuilder::Group<>(this, B_VERTICAL)
554 		.Add(descriptionScrollView)
555 		.AddGrid(B_USE_SMALL_SPACING, B_USE_DEFAULT_SPACING)
556 			.Add(typeLabelItem, 0, 0)
557 			.Add(typeMenuItem, 1, 0)
558 			.Add(installTypeScrollView, 1, 1)
559 			.Add(destFieldLabelItem, 0, 2)
560 			.Add(destFieldMenuItem, 1, 2)
561 		.End()
562 		.AddGroup(B_HORIZONTAL)
563 			.AddGlue()
564 			.Add(fBeginButton)
565 		.End()
566 		.SetInsets(B_USE_DEFAULT_SPACING)
567 	;
568 
569 	fBeginButton->MakeDefault(true);
570 }
571 
572 
573 void
574 PackageView::_InitProfiles()
575 {
576 	int count = fInfo.GetProfileCount();
577 
578 	if (count > 0) {
579 		// Add the default item
580 		pkg_profile* profile = fInfo.GetProfile(0);
581 		BMenuItem* item = _AddInstallTypeMenuItem(profile->name,
582 			profile->space_needed, 0);
583 		item->SetMarked(true);
584 		fCurrentType = 0;
585 	}
586 
587 	for (int i = 1; i < count; i++) {
588 		pkg_profile* profile = fInfo.GetProfile(i);
589 
590 		if (profile != NULL)
591 			_AddInstallTypeMenuItem(profile->name, profile->space_needed, i);
592 		else
593 			fInstallTypes->AddSeparatorItem();
594 	}
595 }
596 
597 
598 status_t
599 PackageView::_InstallTypeChanged(int32 index)
600 {
601 	if (index < 0)
602 		return B_ERROR;
603 
604 	// Clear the choice list
605 	for (int32 i = fDestination->CountItems() - 1; i >= 0; i--) {
606 		BMenuItem* item = fDestination->RemoveItem(i);
607 		delete item;
608 	}
609 
610 	fCurrentType = index;
611 	pkg_profile* profile = fInfo.GetProfile(index);
612 
613 	if (profile == NULL)
614 		return B_ERROR;
615 
616 	BString typeDescription = profile->description;
617 	if (typeDescription.IsEmpty())
618 		typeDescription = profile->name;
619 
620 	fInstallTypeDescriptionView->SetText(typeDescription.String());
621 
622 	BPath path;
623 	BVolume volume;
624 
625 	if (profile->path_type == P_INSTALL_PATH) {
626 		BMenuItem* item = NULL;
627 		if (find_directory(B_SYSTEM_NONPACKAGED_DIRECTORY, &path) == B_OK) {
628 			dev_t device = dev_for_path(path.Path());
629 			if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()
630 				&& path.Append("apps") == B_OK) {
631 				item = _AddDestinationMenuItem(path.Path(), volume.FreeBytes(),
632 					path.Path());
633 			}
634 		}
635 
636 		if (item != NULL) {
637 			item->SetMarked(true);
638 			fCurrentPath.SetTo(path.Path());
639 			fDestination->AddSeparatorItem();
640 		}
641 
642 		_AddMenuItem(B_TRANSLATE("Other" B_UTF8_ELLIPSIS),
643 			new BMessage(P_MSG_OPEN_PANEL), fDestination);
644 
645 		fDestField->SetEnabled(true);
646 	} else if (profile->path_type == P_USER_PATH) {
647 		bool defaultPathSet = false;
648 		BVolumeRoster roster;
649 
650 		while (roster.GetNextVolume(&volume) != B_BAD_VALUE) {
651 			BDirectory mountPoint;
652 			if (volume.IsReadOnly() || !volume.IsPersistent()
653 				|| volume.GetRootDirectory(&mountPoint) != B_OK) {
654 				continue;
655 			}
656 
657 			if (path.SetTo(&mountPoint, NULL) != B_OK)
658 				continue;
659 
660 			char volumeName[B_FILE_NAME_LENGTH];
661 			volume.GetName(volumeName);
662 
663 			BMenuItem* item = _AddDestinationMenuItem(volumeName,
664 				volume.FreeBytes(), path.Path());
665 
666 			// The first volume becomes the default element
667 			if (!defaultPathSet) {
668 				item->SetMarked(true);
669 				fCurrentPath.SetTo(path.Path());
670 				defaultPathSet = true;
671 			}
672 		}
673 
674 		fDestField->SetEnabled(true);
675 	} else
676 		fDestField->SetEnabled(false);
677 
678 	return B_OK;
679 }
680 
681 
682 BString
683 PackageView::_NamePlusSizeString(BString baseName, size_t size,
684 	const char* format) const
685 {
686 	char sizeString[48];
687 	convert_size(size, sizeString, sizeof(sizeString));
688 
689 	BString name(format);
690 	name.ReplaceAll("%name%", baseName);
691 	name.ReplaceAll("%size%", sizeString);
692 
693 	return name;
694 }
695 
696 
697 BMenuItem*
698 PackageView::_AddInstallTypeMenuItem(BString baseName, size_t size,
699 	int32 index) const
700 {
701 	BString name = _NamePlusSizeString(baseName, size,
702 		B_TRANSLATE("%name% (%size%)"));
703 
704 	BMessage* message = new BMessage(P_MSG_INSTALL_TYPE_CHANGED);
705 	message->AddInt32("index", index);
706 
707 	return _AddMenuItem(name, message, fInstallTypes);
708 }
709 
710 
711 BMenuItem*
712 PackageView::_AddDestinationMenuItem(BString baseName, size_t size,
713 	const char* path) const
714 {
715 	BString name = _NamePlusSizeString(baseName, size,
716 		B_TRANSLATE("%name% (%size% free)"));
717 
718 	BMessage* message = new BMessage(P_MSG_PATH_CHANGED);
719 	message->AddString("path", path);
720 
721 	return _AddMenuItem(name, message, fDestination);
722 }
723 
724 
725 BMenuItem*
726 PackageView::_AddMenuItem(const char* name, BMessage* message,
727 	BMenu* menu) const
728 {
729 	BMenuItem* item = new BMenuItem(name, message);
730 	item->SetTarget(this);
731 	menu->AddItem(item);
732 	return item;
733 }
734 
735 
736 bool
737 PackageView::_ValidateFilePanelMessage(BMessage* message)
738 {
739 	if (!fExpectingOpenPanelResult)
740 		return false;
741 
742 	fExpectingOpenPanelResult = false;
743 	return true;
744 }
745