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