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