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