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