xref: /haiku/src/apps/installer/InstallerWindow.cpp (revision 445d4fd926c569e7b9ae28017da86280aaecbae2)
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 	// finish creating window
295 	if (!be_roster->IsRunning(kDeskbarSignature))
296 		SetFlags(Flags() | B_NOT_MINIMIZABLE);
297 
298 	CenterOnScreen();
299 	Show();
300 
301 	// Register to receive notifications when apps launch or quit...
302 	be_roster->StartWatching(this);
303 	// ... and check the two we are interested in.
304 	fDriveSetupLaunched = be_roster->IsRunning(kDriveSetupSignature);
305 	fBootManagerLaunched = be_roster->IsRunning(kBootManagerSignature);
306 
307 	if (Lock()) {
308 		fLaunchDriveSetupButton->SetEnabled(!fDriveSetupLaunched);
309 		fLaunchBootManagerItem->SetEnabled(!fBootManagerLaunched);
310 		Unlock();
311 	}
312 
313 	PostMessage(START_SCAN);
314 }
315 
316 
317 InstallerWindow::~InstallerWindow()
318 {
319 	_SetCopyEngineCancelSemaphore(-1);
320 	be_roster->StopWatching(this);
321 }
322 
323 
324 void
325 InstallerWindow::MessageReceived(BMessage *msg)
326 {
327 	switch (msg->what) {
328 		case MSG_RESET:
329 		{
330 			_SetCopyEngineCancelSemaphore(-1);
331 
332 			status_t error;
333 			if (msg->FindInt32("error", &error) == B_OK) {
334 				char errorMessage[2048];
335 				snprintf(errorMessage, sizeof(errorMessage),
336 					B_TRANSLATE("An error was encountered and the "
337 					"installation was not completed:\n\n"
338 					"Error:  %s"), strerror(error));
339 				BAlert* alert = new BAlert("error", errorMessage, B_TRANSLATE("OK"));
340 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
341 				alert->Go();
342 			}
343 
344 			_DisableInterface(false);
345 
346 			fProgressLayoutItem->SetVisible(false);
347 			fPkgSwitchLayoutItem->SetVisible(true);
348 			_ShowOptionalPackages();
349 			_UpdateControls();
350 			break;
351 		}
352 		case START_SCAN:
353 			_ScanPartitions();
354 			break;
355 		case BEGIN_MESSAGE:
356 			switch (fInstallStatus) {
357 				case kReadyForInstall:
358 				{
359 					// get source and target
360 					PartitionMenuItem* targetItem
361 						= (PartitionMenuItem*)fDestMenu->FindMarked();
362 					PartitionMenuItem* srcItem
363 						= (PartitionMenuItem*)fSrcMenu->FindMarked();
364 					if (srcItem == NULL || targetItem == NULL)
365 						break;
366 
367 					_SetCopyEngineCancelSemaphore(create_sem(1,
368 						"copy engine cancel"));
369 
370 					BList* list = new BList();
371 					int32 size = 0;
372 					fPackagesView->GetPackagesToInstall(list, &size);
373 					fWorkerThread->SetLock(fCopyEngineCancelSemaphore);
374 					fWorkerThread->SetPackagesList(list);
375 					fWorkerThread->SetSpaceRequired(size);
376 					fInstallStatus = kInstalling;
377 					fWorkerThread->StartInstall(srcItem->ID(),
378 						targetItem->ID());
379 					fBeginButton->SetLabel(B_TRANSLATE("Stop"));
380 					_DisableInterface(true);
381 
382 					fProgressBar->SetTo(0.0, NULL, NULL);
383 
384 					fPkgSwitchLayoutItem->SetVisible(false);
385 					fPackagesLayoutItem->SetVisible(false);
386 					fSizeViewLayoutItem->SetVisible(false);
387 					fProgressLayoutItem->SetVisible(true);
388 					break;
389 				}
390 				case kInstalling:
391 				{
392 					_QuitCopyEngine(true);
393 					break;
394 				}
395 				case kFinished:
396 					PostMessage(B_QUIT_REQUESTED);
397 					break;
398 				case kCancelled:
399 					break;
400 			}
401 			break;
402 		case SHOW_BOTTOM_MESSAGE:
403 			_ShowOptionalPackages();
404 			break;
405 		case SOURCE_PARTITION:
406 			_PublishPackages();
407 			_UpdateControls();
408 			break;
409 		case TARGET_PARTITION:
410 			_UpdateControls();
411 			break;
412 		case LAUNCH_DRIVE_SETUP:
413 			_LaunchDriveSetup();
414 			break;
415 		case LAUNCH_BOOTMAN:
416 			_LaunchBootManager();
417 			break;
418 		case PACKAGE_CHECKBOX:
419 		{
420 			char buffer[15];
421 			fPackagesView->GetTotalSizeAsString(buffer, sizeof(buffer));
422 			char string[256];
423 			snprintf(string, sizeof(string),
424 				B_TRANSLATE("Additional disk space required: %s"), buffer);
425 			fSizeView->SetText(string);
426 			fSizeView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
427 			break;
428 		}
429 		case ENCOURAGE_DRIVESETUP:
430 		{
431 			BAlert* alert = new BAlert("use drive setup", B_TRANSLATE("No partitions have "
432 				"been found that are suitable for installation. Please set "
433 				"up partitions and format at least one partition with the "
434 				"Be File System."), B_TRANSLATE("OK"));
435 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
436 			alert->Go();
437 			break;
438 		}
439 		case MSG_STATUS_MESSAGE:
440 		{
441 			float progress;
442 			if (msg->FindFloat("progress", &progress) == B_OK) {
443 				const char* currentItem;
444 				if (msg->FindString("item", &currentItem) != B_OK) {
445 					currentItem = B_TRANSLATE_COMMENT("???",
446 						"Unknown currently copied item");
447 				}
448 				BString trailingLabel;
449 				int32 currentCount;
450 				int32 maximumCount;
451 				if (msg->FindInt32("current", &currentCount) == B_OK
452 					&& msg->FindInt32("maximum", &maximumCount) == B_OK) {
453 					char buffer[64];
454 					snprintf(buffer, sizeof(buffer),
455 						B_TRANSLATE_COMMENT("%1ld of %2ld",
456 							"number of files copied"),
457 						currentCount, maximumCount);
458 					trailingLabel << buffer;
459 				} else {
460 					trailingLabel <<
461 						B_TRANSLATE_COMMENT("?? of ??", "Unknown progress");
462 				}
463 				fProgressBar->SetTo(progress, currentItem,
464 					trailingLabel.String());
465 			} else {
466 				const char *status;
467 				if (msg->FindString("status", &status) == B_OK) {
468 					fLastStatus = fStatusView->Text();
469 					_SetStatusMessage(status);
470 				} else
471 					_SetStatusMessage(fLastStatus.String());
472 			}
473 			break;
474 		}
475 		case MSG_INSTALL_FINISHED:
476 		{
477 
478 			_SetCopyEngineCancelSemaphore(-1);
479 
480 			PartitionMenuItem* dstItem
481 				= (PartitionMenuItem*)fDestMenu->FindMarked();
482 
483 			BString status;
484 			if (be_roster->IsRunning(kDeskbarSignature)) {
485 				fBeginButton->SetLabel(B_TRANSLATE("Quit"));
486 				status.SetToFormat(B_TRANSLATE("Installation "
487 					"completed. Boot sector has been written to '%s'. Press "
488 					"'Quit' to leave the Installer or choose a new target "
489 					"volume to perform another installation."),
490 					dstItem ? dstItem->Name() : B_TRANSLATE_COMMENT("???",
491 						"Unknown partition name"));
492 			} else {
493 				fBeginButton->SetLabel(B_TRANSLATE("Restart"));
494 				status.SetToFormat(B_TRANSLATE("Installation "
495 					"completed. Boot sector has been written to '%s'. Press "
496 					"'Restart' to restart the computer or choose a new target "
497 					"volume to perform another installation."),
498 					dstItem ? dstItem->Name() : B_TRANSLATE_COMMENT("???",
499 						"Unknown partition name"));
500 			}
501 
502 			_SetStatusMessage(status.String());
503 			fInstallStatus = kFinished;
504 
505 			_DisableInterface(false);
506 			fProgressLayoutItem->SetVisible(false);
507 			fPkgSwitchLayoutItem->SetVisible(true);
508 			_ShowOptionalPackages();
509 			break;
510 		}
511 		case B_SOME_APP_LAUNCHED:
512 		case B_SOME_APP_QUIT:
513 		{
514 			const char *signature;
515 			if (msg->FindString("be:signature", &signature) != B_OK)
516 				break;
517 			bool isDriveSetup = !strcasecmp(signature, kDriveSetupSignature);
518 			bool isBootManager = !strcasecmp(signature, kBootManagerSignature);
519 			if (isDriveSetup || isBootManager) {
520 				bool scanPartitions = false;
521 				if (isDriveSetup) {
522 					bool launched = msg->what == B_SOME_APP_LAUNCHED;
523 					// We need to scan partitions if DriveSetup has quit.
524 					scanPartitions = fDriveSetupLaunched && !launched;
525 					fDriveSetupLaunched = launched;
526 				}
527 				if (isBootManager)
528 					fBootManagerLaunched = msg->what == B_SOME_APP_LAUNCHED;
529 
530 				fBeginButton->SetEnabled(
531 					!fDriveSetupLaunched && !fBootManagerLaunched);
532 				_DisableInterface(fDriveSetupLaunched || fBootManagerLaunched);
533 				if (fDriveSetupLaunched && fBootManagerLaunched) {
534 					_SetStatusMessage(B_TRANSLATE("Running BootManager and "
535 						"DriveSetup" B_UTF8_ELLIPSIS
536 						"\n\nClose both applications to continue with the "
537 						"installation."));
538 				} else if (fDriveSetupLaunched) {
539 					_SetStatusMessage(B_TRANSLATE("Running DriveSetup"
540 						B_UTF8_ELLIPSIS
541 						"\n\nClose DriveSetup to continue with the "
542 						"installation."));
543 				} else if (fBootManagerLaunched) {
544 					_SetStatusMessage(B_TRANSLATE("Running BootManager"
545 						B_UTF8_ELLIPSIS
546 						"\n\nClose BootManager to continue with the "
547 						"installation."));
548 				} else {
549 					// If neither DriveSetup nor Bootman is running, we need
550 					// to scan partitions in case DriveSetup has quit, or
551 					// we need to update the guidance message, unless install
552 					// was already finished.
553 					if (scanPartitions)
554 						_ScanPartitions();
555 					else if (fInstallStatus != kFinished)
556 						_UpdateControls();
557 					else
558 						PostMessage(MSG_INSTALL_FINISHED);
559 				}
560 			}
561 			break;
562 		}
563 		case MSG_WRITE_BOOT_SECTOR:
564 			fWorkerThread->WriteBootSector(fDestMenu);
565 			break;
566 
567 		default:
568 			BWindow::MessageReceived(msg);
569 			break;
570 	}
571 }
572 
573 
574 bool
575 InstallerWindow::QuitRequested()
576 {
577 	if ((Flags() & B_NOT_MINIMIZABLE) != 0) {
578 		// This means Deskbar is not running, i.e. Installer is the only
579 		// thing on the screen and we will reboot the machine once it quits.
580 
581 		if (fDriveSetupLaunched && fBootManagerLaunched) {
582 			BAlert* alert = new BAlert(B_TRANSLATE("Quit BootManager and "
583 				"DriveSetup"),	B_TRANSLATE("Please close the BootManager "
584 				"and DriveSetup windows before closing the Installer window."),
585 				B_TRANSLATE("OK"));
586 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
587 			alert->Go();
588 			return false;
589 		}
590 		if (fDriveSetupLaunched) {
591 			BAlert* alert = new BAlert(B_TRANSLATE("Quit DriveSetup"),
592 				B_TRANSLATE("Please close the DriveSetup window before "
593 				"closing the Installer window."), B_TRANSLATE("OK"));
594 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
595 			alert->Go();
596 			return false;
597 		}
598 		if (fBootManagerLaunched) {
599 			BAlert* alert = new BAlert(B_TRANSLATE("Quit BootManager"),
600 				B_TRANSLATE("Please close the BootManager window before "
601 				"closing the Installer window."), B_TRANSLATE("OK"));
602 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
603 			alert->Go();
604 			return false;
605 		}
606 		if (fInstallStatus != kFinished) {
607 			BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("Installer"),
608 				B_TRANSLATE("Are you sure you want to stop the installation?"),
609 				B_TRANSLATE("Cancel"), B_TRANSLATE("Stop"), NULL,
610 				B_WIDTH_AS_USUAL, B_STOP_ALERT);
611 			alert->SetShortcut(0, B_ESCAPE);
612 			if (alert->Go() == 0)
613 				return false;
614 		}
615 	} else if (fInstallStatus == kInstalling) {
616 			BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("Installer"),
617 				B_TRANSLATE("The installation is not complete yet!\n"
618                                 "Are you sure you want to stop it?"),
619 				B_TRANSLATE("Cancel"), B_TRANSLATE("Stop"), NULL,
620 				B_WIDTH_AS_USUAL, B_STOP_ALERT);
621 			alert->SetShortcut(0, B_ESCAPE);
622 			if (alert->Go() == 0)
623 				return false;
624 	}
625 
626 	_QuitCopyEngine(false);
627 
628 	BMessage quitWithInstallStatus(B_QUIT_REQUESTED);
629 	quitWithInstallStatus.AddBool("install_complete",
630 		fInstallStatus == kFinished);
631 
632 	fWorkerThread->PostMessage(&quitWithInstallStatus);
633 	be_app->PostMessage(&quitWithInstallStatus);
634 	return true;
635 }
636 
637 
638 // #pragma mark -
639 
640 
641 void
642 InstallerWindow::_ShowOptionalPackages()
643 {
644 	if (fPackagesLayoutItem && fSizeViewLayoutItem) {
645 		fPackagesLayoutItem->SetVisible(fPackagesSwitch->Value());
646 		fSizeViewLayoutItem->SetVisible(fPackagesSwitch->Value());
647 	}
648 }
649 
650 
651 void
652 InstallerWindow::_LaunchDriveSetup()
653 {
654 	if (be_roster->Launch(kDriveSetupSignature) != B_OK) {
655 		// Try really hard to launch it. It's very likely that this fails,
656 		// when we run from the CD and there is only an incomplete mime
657 		// database for example...
658 		BPath path;
659 		if (find_directory(B_SYSTEM_APPS_DIRECTORY, &path) != B_OK
660 			|| path.Append("DriveSetup") != B_OK) {
661 			path.SetTo("/boot/system/apps/DriveSetup");
662 		}
663 		BEntry entry(path.Path());
664 		entry_ref ref;
665 		if (entry.GetRef(&ref) != B_OK || be_roster->Launch(&ref) != B_OK) {
666 			BAlert* alert = new BAlert("error", B_TRANSLATE("DriveSetup, the "
667 				"application to configure disk partitions, could not be "
668 				"launched."), B_TRANSLATE("OK"));
669 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
670 			alert->Go();
671 		}
672 	}
673 }
674 
675 
676 void
677 InstallerWindow::_LaunchBootManager()
678 {
679 	// TODO: Currently BootManager always tries to install to the "first"
680 	// harddisk. If/when it later supports being installed to a certain
681 	// harddisk, we would have to pass it the disk that contains the target
682 	// partition here.
683 	if (be_roster->Launch(kBootManagerSignature) != B_OK) {
684 		// Try really hard to launch it. It's very likely that this fails,
685 		// when we run from the CD and there is only an incomplete mime
686 		// database for example...
687 		BPath path;
688 		if (find_directory(B_SYSTEM_APPS_DIRECTORY, &path) != B_OK
689 			|| path.Append("BootManager") != B_OK) {
690 			path.SetTo("/boot/system/apps/BootManager");
691 		}
692 		BEntry entry(path.Path());
693 		entry_ref ref;
694 		if (entry.GetRef(&ref) != B_OK || be_roster->Launch(&ref) != B_OK) {
695 			BAlert* alert = new BAlert(
696 				B_TRANSLATE("Failed to launch BootManager"),
697 				B_TRANSLATE("BootManager, the application to configure the "
698 					"Haiku boot menu, could not be launched."),
699 				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
700 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
701 			alert->Go();
702 		}
703 	}
704 }
705 
706 
707 void
708 InstallerWindow::_DisableInterface(bool disable)
709 {
710 	fLaunchDriveSetupButton->SetEnabled(!disable);
711 	fLaunchBootManagerItem->SetEnabled(!disable);
712 	fMakeBootableItem->SetEnabled(!disable);
713 	fSrcMenuField->SetEnabled(!disable);
714 	fDestMenuField->SetEnabled(!disable);
715 }
716 
717 
718 void
719 InstallerWindow::_ScanPartitions()
720 {
721 	_SetStatusMessage(B_TRANSLATE("Scanning for disks" B_UTF8_ELLIPSIS));
722 
723 	BMenuItem *item;
724 	while ((item = fSrcMenu->RemoveItem((int32)0)))
725 		delete item;
726 	while ((item = fDestMenu->RemoveItem((int32)0)))
727 		delete item;
728 
729 	fWorkerThread->ScanDisksPartitions(fSrcMenu, fDestMenu);
730 
731 	if (fSrcMenu->ItemAt(0) != NULL)
732 		_PublishPackages();
733 
734 	_UpdateControls();
735 }
736 
737 
738 void
739 InstallerWindow::_UpdateControls()
740 {
741 	PartitionMenuItem* srcItem = (PartitionMenuItem*)fSrcMenu->FindMarked();
742 	BString label;
743 	if (srcItem) {
744 		label = srcItem->MenuLabel();
745 	} else {
746 		if (fSrcMenu->CountItems() == 0)
747 			label = B_TRANSLATE_COMMENT("<none>", "No partition available");
748 		else
749 			label = ((PartitionMenuItem*)fSrcMenu->ItemAt(0))->MenuLabel();
750 	}
751 	fSrcMenuField->MenuItem()->SetLabel(label.String());
752 
753 	// Disable any unsuitable target items, check if at least one partition
754 	// is suitable.
755 	bool foundOneSuitableTarget = false;
756 	for (int32 i = fDestMenu->CountItems() - 1; i >= 0; i--) {
757 		PartitionMenuItem* dstItem
758 			= (PartitionMenuItem*)fDestMenu->ItemAt(i);
759 		if (srcItem != NULL && dstItem->ID() == srcItem->ID()) {
760 			// Prevent the user from having picked the same partition as source
761 			// and destination.
762 			dstItem->SetEnabled(false);
763 			dstItem->SetMarked(false);
764 		} else
765 			dstItem->SetEnabled(dstItem->IsValidTarget());
766 
767 		if (dstItem->IsEnabled())
768 			foundOneSuitableTarget = true;
769 	}
770 
771 	PartitionMenuItem* dstItem = (PartitionMenuItem*)fDestMenu->FindMarked();
772 	if (dstItem) {
773 		label = dstItem->MenuLabel();
774 	} else {
775 		if (fDestMenu->CountItems() == 0)
776 			label = B_TRANSLATE_COMMENT("<none>", "No partition available");
777 		else
778 			label = B_TRANSLATE("Please choose target");
779 	}
780 	fDestMenuField->MenuItem()->SetLabel(label.String());
781 
782 	BString statusText;
783 	if (srcItem != NULL && dstItem != NULL) {
784 		statusText.SetToFormat(B_TRANSLATE("Press the 'Begin' button to install "
785 			"from '%1s' onto '%2s'."), srcItem->Name(), dstItem->Name());
786 	} else if (srcItem != NULL) {
787 		BString partitionRequiredHaiku = B_TRANSLATE(
788 			"Haiku has to be installed on a partition that uses "
789 			"the Be File System, but there are currently no such "
790 			"partitions available on your system.");
791 
792 		BString partitionRequiredDebranded = B_TRANSLATE(
793 			"This operating system has to be installed on a partition "
794 			"that uses the Be File System, but there are currently "
795 			"no such partitions available on your system.");
796 
797 		if (!foundOneSuitableTarget) {
798 #ifdef HAIKU_DISTRO_COMPATIBILITY_OFFICIAL
799 			statusText.Append(partitionRequiredHaiku);
800 #else
801 			statusText.Append(partitionRequiredDebranded);
802 #endif
803 			statusText.Append(" ");
804 			statusText.Append(B_TRANSLATE(
805 				"Click on 'Set up partitions" B_UTF8_ELLIPSIS
806 				"' to create one."));
807 		} else {
808 			statusText = B_TRANSLATE(
809 				"Choose the disk you want to install "
810 				"onto from the pop-up menu. Then click 'Begin'.");
811 		}
812 	} else if (dstItem != NULL) {
813 		statusText = B_TRANSLATE("Choose the source disk from the "
814 			"pop-up menu. Then click 'Begin'.");
815 	} else {
816 		statusText = B_TRANSLATE("Choose the source and destination disk "
817 			"from the pop-up menus. Then click 'Begin'.");
818 	}
819 
820 	_SetStatusMessage(statusText.String());
821 
822 	fInstallStatus = kReadyForInstall;
823 	fBeginButton->SetLabel(B_TRANSLATE("Begin"));
824 	fBeginButton->SetEnabled(srcItem && dstItem);
825 
826 	// adjust "Write Boot Sector" and "Set up boot menu" buttons
827 	if (dstItem != NULL) {
828 		char buffer[256];
829 		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Write boot sector to '%s'"),
830 			dstItem->Name());
831 		label = buffer;
832 	} else
833 		label = B_TRANSLATE("Write boot sector");
834 	fMakeBootableItem->SetEnabled(dstItem != NULL);
835 	fMakeBootableItem->SetLabel(label.String());
836 // TODO: Once bootman support writing to specific disks, enable this, since
837 // we would pass it the disk which contains the target partition.
838 //	fLaunchBootManagerItem->SetEnabled(dstItem != NULL);
839 
840 	if (!fEncouragedToSetupPartitions && !foundOneSuitableTarget) {
841 		// Focus the users attention on the DriveSetup button
842 		fEncouragedToSetupPartitions = true;
843 		PostMessage(ENCOURAGE_DRIVESETUP);
844 	}
845 }
846 
847 
848 void
849 InstallerWindow::_PublishPackages()
850 {
851 	fPackagesView->Clean();
852 	PartitionMenuItem *item = (PartitionMenuItem *)fSrcMenu->FindMarked();
853 	if (item == NULL)
854 		return;
855 
856 	BPath directory;
857 	BDiskDeviceRoster roster;
858 	BDiskDevice device;
859 	BPartition *partition;
860 	if (roster.GetPartitionWithID(item->ID(), &device, &partition) == B_OK) {
861 		if (partition->GetMountPoint(&directory) != B_OK)
862 			return;
863 	} else if (roster.GetDeviceWithID(item->ID(), &device) == B_OK) {
864 		if (device.GetMountPoint(&directory) != B_OK)
865 			return;
866 	} else
867 		return; // shouldn't happen
868 
869 	directory.Append(kPackagesDirectoryPath);
870 	BDirectory dir(directory.Path());
871 	if (dir.InitCheck() != B_OK)
872 		return;
873 
874 	BEntry packageEntry;
875 	BList packages;
876 	while (dir.GetNextEntry(&packageEntry) == B_OK) {
877 		Package* package = Package::PackageFromEntry(packageEntry);
878 		if (package != NULL)
879 			packages.AddItem(package);
880 	}
881 	packages.SortItems(_ComparePackages);
882 
883 	fPackagesView->AddPackages(packages, new BMessage(PACKAGE_CHECKBOX));
884 	PostMessage(PACKAGE_CHECKBOX);
885 }
886 
887 
888 void
889 InstallerWindow::_SetStatusMessage(const char *text)
890 {
891 	fStatusView->SetText(text);
892 	fStatusView->InvalidateLayout();
893 		// In case the status message makes the text view higher than the
894 		// logo, then we need to resize te whole window to fit it.
895 }
896 
897 
898 void
899 InstallerWindow::_SetCopyEngineCancelSemaphore(sem_id id, bool alreadyLocked)
900 {
901 	if (fCopyEngineCancelSemaphore >= 0) {
902 		if (!alreadyLocked)
903 			acquire_sem(fCopyEngineCancelSemaphore);
904 		delete_sem(fCopyEngineCancelSemaphore);
905 	}
906 	fCopyEngineCancelSemaphore = id;
907 }
908 
909 
910 void
911 InstallerWindow::_QuitCopyEngine(bool askUser)
912 {
913 	if (fCopyEngineCancelSemaphore < 0)
914 		return;
915 
916 	// First of all block the copy engine, so that it doesn't continue
917 	// while the alert is showing, which would be irritating.
918 	acquire_sem(fCopyEngineCancelSemaphore);
919 
920 	bool quit = true;
921 	if (askUser) {
922 		BAlert* alert = new BAlert("cancel",
923 			B_TRANSLATE("Are you sure you want to to stop the installation?"),
924 			B_TRANSLATE_COMMENT("Continue", "In alert after pressing Stop"),
925 			B_TRANSLATE_COMMENT("Stop", "In alert after pressing Stop"), 0,
926 			B_WIDTH_AS_USUAL, B_STOP_ALERT);
927 		alert->SetShortcut(1, B_ESCAPE);
928 		quit = alert->Go() != 0;
929 	}
930 
931 	if (quit) {
932 		// Make it quit by having it's lock fail...
933 		_SetCopyEngineCancelSemaphore(-1, true);
934 	} else
935 		release_sem(fCopyEngineCancelSemaphore);
936 }
937 
938 
939 // #pragma mark -
940 
941 
942 int
943 InstallerWindow::_ComparePackages(const void* firstArg, const void* secondArg)
944 {
945 	const Group* group1 = *static_cast<const Group* const *>(firstArg);
946 	const Group* group2 = *static_cast<const Group* const *>(secondArg);
947 	const Package* package1 = dynamic_cast<const Package*>(group1);
948 	const Package* package2 = dynamic_cast<const Package*>(group2);
949 	int sameGroup = strcmp(group1->GroupName(), group2->GroupName());
950 	if (sameGroup != 0)
951 		return sameGroup;
952 	if (package2 == NULL)
953 		return -1;
954 	if (package1 == NULL)
955 		return 1;
956 	return strcmp(package1->Name(), package2->Name());
957 }
958 
959 
960