xref: /haiku/src/apps/packageinstaller/PackageView.cpp (revision fb4fd84f4dcd4ee910143eb46c600fe2b842d142)
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 <StringForSize.h>
31 #include <TextView.h>
32 #include <Volume.h>
33 #include <VolumeRoster.h>
34 #include <Window.h>
35 
36 #include <GroupLayout.h>
37 #include <GroupLayoutBuilder.h>
38 #include <GroupView.h>
39 
40 #include <fs_info.h>
41 #include <stdio.h> // For debugging
42 
43 
44 #undef B_TRANSLATION_CONTEXT
45 #define B_TRANSLATION_CONTEXT "PackageView"
46 
47 const float kMaxDescHeight = 125.0f;
48 const uint32 kSeparatorIndex = 3;
49 
50 
51 // #pragma mark -
52 
53 
PackageView(const entry_ref * ref)54 PackageView::PackageView(const entry_ref* ref)
55 	:
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)
62 {
63 	_InitView();
64 
65 	// Check whether the package has been successfuly parsed
66 	status_t ret = fInfo.InitCheck();
67 	if (ret == B_OK)
68 		_InitProfiles();
69 }
70 
71 
~PackageView()72 PackageView::~PackageView()
73 {
74 	delete fOpenPanel;
75 }
76 
77 
78 void
AttachedToWindow()79 PackageView::AttachedToWindow()
80 {
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();
90 
91 		Window()->PostMessage(B_QUIT_REQUESTED);
92 		return;
93 	}
94 
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);
104 	}
105 	parent->SetTitle(title.String());
106 	fBeginButton->SetTarget(this);
107 
108 	fOpenPanel->SetTarget(BMessenger(this));
109 	fInstallTypes->SetTargetForItems(this);
110 
111 	if (ret != B_OK)
112 		return;
113 
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);
118 
119 	fStatusWindow = new PackageStatus(B_TRANSLATE("Installation progress"),
120 		NULL, NULL, this);
121 
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();
127 	}
128 
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();
139 	}
140 }
141 
142 
143 void
MessageReceived(BMessage * message)144 PackageView::MessageReceived(BMessage* message)
145 {
146 	switch (message->what) {
147 		case P_MSG_INSTALL:
148 		{
149 			fBeginButton->SetEnabled(false);
150 			fInstallTypes->SetEnabled(false);
151 			fDestination->SetEnabled(false);
152 			fStatusWindow->Show();
153 
154 			fInstallProcess.Start();
155 			break;
156 		}
157 
158 		case P_MSG_PATH_CHANGED:
159 		{
160 			BString path;
161 			if (message->FindString("path", &path) == B_OK)
162 				fCurrentPath.SetTo(path.String());
163 			break;
164 		}
165 
166 		case P_MSG_OPEN_PANEL:
167 			fExpectingOpenPanelResult = true;
168 			fOpenPanel->Show();
169 			break;
170 
171 		case P_MSG_INSTALL_TYPE_CHANGED:
172 		{
173 			int32 index;
174 			if (message->FindInt32("index", &index) == B_OK)
175 				_InstallTypeChanged(index);
176 			break;
177 		}
178 
179 		case P_MSG_I_FINISHED:
180 		{
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);
186 
187 			notify->Go();
188 			fStatusWindow->Hide();
189 			fBeginButton->SetEnabled(true);
190 			fInstallTypes->SetEnabled(true);
191 			fDestination->SetEnabled(true);
192 			fInstallProcess.Stop();
193 
194 			BWindow *parent = Window();
195 			if (parent && parent->Lock())
196 				parent->Quit();
197 			break;
198 		}
199 
200 		case P_MSG_I_ABORT:
201 		{
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;
214 		}
215 
216 		case P_MSG_I_ERROR:
217 		{
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 			fputs(B_TRANSLATE("Error while installing the package\n"),
227 				stderr);
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;
236 		}
237 
238 		case P_MSG_STOP:
239 		{
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;
250 		}
251 
252 		case B_REFS_RECEIVED:
253 		{
254 			if (!_ValidateFilePanelMessage(message))
255 				break;
256 
257 			entry_ref ref;
258 			if (message->FindRef("refs", &ref) == B_OK) {
259 				BPath path(&ref);
260 				if (path.InitCheck() != B_OK)
261 					break;
262 
263 				dev_t device = dev_for_path(path.Path());
264 				BVolume volume(device);
265 				if (volume.InitCheck() != B_OK)
266 					break;
267 
268 				BMenuItem* item = fDestField->MenuItem();
269 
270 				BString name = _NamePlusSizeString(path.Path(),
271 					volume.FreeBytes(), B_TRANSLATE("%name% (%size% free)"));
272 
273 				item->SetLabel(name.String());
274 				fCurrentPath.SetTo(path.Path());
275 			}
276 			break;
277 		}
278 
279 		case B_CANCEL:
280 		{
281 			if (!_ValidateFilePanelMessage(message))
282 				break;
283 
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;
295 				}
296 			}
297 			break;
298 		}
299 
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);
311 				}
312 			}
313 			// fall-through
314 		default:
315 			BView::MessageReceived(message);
316 			break;
317 	}
318 }
319 
320 
321 int32
ItemExists(PackageItem & item,BPath & path,int32 & policy)322 PackageView::ItemExists(PackageItem& item, BPath& path, int32& policy)
323 {
324 	int32 choice = P_EXISTS_NONE;
325 
326 	switch (policy) {
327 		case P_EXISTS_OVERWRITE:
328 			choice = P_EXISTS_OVERWRITE;
329 			break;
330 
331 		case P_EXISTS_SKIP:
332 			choice = P_EXISTS_SKIP;
333 			break;
334 
335 		case P_EXISTS_ASK:
336 		case P_EXISTS_NONE:
337 		{
338 			const char* formatString;
339 			switch (item.ItemKind()) {
340 				case P_KIND_SCRIPT:
341 					formatString = B_TRANSLATE("The script named \'%s\' "
342 						"already exists 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 						"exists 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 exists 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 						"exists in the given path.\nReplace the item with the "
363 						"one from this package or skip it?");
364 					break;
365 			}
366 			char buffer[512];
367 			snprintf(buffer, sizeof(buffer), formatString, path.Leaf());
368 
369 			BString alertString = buffer;
370 
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);
376 
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;
387 			}
388 
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");
393 
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");
403 				}
404 				alert = new BAlert("policy_decision", alertString.String(),
405 					actionString.String(), B_TRANSLATE("Ask again"));
406 
407 				int32 decision = alert->Go();
408 				if (decision == 0)
409 					policy = choice;
410 				else
411 					policy = P_EXISTS_ASK;
412 			}
413 			break;
414 		}
415 	}
416 
417 	return choice;
418 }
419 
420 
421 // #pragma mark -
422 
423 
424 class DescriptionTextView : public BTextView {
425 public:
DescriptionTextView(const char * name,float minHeight)426 	DescriptionTextView(const char* name, float minHeight)
427 		:
428 		BTextView(name)
429 	{
430 		SetExplicitMinSize(BSize(B_SIZE_UNSET, minHeight));
431 	}
432 
AttachedToWindow()433 	virtual void AttachedToWindow()
434 	{
435 		BTextView::AttachedToWindow();
436 		_UpdateScrollBarVisibility();
437 	}
438 
FrameResized(float width,float height)439 	virtual void FrameResized(float width, float height)
440 	{
441 		BTextView::FrameResized(width, height);
442 		_UpdateScrollBarVisibility();
443 	}
444 
Draw(BRect updateRect)445 	virtual void Draw(BRect updateRect)
446 	{
447 		BTextView::Draw(updateRect);
448 		_UpdateScrollBarVisibility();
449 	}
450 
451 private:
_UpdateScrollBarVisibility()452 	void _UpdateScrollBarVisibility()
453 	{
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();
465 			}
466 		}
467 	}
468 };
469 
470 
471 void
_InitView()472 PackageView::_InitView()
473 {
474 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
475 
476 	float fontHeight = be_plain_font->Size();
477 	rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
478 
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);
487 
488 	BScrollView* descriptionScrollView = new BScrollView(
489 		"package description scroll view", packageDescriptionView,
490 		0, false, true, B_NO_BORDER);
491 
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);
496 
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);
511 
512 	BScrollView* installTypeScrollView = new BScrollView(
513 		"install type description scroll view", fInstallTypeDescriptionView,
514 		 0, false, true, B_NO_BORDER);
515 
516 	// Destination menu field
517 	fDestination = new BPopUpMenu(B_TRANSLATE("none"));
518 	fDestField = new BMenuField("install_to", B_TRANSLATE("Install to:"),
519 		fDestination);
520 
521 	fBeginButton = new BButton("begin_button", B_TRANSLATE("Begin"),
522 		new BMessage(P_MSG_INSTALL));
523 
524 	BLayoutItem* typeLabelItem = installType->CreateLabelLayoutItem();
525 	BLayoutItem* typeMenuItem = installType->CreateMenuBarLayoutItem();
526 
527 	BLayoutItem* destFieldLabelItem = fDestField->CreateLabelLayoutItem();
528 	BLayoutItem* destFieldMenuItem = fDestField->CreateMenuBarLayoutItem();
529 
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));
533 
534 	BAlignment labelAlignment(B_ALIGN_RIGHT, B_ALIGN_VERTICAL_UNSET);
535 	typeLabelItem->SetExplicitAlignment(labelAlignment);
536 	destFieldLabelItem->SetExplicitAlignment(labelAlignment);
537 
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)
553 	;
554 
555 	fBeginButton->MakeDefault(true);
556 }
557 
558 
559 void
_InitProfiles()560 PackageView::_InitProfiles()
561 {
562 	int count = fInfo.GetProfileCount();
563 
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;
571 	}
572 
573 	for (int i = 1; i < count; i++) {
574 		pkg_profile* profile = fInfo.GetProfile(i);
575 
576 		if (profile != NULL)
577 			_AddInstallTypeMenuItem(profile->name, profile->space_needed, i);
578 		else
579 			fInstallTypes->AddSeparatorItem();
580 	}
581 }
582 
583 
584 status_t
_InstallTypeChanged(int32 index)585 PackageView::_InstallTypeChanged(int32 index)
586 {
587 	if (index < 0)
588 		return B_ERROR;
589 
590 	// Clear the choice list
591 	for (int32 i = fDestination->CountItems() - 1; i >= 0; i--) {
592 		BMenuItem* item = fDestination->RemoveItem(i);
593 		delete item;
594 	}
595 
596 	fCurrentType = index;
597 	pkg_profile* profile = fInfo.GetProfile(index);
598 
599 	if (profile == NULL)
600 		return B_ERROR;
601 
602 	BString typeDescription = profile->description;
603 	if (typeDescription.IsEmpty())
604 		typeDescription = profile->name;
605 
606 	fInstallTypeDescriptionView->SetText(typeDescription.String());
607 
608 	BPath path;
609 	BVolume volume;
610 
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());
619 			}
620 		}
621 
622 		if (item != NULL) {
623 			item->SetMarked(true);
624 			fCurrentPath.SetTo(path.Path());
625 			fDestination->AddSeparatorItem();
626 		}
627 
628 		_AddMenuItem(B_TRANSLATE("Other" B_UTF8_ELLIPSIS),
629 			new BMessage(P_MSG_OPEN_PANEL), fDestination);
630 
631 		fDestField->SetEnabled(true);
632 	} else if (profile->path_type == P_USER_PATH) {
633 		bool defaultPathSet = false;
634 		BVolumeRoster roster;
635 
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;
641 			}
642 
643 			if (path.SetTo(&mountPoint, NULL) != B_OK)
644 				continue;
645 
646 			char volumeName[B_FILE_NAME_LENGTH];
647 			volume.GetName(volumeName);
648 
649 			BMenuItem* item = _AddDestinationMenuItem(volumeName,
650 				volume.FreeBytes(), path.Path());
651 
652 			// The first volume becomes the default element
653 			if (!defaultPathSet) {
654 				item->SetMarked(true);
655 				fCurrentPath.SetTo(path.Path());
656 				defaultPathSet = true;
657 			}
658 		}
659 
660 		fDestField->SetEnabled(true);
661 	} else
662 		fDestField->SetEnabled(false);
663 
664 	return B_OK;
665 }
666 
667 
668 BString
_NamePlusSizeString(BString baseName,size_t size,const char * format) const669 PackageView::_NamePlusSizeString(BString baseName, size_t size,
670 	const char* format) const
671 {
672 	char sizeString[48];
673 	string_for_size(size, sizeString, sizeof(sizeString));
674 
675 	BString name(format);
676 	name.ReplaceAll("%name%", baseName);
677 	name.ReplaceAll("%size%", sizeString);
678 
679 	return name;
680 }
681 
682 
683 BMenuItem*
_AddInstallTypeMenuItem(BString baseName,size_t size,int32 index) const684 PackageView::_AddInstallTypeMenuItem(BString baseName, size_t size,
685 	int32 index) const
686 {
687 	BString name = _NamePlusSizeString(baseName, size,
688 		B_TRANSLATE("%name% (%size%)"));
689 
690 	BMessage* message = new BMessage(P_MSG_INSTALL_TYPE_CHANGED);
691 	message->AddInt32("index", index);
692 
693 	return _AddMenuItem(name, message, fInstallTypes);
694 }
695 
696 
697 BMenuItem*
_AddDestinationMenuItem(BString baseName,size_t size,const char * path) const698 PackageView::_AddDestinationMenuItem(BString baseName, size_t size,
699 	const char* path) const
700 {
701 	BString name = _NamePlusSizeString(baseName, size,
702 		B_TRANSLATE("%name% (%size% free)"));
703 
704 	BMessage* message = new BMessage(P_MSG_PATH_CHANGED);
705 	message->AddString("path", path);
706 
707 	return _AddMenuItem(name, message, fDestination);
708 }
709 
710 
711 BMenuItem*
_AddMenuItem(const char * name,BMessage * message,BMenu * menu) const712 PackageView::_AddMenuItem(const char* name, BMessage* message,
713 	BMenu* menu) const
714 {
715 	BMenuItem* item = new BMenuItem(name, message);
716 	item->SetTarget(this);
717 	menu->AddItem(item);
718 	return item;
719 }
720 
721 
722 bool
_ValidateFilePanelMessage(BMessage * message)723 PackageView::_ValidateFilePanelMessage(BMessage* message)
724 {
725 	if (!fExpectingOpenPanelResult)
726 		return false;
727 
728 	fExpectingOpenPanelResult = false;
729 	return true;
730 }
731