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