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