xref: /haiku/src/apps/installer/InstallerWindow.cpp (revision 74fc3e9a8bc4831206f84209be224cad7792dc0e)
1 /*
2  * Copyright 2009, Stephan Aßmus <superstippi@gmx.de>
3  * Copyright 2005-2008, Jérôme DUVAL
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 #include "InstallerWindow.h"
8 
9 #include <stdio.h>
10 #include <string.h>
11 
12 #include <Alert.h>
13 #include <Application.h>
14 #include <Autolock.h>
15 #include <Box.h>
16 #include <Button.h>
17 #include <Catalog.h>
18 #include <Directory.h>
19 #include <FindDirectory.h>
20 #include <GridLayoutBuilder.h>
21 #include <GroupLayoutBuilder.h>
22 #include <LayoutUtils.h>
23 #include <Locale.h>
24 #include <MenuBar.h>
25 #include <MenuField.h>
26 #include <Path.h>
27 #include <PopUpMenu.h>
28 #include <Roster.h>
29 #include <Screen.h>
30 #include <ScrollView.h>
31 #include <SeparatorView.h>
32 #include <SpaceLayoutItem.h>
33 #include <StatusBar.h>
34 #include <String.h>
35 #include <TextView.h>
36 #include <TranslationUtils.h>
37 #include <TranslatorFormats.h>
38 
39 #include "tracker_private.h"
40 
41 #include "DialogPane.h"
42 #include "PackageViews.h"
43 #include "PartitionMenuItem.h"
44 #include "WorkerThread.h"
45 
46 
47 #undef TR_CONTEXT
48 #define TR_CONTEXT "InstallerWindow"
49 
50 #define DRIVESETUP_SIG "application/x-vnd.Haiku-DriveSetup"
51 
52 const uint32 BEGIN_MESSAGE = 'iBGN';
53 const uint32 SHOW_BOTTOM_MESSAGE = 'iSBT';
54 const uint32 SETUP_MESSAGE = 'iSEP';
55 const uint32 START_SCAN = 'iSSC';
56 const uint32 PACKAGE_CHECKBOX = 'iPCB';
57 const uint32 ENCOURAGE_DRIVESETUP = 'iENC';
58 
59 class LogoView : public BView {
60 public:
61 								LogoView(const BRect& frame);
62 								LogoView();
63 	virtual						~LogoView();
64 
65 	virtual	void				Draw(BRect update);
66 
67 	virtual	void				GetPreferredSize(float* _width, float* _height);
68 
69 private:
70 			void				_Init();
71 
72 			BBitmap*			fLogo;
73 };
74 
75 
76 LogoView::LogoView(const BRect& frame)
77 	:
78 	BView(frame, "logoview", B_FOLLOW_LEFT | B_FOLLOW_TOP,
79 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
80 {
81 	_Init();
82 }
83 
84 
85 LogoView::LogoView()
86 	:
87 	BView("logoview", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
88 {
89 	_Init();
90 }
91 
92 
93 LogoView::~LogoView(void)
94 {
95 	delete fLogo;
96 }
97 
98 
99 void
100 LogoView::Draw(BRect update)
101 {
102 	if (fLogo == NULL)
103 		return;
104 
105 	BRect bounds(Bounds());
106 	BPoint placement;
107 	placement.x = (bounds.left + bounds.right - fLogo->Bounds().Width()) / 2;
108 	placement.y = (bounds.top + bounds.bottom - fLogo->Bounds().Height()) / 2;
109 
110 	DrawBitmap(fLogo, placement);
111 }
112 
113 
114 void
115 LogoView::GetPreferredSize(float* _width, float* _height)
116 {
117 	float width = 0.0;
118 	float height = 0.0;
119 	if (fLogo) {
120 		width = fLogo->Bounds().Width();
121 		height = fLogo->Bounds().Height();
122 	}
123 	if (_width)
124 		*_width = width;
125 	if (_height)
126 		*_height = height;
127 }
128 
129 
130 void
131 LogoView::_Init()
132 {
133 	fLogo = BTranslationUtils::GetBitmap(B_PNG_FORMAT, "haikulogo.png");
134 }
135 
136 
137 // #pragma mark -
138 
139 
140 static BLayoutItem*
141 layout_item_for(BView* view)
142 {
143 	BLayout* layout = view->Parent()->GetLayout();
144 	int32 index = layout->IndexOfView(view);
145 	return layout->ItemAt(index);
146 }
147 
148 
149 InstallerWindow::InstallerWindow()
150 	: BWindow(BRect(-2000, -2000, -1800, -1800), TR("Installer"),
151 		B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS),
152 	fEncouragedToSetupPartitions(false),
153 	fDriveSetupLaunched(false),
154 	fInstallStatus(kReadyForInstall),
155 	fWorkerThread(new WorkerThread(this)),
156 	fCopyEngineCancelSemaphore(-1)
157 {
158 	LogoView* logoView = new LogoView();
159 
160 	fStatusView = new BTextView("statusView", be_plain_font, NULL, B_WILL_DRAW);
161 	fStatusView->SetInsets(10, 10, 10, 10);
162 	fStatusView->MakeEditable(false);
163 	fStatusView->MakeSelectable(false);
164 
165 	BSize logoSize = logoView->MinSize();
166 	logoView->SetExplicitMaxSize(logoSize);
167 	fStatusView->SetExplicitMinSize(BSize(logoSize.width * 0.66, B_SIZE_UNSET));
168 
169 	fDestMenu = new BPopUpMenu(TR("scanning" B_UTF8_ELLIPSIS), true, false);
170 	fSrcMenu = new BPopUpMenu(TR("scanning" B_UTF8_ELLIPSIS), true, false);
171 
172 	fSrcMenuField = new BMenuField("srcMenuField", TR("Install from:"),
173 		fSrcMenu, NULL);
174 	fSrcMenuField->SetAlignment(B_ALIGN_RIGHT);
175 
176 	fDestMenuField = new BMenuField("destMenuField", TR("Onto:"), fDestMenu,
177 		NULL);
178 	fDestMenuField->SetAlignment(B_ALIGN_RIGHT);
179 
180 	fPackagesSwitch = new PaneSwitch("options_button");
181 	fPackagesSwitch->SetLabels(TR("Hide optional packages"),
182 		TR("Show optional packages"));
183 	fPackagesSwitch->SetMessage(new BMessage(SHOW_BOTTOM_MESSAGE));
184 	fPackagesSwitch->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
185 		B_SIZE_UNSET));
186 	fPackagesSwitch->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
187 		B_ALIGN_TOP));
188 
189 	fPackagesView = new PackagesView("packages_view");
190 	BScrollView* packagesScrollView = new BScrollView("packagesScroll",
191 		fPackagesView, B_WILL_DRAW, false, true);
192 
193 	const char* requiredDiskSpaceString
194 		= TR("Additional disk space required: 0.0 KiB");
195 	fSizeView = new BStringView("size_view", requiredDiskSpaceString);
196 	fSizeView->SetAlignment(B_ALIGN_RIGHT);
197 	fSizeView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
198 	fSizeView->SetExplicitAlignment(
199 		BAlignment(B_ALIGN_RIGHT, B_ALIGN_MIDDLE));
200 
201 	fProgressBar = new BStatusBar("progress", TR("Install progress:  "));
202 	fProgressBar->SetMaxValue(100.0);
203 
204 	fBeginButton = new BButton("begin_button", TR("Begin"),
205 		new BMessage(BEGIN_MESSAGE));
206 	fBeginButton->MakeDefault(true);
207 	fBeginButton->SetEnabled(false);
208 
209 	fSetupButton = new BButton("setup_button",
210 		TR("Set up partitions" B_UTF8_ELLIPSIS), new BMessage(SETUP_MESSAGE));
211 
212 	fMakeBootableButton = new BButton("makebootable_button",
213 		TR("Write boot sector"), new BMessage(MSG_WRITE_BOOT_SECTOR));
214 	fMakeBootableButton->SetEnabled(false);
215 
216 	SetLayout(new BGroupLayout(B_HORIZONTAL));
217 	AddChild(BGroupLayoutBuilder(B_VERTICAL)
218 		.Add(BGroupLayoutBuilder(B_HORIZONTAL)
219 			.Add(logoView)
220 			.Add(fStatusView)
221 		)
222 		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
223 		.Add(BGroupLayoutBuilder(B_VERTICAL, 10)
224 			.Add(BGridLayoutBuilder(0, 10)
225 				.Add(fSrcMenuField->CreateLabelLayoutItem(), 0, 0)
226 				.Add(fSrcMenuField->CreateMenuBarLayoutItem(), 1, 0)
227 				.Add(fDestMenuField->CreateLabelLayoutItem(), 0, 1)
228 				.Add(fDestMenuField->CreateMenuBarLayoutItem(), 1, 1)
229 
230 				.Add(BSpaceLayoutItem::CreateVerticalStrut(5), 0, 2, 2)
231 
232 				.Add(fPackagesSwitch, 0, 3, 2)
233 				.Add(packagesScrollView, 0, 4, 2)
234 				.Add(fProgressBar, 0, 5, 2)
235 				.Add(fSizeView, 0, 6, 2)
236 			)
237 
238 			.Add(BGroupLayoutBuilder(B_HORIZONTAL, 10)
239 				.Add(fSetupButton)
240 				.Add(fMakeBootableButton)
241 				.AddGlue()
242 				.Add(fBeginButton)
243 			)
244 			.SetInsets(10, 10, 10, 10)
245 		)
246 	);
247 
248 	// Make the optional packages and progress bar invisible on start
249 	fPackagesLayoutItem = layout_item_for(packagesScrollView);
250 	fPkgSwitchLayoutItem = layout_item_for(fPackagesSwitch);
251 	fSizeViewLayoutItem = layout_item_for(fSizeView);
252 	fProgressLayoutItem = layout_item_for(fProgressBar);
253 
254 	fPackagesLayoutItem->SetVisible(false);
255 	fSizeViewLayoutItem->SetVisible(false);
256 	fProgressLayoutItem->SetVisible(false);
257 
258 	// Setup tool tips for the non-obvious features
259 	fSetupButton->SetToolTip(
260 		TR("Launch the DriveSetup utility to partition\n"
261 		"available hard drives and other media.\n"
262 		"Partitions can be initialized with the\n"
263 		"Be File System needed for a Haiku boot\n"
264 		"partition."));
265 	fMakeBootableButton->SetToolTip(
266 		TR("Writes the Haiku boot code to the partition start\n"
267 		"sector. This step is automatically performed by\n"
268 		"the installation, but you can manually make a\n"
269 		"partition bootable in case you do not need to\n"
270 		"perform an installation."));
271 
272 	// finish creating window
273 	if (!be_roster->IsRunning(kDeskbarSignature))
274 		SetFlags(Flags() | B_NOT_MINIMIZABLE);
275 
276 	CenterOnScreen();
277 	Show();
278 
279 	fDriveSetupLaunched = be_roster->IsRunning(DRIVESETUP_SIG);
280 
281 	if (Lock()) {
282 		fSetupButton->SetEnabled(!fDriveSetupLaunched);
283 		Unlock();
284 	}
285 
286 	be_roster->StartWatching(this);
287 
288 	PostMessage(START_SCAN);
289 }
290 
291 
292 InstallerWindow::~InstallerWindow()
293 {
294 	_SetCopyEngineCancelSemaphore(-1);
295 	be_roster->StopWatching(this);
296 }
297 
298 
299 void
300 InstallerWindow::MessageReceived(BMessage *msg)
301 {
302 	switch (msg->what) {
303 		case MSG_RESET:
304 		{
305 			_SetCopyEngineCancelSemaphore(-1);
306 
307 			status_t error;
308 			if (msg->FindInt32("error", &error) == B_OK) {
309 				char errorMessage[2048];
310 				snprintf(errorMessage, sizeof(errorMessage), TR("An error was "
311 					"encountered and the installation was not completed:\n\n"
312 					"Error:  %s"), strerror(error));
313 				(new BAlert("error", errorMessage, TR("OK")))->Go();
314 			}
315 
316 			_DisableInterface(false);
317 
318 			fProgressLayoutItem->SetVisible(false);
319 			fPkgSwitchLayoutItem->SetVisible(true);
320 			_ShowOptionalPackages();
321 			_UpdateControls();
322 			break;
323 		}
324 		case START_SCAN:
325 			_ScanPartitions();
326 			break;
327 		case BEGIN_MESSAGE:
328 			switch (fInstallStatus) {
329 				case kReadyForInstall:
330 				{
331 					_SetCopyEngineCancelSemaphore(create_sem(1,
332 						"copy engine cancel"));
333 
334 					BList* list = new BList();
335 					int32 size = 0;
336 					fPackagesView->GetPackagesToInstall(list, &size);
337 					fWorkerThread->SetLock(fCopyEngineCancelSemaphore);
338 					fWorkerThread->SetPackagesList(list);
339 					fWorkerThread->SetSpaceRequired(size);
340 					fInstallStatus = kInstalling;
341 					fWorkerThread->StartInstall();
342 					fBeginButton->SetLabel(TR("Stop"));
343 					_DisableInterface(true);
344 
345 					fProgressBar->SetTo(0.0, NULL, NULL);
346 
347 					fPkgSwitchLayoutItem->SetVisible(false);
348 					fPackagesLayoutItem->SetVisible(false);
349 					fSizeViewLayoutItem->SetVisible(false);
350 					fProgressLayoutItem->SetVisible(true);
351 					break;
352 				}
353 				case kInstalling:
354 				{
355 					_QuitCopyEngine(true);
356 					break;
357 				}
358 				case kFinished:
359 					PostMessage(B_QUIT_REQUESTED);
360 					break;
361 				case kCancelled:
362 					break;
363 			}
364 			break;
365 		case SHOW_BOTTOM_MESSAGE:
366 			_ShowOptionalPackages();
367 			break;
368 		case SOURCE_PARTITION:
369 			_PublishPackages();
370 			_UpdateControls();
371 			break;
372 		case TARGET_PARTITION:
373 			_UpdateControls();
374 			break;
375 		case SETUP_MESSAGE:
376 			_LaunchDriveSetup();
377 			break;
378 		case PACKAGE_CHECKBOX:
379 		{
380 			char buffer[15];
381 			fPackagesView->GetTotalSizeAsString(buffer, sizeof(buffer));
382 			char string[256];
383 			snprintf(string, sizeof(string),
384 				TR("Additional disk space required: %s"), buffer);
385 			fSizeView->SetText(string);
386 			break;
387 		}
388 		case ENCOURAGE_DRIVESETUP:
389 		{
390 			(new BAlert("use drive setup", TR("No partitions have been found "
391 				"that are suitable for installation. Please set up partitions "
392 				"and initialize at least one partition with the Be File "
393 				"System."), TR("OK")))->Go();
394 		}
395 		case MSG_STATUS_MESSAGE:
396 		{
397 // TODO: Was this supposed to prevent status messages still arriving
398 // after the copy engine was shut down?
399 //			if (fInstallStatus != kInstalling)
400 //				break;
401 			float progress;
402 			if (msg->FindFloat("progress", &progress) == B_OK) {
403 				const char* currentItem;
404 				if (msg->FindString("item", &currentItem) != B_OK) {
405 					currentItem = TR_CMT("???",
406 						"Unknown currently copied item");
407 				}
408 				BString trailingLabel;
409 				int32 currentCount;
410 				int32 maximumCount;
411 				if (msg->FindInt32("current", &currentCount) == B_OK
412 					&& msg->FindInt32("maximum", &maximumCount) == B_OK) {
413 					char buffer[64];
414 					snprintf(buffer, sizeof(buffer), TR_CMT("%1ld of %2ld",
415 						"number of files copied"), currentCount, maximumCount);
416 					trailingLabel << buffer;
417 				} else {
418 					trailingLabel << TR_CMT("?? of ??", "Unknown progress");
419 				}
420 				fProgressBar->SetTo(progress, currentItem,
421 					trailingLabel.String());
422 			} else {
423 				const char *status;
424 				if (msg->FindString("status", &status) == B_OK) {
425 					fLastStatus = fStatusView->Text();
426 					_SetStatusMessage(status);
427 				} else
428 					_SetStatusMessage(fLastStatus.String());
429 			}
430 			break;
431 		}
432 		case MSG_INSTALL_FINISHED:
433 		{
434 
435 			_SetCopyEngineCancelSemaphore(-1);
436 
437 			fBeginButton->SetLabel(TR("Quit"));
438 
439 			PartitionMenuItem* dstItem
440 				= (PartitionMenuItem*)fDestMenu->FindMarked();
441 
442 			char status[1024];
443 			if (be_roster->IsRunning(kDeskbarSignature)) {
444 				snprintf(status, sizeof(status), TR("Installation completed. "
445 					"Boot sector has been written to '%s'. Press Quit to "
446 					"leave the Installer or choose a new target volume to "
447 					"perform another installation."),
448 					dstItem ? dstItem->Name() : TR_CMT("???",
449 						"Unknown partition name"));
450 			} else {
451 				snprintf(status, sizeof(status), TR("Installation completed. "
452 					"Boot sector has been written to '%s'. Press Quit to "
453 					"restart the computer or choose a new target volume "
454 					"to perform another installation."),
455 					dstItem ? dstItem->Name() : TR_CMT("???",
456 						"Unknown partition name"));
457 			}
458 
459 			_SetStatusMessage(status);
460 			fInstallStatus = kFinished;
461 			_DisableInterface(false);
462 			fProgressLayoutItem->SetVisible(false);
463 			fPkgSwitchLayoutItem->SetVisible(true);
464 			_ShowOptionalPackages();
465 			break;
466 		}
467 		case B_SOME_APP_LAUNCHED:
468 		case B_SOME_APP_QUIT:
469 		{
470 			const char *signature;
471 			if (msg->FindString("be:signature", &signature) == B_OK
472 				&& strcasecmp(signature, DRIVESETUP_SIG) == 0) {
473 				fDriveSetupLaunched = msg->what == B_SOME_APP_LAUNCHED;
474 				fBeginButton->SetEnabled(!fDriveSetupLaunched);
475 				_DisableInterface(fDriveSetupLaunched);
476 				if (fDriveSetupLaunched)
477 					_SetStatusMessage(TR("Running DriveSetup" B_UTF8_ELLIPSIS
478 						"\n\nClose DriveSetup to continue with the "
479 						"installation."));
480 				else
481 					_ScanPartitions();
482 			}
483 			break;
484 		}
485 		case MSG_WRITE_BOOT_SECTOR:
486 			fWorkerThread->WriteBootSector(fDestMenu);
487 			break;
488 
489 		default:
490 			BWindow::MessageReceived(msg);
491 			break;
492 	}
493 }
494 
495 
496 bool
497 InstallerWindow::QuitRequested()
498 {
499 	if (fDriveSetupLaunched) {
500 		(new BAlert("driveSetup",
501 			TR("Please close the DriveSetup window before closing the "
502 			"Installer window."), TR("OK")))->Go();
503 		return false;
504 	}
505 
506 	if (fInstallStatus != kFinished && (Flags() & B_NOT_MINIMIZABLE) != 0) {
507 		// This means Deskbar is not running, i.e. Installer is the only
508 		// thing on the screen and we will reboot the machine once it quits.
509 		if ((new BAlert("reallyQuit",
510 			TR("Are you sure you want to abort the installation and restart "
511 				"the system?"),
512 			TR("Cancel"), TR("Restart system")))->Go() == 0) {
513 			return false;
514 		}
515 	}
516 
517 	_QuitCopyEngine(false);
518 	fWorkerThread->PostMessage(B_QUIT_REQUESTED);
519 	be_app->PostMessage(B_QUIT_REQUESTED);
520 	return true;
521 }
522 
523 
524 // #pragma mark -
525 
526 
527 void
528 InstallerWindow::_ShowOptionalPackages()
529 {
530 	if (fPackagesLayoutItem && fSizeViewLayoutItem) {
531 		fPackagesLayoutItem->SetVisible(fPackagesSwitch->Value());
532 		fSizeViewLayoutItem->SetVisible(fPackagesSwitch->Value());
533 	}
534 }
535 
536 
537 void
538 InstallerWindow::_LaunchDriveSetup()
539 {
540 	if (be_roster->Launch(DRIVESETUP_SIG) != B_OK) {
541 		// Try really hard to launch it. It's very likely that this fails,
542 		// when we run from the CD and there is only an incomplete mime
543 		// database for example...
544 		BPath path;
545 		if (find_directory(B_SYSTEM_APPS_DIRECTORY, &path) != B_OK
546 			|| path.Append("DriveSetup") != B_OK) {
547 			path.SetTo("/boot/system/apps/DriveSetup");
548 		}
549 		BEntry entry(path.Path());
550 		entry_ref ref;
551 		if (entry.GetRef(&ref) != B_OK || be_roster->Launch(&ref) != B_OK) {
552 			BAlert* alert = new BAlert("error", TR("DriveSetup, the "
553 				"application to configure disk partitions, could not be "
554 				"launched."), TR("OK"));
555 			alert->Go();
556 		}
557 	}
558 }
559 
560 
561 void
562 InstallerWindow::_DisableInterface(bool disable)
563 {
564 	fSetupButton->SetEnabled(!disable);
565 	fMakeBootableButton->SetEnabled(!disable);
566 	fSrcMenuField->SetEnabled(!disable);
567 	fDestMenuField->SetEnabled(!disable);
568 }
569 
570 
571 void
572 InstallerWindow::_ScanPartitions()
573 {
574 	_SetStatusMessage(TR("Scanning for disks" B_UTF8_ELLIPSIS));
575 
576 	BMenuItem *item;
577 	while ((item = fSrcMenu->RemoveItem((int32)0)))
578 		delete item;
579 	while ((item = fDestMenu->RemoveItem((int32)0)))
580 		delete item;
581 
582 	fWorkerThread->ScanDisksPartitions(fSrcMenu, fDestMenu);
583 
584 	if (fSrcMenu->ItemAt(0)) {
585 		_PublishPackages();
586 	}
587 	_UpdateControls();
588 }
589 
590 
591 void
592 InstallerWindow::_UpdateControls()
593 {
594 	PartitionMenuItem* srcItem = (PartitionMenuItem*)fSrcMenu->FindMarked();
595 	BString label;
596 	if (srcItem) {
597 		label = srcItem->MenuLabel();
598 	} else {
599 		if (fSrcMenu->CountItems() == 0)
600 			label = TR_CMT("<none>", "No partition available");
601 		else
602 			label = ((PartitionMenuItem*)fSrcMenu->ItemAt(0))->MenuLabel();
603 	}
604 	fSrcMenuField->MenuItem()->SetLabel(label.String());
605 
606 	// Disable any unsuitable target items, check if at least one partition
607 	// is suitable.
608 	bool foundOneSuitableTarget = false;
609 	for (int32 i = fDestMenu->CountItems() - 1; i >= 0; i--) {
610 		PartitionMenuItem* dstItem
611 			= (PartitionMenuItem*)fDestMenu->ItemAt(i);
612 		if (srcItem != NULL && dstItem->ID() == srcItem->ID()) {
613 			// Prevent the user from having picked the same partition as source
614 			// and destination.
615 			dstItem->SetEnabled(false);
616 			dstItem->SetMarked(false);
617 		} else
618 			dstItem->SetEnabled(dstItem->IsValidTarget());
619 
620 		if (dstItem->IsEnabled())
621 			foundOneSuitableTarget = true;
622 	}
623 
624 	PartitionMenuItem* dstItem = (PartitionMenuItem*)fDestMenu->FindMarked();
625 	if (dstItem) {
626 		label = dstItem->MenuLabel();
627 	} else {
628 		if (fDestMenu->CountItems() == 0)
629 			label = TR_CMT("<none>", "No partition available");
630 		else
631 			label = TR("Please choose target");
632 	}
633 	fDestMenuField->MenuItem()->SetLabel(label.String());
634 
635 	if (srcItem && dstItem) {
636 		char message[255];
637 		sprintf(message, TR("Press the Begin button to install from '%1s' "
638 			"onto '%2s'."), srcItem->Name(), dstItem->Name());
639 		_SetStatusMessage(message);
640 	} else if (srcItem) {
641 		_SetStatusMessage(TR("Choose the disk you want to install onto from "
642 			"the pop-up menu. Then click \"Begin\"."));
643 	} else if (dstItem) {
644 		_SetStatusMessage(TR("Choose the source disk from the "
645 			"pop-up menu. Then click \"Begin\"."));
646 	} else {
647 		_SetStatusMessage(TR("Choose the source and destination disk from the "
648 			"pop-up menus. Then click \"Begin\"."));
649 	}
650 
651 	fInstallStatus = kReadyForInstall;
652 	fBeginButton->SetLabel(TR("Begin"));
653 	fBeginButton->SetEnabled(srcItem && dstItem);
654 
655 	// adjust "Write Boot Sector" button
656 	if (dstItem) {
657 		char buffer[256];
658 		snprintf(buffer, sizeof(buffer), TR("Write boot sector to '%s'"),
659 			dstItem->Name());
660 		label = buffer;
661 	} else
662 		label = TR("Write boot sector");
663 	fMakeBootableButton->SetEnabled(dstItem);
664 	fMakeBootableButton->SetLabel(label.String());
665 
666 	if (!fEncouragedToSetupPartitions && !foundOneSuitableTarget) {
667 		// Focus the users attention on the DriveSetup button
668 		fEncouragedToSetupPartitions = true;
669 		PostMessage(ENCOURAGE_DRIVESETUP);
670 	}
671 }
672 
673 
674 void
675 InstallerWindow::_PublishPackages()
676 {
677 	fPackagesView->Clean();
678 	PartitionMenuItem *item = (PartitionMenuItem *)fSrcMenu->FindMarked();
679 	if (!item)
680 		return;
681 
682 	BPath directory;
683 	BDiskDeviceRoster roster;
684 	BDiskDevice device;
685 	BPartition *partition;
686 	if (roster.GetPartitionWithID(item->ID(), &device, &partition) == B_OK) {
687 		if (partition->GetMountPoint(&directory) != B_OK)
688 			return;
689 	} else if (roster.GetDeviceWithID(item->ID(), &device) == B_OK) {
690 		if (device.GetMountPoint(&directory) != B_OK)
691 			return;
692 	} else
693 		return; // shouldn't happen
694 
695 	directory.Append(PACKAGES_DIRECTORY);
696 	BDirectory dir(directory.Path());
697 	if (dir.InitCheck() != B_OK)
698 		return;
699 
700 	BEntry packageEntry;
701 	BList packages;
702 	while (dir.GetNextEntry(&packageEntry) == B_OK) {
703 		Package* package = Package::PackageFromEntry(packageEntry);
704 		if (package != NULL)
705 			packages.AddItem(package);
706 	}
707 	packages.SortItems(_ComparePackages);
708 
709 	fPackagesView->AddPackages(packages, new BMessage(PACKAGE_CHECKBOX));
710 	PostMessage(PACKAGE_CHECKBOX);
711 }
712 
713 
714 void
715 InstallerWindow::_SetStatusMessage(const char *text)
716 {
717 	fStatusView->SetText(text);
718 }
719 
720 
721 void
722 InstallerWindow::_SetCopyEngineCancelSemaphore(sem_id id, bool alreadyLocked)
723 {
724 	if (fCopyEngineCancelSemaphore >= 0) {
725 		if (!alreadyLocked)
726 			acquire_sem(fCopyEngineCancelSemaphore);
727 		delete_sem(fCopyEngineCancelSemaphore);
728 	}
729 	fCopyEngineCancelSemaphore = id;
730 }
731 
732 
733 void
734 InstallerWindow::_QuitCopyEngine(bool askUser)
735 {
736 	if (fCopyEngineCancelSemaphore < 0)
737 		return;
738 
739 	// First of all block the copy engine, so that it doesn't continue
740 	// while the alert is showing, which would be irritating.
741 	acquire_sem(fCopyEngineCancelSemaphore);
742 
743 	bool quit = true;
744 	if (askUser) {
745 		quit = (new BAlert("cancel",
746 			TR("Are you sure you want to to stop the installation?"),
747 			TR_CMT("Continue", "In alert after pressing Stop"),
748 			TR_CMT("Stop", "In alert after pressing Stop"), 0,
749 			B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0;
750 	}
751 
752 	if (quit) {
753 		// Make it quit by having it's lock fail...
754 		_SetCopyEngineCancelSemaphore(-1, true);
755 	} else
756 		release_sem(fCopyEngineCancelSemaphore);
757 }
758 
759 
760 // #pragma mark -
761 
762 
763 int
764 InstallerWindow::_ComparePackages(const void* firstArg, const void* secondArg)
765 {
766 	const Group* group1 = *static_cast<const Group* const *>(firstArg);
767 	const Group* group2 = *static_cast<const Group* const *>(secondArg);
768 	const Package* package1 = dynamic_cast<const Package*>(group1);
769 	const Package* package2 = dynamic_cast<const Package*>(group2);
770 	int sameGroup = strcmp(group1->GroupName(), group2->GroupName());
771 	if (sameGroup != 0)
772 		return sameGroup;
773 	if (package2 == NULL)
774 		return -1;
775 	if (package1 == NULL)
776 		return 1;
777 	return strcmp(package1->Name(), package2->Name());
778 }
779 
780 
781