xref: /haiku/src/apps/installer/InstallerWindow.cpp (revision 4bd0c1066b227cec4b79883bdef697c7a27f2e90)
1 /*
2  * Copyright 2009-2010, Stephan Aßmus <superstippi@gmx.de>
3  * Copyright 2005-2008, Jérôme DUVAL
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include "InstallerWindow.h"
9 
10 #include <stdio.h>
11 #include <strings.h>
12 
13 #include <Alert.h>
14 #include <Application.h>
15 #include <Autolock.h>
16 #include <Box.h>
17 #include <Button.h>
18 #include <Catalog.h>
19 #include <ControlLook.h>
20 #include <Directory.h>
21 #include <FindDirectory.h>
22 #include <LayoutBuilder.h>
23 #include <LayoutUtils.h>
24 #include <Locale.h>
25 #include <MenuBar.h>
26 #include <MenuField.h>
27 #include <Path.h>
28 #include <PopUpMenu.h>
29 #include <Roster.h>
30 #include <Screen.h>
31 #include <ScrollView.h>
32 #include <SeparatorView.h>
33 #include <SpaceLayoutItem.h>
34 #include <StatusBar.h>
35 #include <String.h>
36 #include <TextView.h>
37 #include <TranslationUtils.h>
38 #include <TranslatorFormats.h>
39 
40 #include "tracker_private.h"
41 
42 #include "DialogPane.h"
43 #include "InstallerDefs.h"
44 #include "PackageViews.h"
45 #include "PartitionMenuItem.h"
46 #include "WorkerThread.h"
47 
48 
49 #undef B_TRANSLATION_CONTEXT
50 #define B_TRANSLATION_CONTEXT "InstallerWindow"
51 
52 
53 static const char* kDriveSetupSignature = "application/x-vnd.Haiku-DriveSetup";
54 static const char* kBootManagerSignature
55 	= "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(-2000, -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->SetInsets(10, 0, 10, 0);
178 	fStatusView->MakeEditable(false);
179 	fStatusView->MakeSelectable(false);
180 
181 	BSize logoSize = logoView->MinSize();
182 	logoView->SetExplicitMaxSize(logoSize);
183 	fStatusView->SetExplicitMinSize(BSize(logoSize.width * 0.8,
184 		B_SIZE_UNSET));
185 
186 	// Explicitly create group view to set the background white in case
187 	// height resizing is needed for the status view
188 	BGroupView* logoGroup = new BGroupView(B_HORIZONTAL, 0);
189 	logoGroup->SetViewColor(255, 255, 255);
190 	logoGroup->AddChild(logoView);
191 	logoGroup->AddChild(fStatusView);
192 
193 	fDestMenu = new BPopUpMenu(B_TRANSLATE("scanning" B_UTF8_ELLIPSIS),
194 		true, false);
195 	fSrcMenu = new BPopUpMenu(B_TRANSLATE("scanning" B_UTF8_ELLIPSIS),
196 		true, false);
197 
198 	fSrcMenuField = new BMenuField("srcMenuField",
199 		B_TRANSLATE("Install from:"), fSrcMenu);
200 	fSrcMenuField->SetAlignment(B_ALIGN_RIGHT);
201 
202 	fDestMenuField = new BMenuField("destMenuField", B_TRANSLATE("Onto:"),
203 		fDestMenu);
204 	fDestMenuField->SetAlignment(B_ALIGN_RIGHT);
205 
206 	fPackagesSwitch = new PaneSwitch("options_button");
207 	fPackagesSwitch->SetLabels(B_TRANSLATE("Hide optional packages"),
208 		B_TRANSLATE("Show optional packages"));
209 	fPackagesSwitch->SetMessage(new BMessage(SHOW_BOTTOM_MESSAGE));
210 	fPackagesSwitch->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
211 		B_SIZE_UNSET));
212 	fPackagesSwitch->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
213 		B_ALIGN_TOP));
214 
215 	fPackagesView = new PackagesView("packages_view");
216 	BScrollView* packagesScrollView = new BScrollView("packagesScroll",
217 		fPackagesView, B_WILL_DRAW, false, true);
218 
219 	const char* requiredDiskSpaceString
220 		= B_TRANSLATE("Additional disk space required: 0.0 KiB");
221 	fSizeView = new BStringView("size_view", requiredDiskSpaceString);
222 	fSizeView->SetAlignment(B_ALIGN_RIGHT);
223 	fSizeView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
224 	fSizeView->SetExplicitAlignment(
225 		BAlignment(B_ALIGN_RIGHT, B_ALIGN_MIDDLE));
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 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
254 		.Add(mainMenu)
255 		.Add(logoGroup)
256 		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
257 		.AddGroup(B_VERTICAL, B_USE_ITEM_SPACING)
258 			.SetInsets(B_USE_WINDOW_SPACING)
259 			.AddGrid(new BGridView(B_USE_ITEM_SPACING, B_USE_ITEM_SPACING))
260 				.Add(fSrcMenuField->CreateLabelLayoutItem(), 0, 0)
261 				.Add(fSrcMenuField->CreateMenuBarLayoutItem(), 1, 0)
262 				.Add(fDestMenuField->CreateLabelLayoutItem(), 0, 1)
263 				.Add(fDestMenuField->CreateMenuBarLayoutItem(), 1, 1)
264 
265 				.Add(BSpaceLayoutItem::CreateVerticalStrut(5), 0, 2, 2)
266 
267 				.Add(fPackagesSwitch, 0, 3, 2)
268 				.Add(packagesScrollView, 0, 4, 2)
269 				.Add(fProgressBar, 0, 5, 2)
270 				.Add(fSizeView, 0, 6, 2)
271 			.End()
272 
273 			.AddGroup(B_HORIZONTAL, B_USE_WINDOW_SPACING)
274 				.Add(fLaunchDriveSetupButton)
275 				.AddGlue()
276 				.Add(fBeginButton);
277 
278 	// Make the optional packages and progress bar invisible on start
279 	fPackagesLayoutItem = layout_item_for(packagesScrollView);
280 	fPkgSwitchLayoutItem = layout_item_for(fPackagesSwitch);
281 	fSizeViewLayoutItem = layout_item_for(fSizeView);
282 	fProgressLayoutItem = layout_item_for(fProgressBar);
283 
284 	fPackagesLayoutItem->SetVisible(false);
285 	fSizeViewLayoutItem->SetVisible(false);
286 	fProgressLayoutItem->SetVisible(false);
287 
288 	// Setup tool tips for the non-obvious features
289 	fLaunchDriveSetupButton->SetToolTip(
290 		B_TRANSLATE("Launch the DriveSetup utility to partition\n"
291 		"available hard drives and other media.\n"
292 		"Partitions can be initialized with the\n"
293 		"Be File System needed for a Haiku boot\n"
294 		"partition."));
295 //	fLaunchBootManagerItem->SetToolTip(
296 //		B_TRANSLATE("Install or uninstall the Haiku boot menu, which allows "
297 //		"to choose an operating system to boot when the computer starts.\n"
298 //		"If this computer already has a boot manager such as GRUB installed, "
299 //		"it is better to add Haiku to that menu than to overwrite it."));
300 //	fMakeBootableItem->SetToolTip(
301 //		B_TRANSLATE("Writes the Haiku boot code to the partition start\n"
302 //		"sector. This step is automatically performed by\n"
303 //		"the installation, but you can manually make a\n"
304 //		"partition bootable in case you do not need to\n"
305 //		"perform an installation."));
306 
307 	// finish creating window
308 	if (!be_roster->IsRunning(kDeskbarSignature))
309 		SetFlags(Flags() | B_NOT_MINIMIZABLE);
310 
311 	CenterOnScreen();
312 	Show();
313 
314 	// Register to receive notifications when apps launch or quit...
315 	be_roster->StartWatching(this);
316 	// ... and check the two we are interested in.
317 	fDriveSetupLaunched = be_roster->IsRunning(kDriveSetupSignature);
318 	fBootManagerLaunched = be_roster->IsRunning(kBootManagerSignature);
319 
320 	if (Lock()) {
321 		fLaunchDriveSetupButton->SetEnabled(!fDriveSetupLaunched);
322 		fLaunchBootManagerItem->SetEnabled(!fBootManagerLaunched);
323 		Unlock();
324 	}
325 
326 	PostMessage(START_SCAN);
327 }
328 
329 
330 InstallerWindow::~InstallerWindow()
331 {
332 	_SetCopyEngineCancelSemaphore(-1);
333 	be_roster->StopWatching(this);
334 }
335 
336 
337 void
338 InstallerWindow::MessageReceived(BMessage *msg)
339 {
340 	switch (msg->what) {
341 		case MSG_RESET:
342 		{
343 			_SetCopyEngineCancelSemaphore(-1);
344 
345 			status_t error;
346 			if (msg->FindInt32("error", &error) == B_OK) {
347 				char errorMessage[2048];
348 				snprintf(errorMessage, sizeof(errorMessage),
349 					B_TRANSLATE("An error was encountered and the "
350 					"installation was not completed:\n\n"
351 					"Error:  %s"), strerror(error));
352 				BAlert* alert = new BAlert("error", errorMessage, B_TRANSLATE("OK"));
353 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
354 				alert->Go();
355 			}
356 
357 			_DisableInterface(false);
358 
359 			fProgressLayoutItem->SetVisible(false);
360 			fPkgSwitchLayoutItem->SetVisible(true);
361 			_ShowOptionalPackages();
362 			_UpdateControls();
363 			break;
364 		}
365 		case START_SCAN:
366 			_ScanPartitions();
367 			break;
368 		case BEGIN_MESSAGE:
369 			switch (fInstallStatus) {
370 				case kReadyForInstall:
371 				{
372 					// get source and target
373 					PartitionMenuItem* targetItem
374 						= (PartitionMenuItem*)fDestMenu->FindMarked();
375 					PartitionMenuItem* srcItem
376 						= (PartitionMenuItem*)fSrcMenu->FindMarked();
377 					if (srcItem == NULL || targetItem == NULL)
378 						break;
379 
380 					_SetCopyEngineCancelSemaphore(create_sem(1,
381 						"copy engine cancel"));
382 
383 					BList* list = new BList();
384 					int32 size = 0;
385 					fPackagesView->GetPackagesToInstall(list, &size);
386 					fWorkerThread->SetLock(fCopyEngineCancelSemaphore);
387 					fWorkerThread->SetPackagesList(list);
388 					fWorkerThread->SetSpaceRequired(size);
389 					fInstallStatus = kInstalling;
390 					fWorkerThread->StartInstall(srcItem->ID(),
391 						targetItem->ID());
392 					fBeginButton->SetLabel(B_TRANSLATE("Stop"));
393 					_DisableInterface(true);
394 
395 					fProgressBar->SetTo(0.0, NULL, NULL);
396 
397 					fPkgSwitchLayoutItem->SetVisible(false);
398 					fPackagesLayoutItem->SetVisible(false);
399 					fSizeViewLayoutItem->SetVisible(false);
400 					fProgressLayoutItem->SetVisible(true);
401 					break;
402 				}
403 				case kInstalling:
404 				{
405 					_QuitCopyEngine(true);
406 					break;
407 				}
408 				case kFinished:
409 					PostMessage(B_QUIT_REQUESTED);
410 					break;
411 				case kCancelled:
412 					break;
413 			}
414 			break;
415 		case SHOW_BOTTOM_MESSAGE:
416 			_ShowOptionalPackages();
417 			break;
418 		case SOURCE_PARTITION:
419 			_PublishPackages();
420 			_UpdateControls();
421 			break;
422 		case TARGET_PARTITION:
423 			_UpdateControls();
424 			break;
425 		case LAUNCH_DRIVE_SETUP:
426 			_LaunchDriveSetup();
427 			break;
428 		case LAUNCH_BOOTMAN:
429 			_LaunchBootManager();
430 			break;
431 		case PACKAGE_CHECKBOX:
432 		{
433 			char buffer[15];
434 			fPackagesView->GetTotalSizeAsString(buffer, sizeof(buffer));
435 			char string[256];
436 			snprintf(string, sizeof(string),
437 				B_TRANSLATE("Additional disk space required: %s"), buffer);
438 			fSizeView->SetText(string);
439 			break;
440 		}
441 		case ENCOURAGE_DRIVESETUP:
442 		{
443 			BAlert* alert = new BAlert("use drive setup", B_TRANSLATE("No partitions have "
444 				"been found that are suitable for installation. Please set "
445 				"up partitions and initialize at least one partition with the "
446 				"Be File System."), B_TRANSLATE("OK"));
447 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
448 			alert->Go();
449 			break;
450 		}
451 		case MSG_STATUS_MESSAGE:
452 		{
453 // TODO: Was this supposed to prevent status messages still arriving
454 // after the copy engine was shut down?
455 //			if (fInstallStatus != kInstalling)
456 //				break;
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 			char status[1024];
500 			if (be_roster->IsRunning(kDeskbarSignature)) {
501 				fBeginButton->SetLabel(B_TRANSLATE("Quit"));
502 				snprintf(status, sizeof(status), 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 				snprintf(status, sizeof(status), 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);
519 			fInstallStatus = kFinished;
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.
567 					if (scanPartitions)
568 						_ScanPartitions();
569 					else
570 						_UpdateControls();
571 				}
572 			}
573 			break;
574 		}
575 		case MSG_WRITE_BOOT_SECTOR:
576 			fWorkerThread->WriteBootSector(fDestMenu);
577 			break;
578 
579 		default:
580 			BWindow::MessageReceived(msg);
581 			break;
582 	}
583 }
584 
585 
586 bool
587 InstallerWindow::QuitRequested()
588 {
589 	if ((Flags() & B_NOT_MINIMIZABLE) != 0) {
590 		// This means Deskbar is not running, i.e. Installer is the only
591 		// thing on the screen and we will reboot the machine once it quits.
592 
593 		if (fDriveSetupLaunched && fBootManagerLaunched) {
594 			BAlert* alert = new BAlert(B_TRANSLATE("Quit Boot Manager and "
595 				"DriveSetup"),	B_TRANSLATE("Please close the Boot Manager "
596 				"and DriveSetup windows before closing the Installer window."),
597 				B_TRANSLATE("OK"));
598 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
599 			alert->Go();
600 			return false;
601 		}
602 		if (fDriveSetupLaunched) {
603 			BAlert* alert = new BAlert(B_TRANSLATE("Quit DriveSetup"),
604 				B_TRANSLATE("Please close the DriveSetup window before "
605 				"closing the Installer window."), B_TRANSLATE("OK"));
606 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
607 			alert->Go();
608 			return false;
609 		}
610 		if (fBootManagerLaunched) {
611 			BAlert* alert = new BAlert(B_TRANSLATE("Quit Boot Manager"),
612 				B_TRANSLATE("Please close the Boot Manager window before "
613 				"closing the Installer window."), B_TRANSLATE("OK"));
614 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
615 			alert->Go();
616 			return false;
617 		}
618 		if (fInstallStatus != kFinished) {
619 			BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("Installer"),
620 				B_TRANSLATE("Are you sure you want to abort the "
621 					"installation and restart the system?"),
622 				B_TRANSLATE("Cancel"), B_TRANSLATE("Restart system"), NULL,
623 				B_WIDTH_AS_USUAL, B_STOP_ALERT);
624 			alert->SetShortcut(0, B_ESCAPE);
625 			if (alert->Go() == 0)
626 				return false;
627 		}
628 	} else if (fInstallStatus == kInstalling) {
629 			BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("Installer"),
630 				B_TRANSLATE("Are you sure you want to abort the installation?"),
631 				B_TRANSLATE("Cancel"), B_TRANSLATE("Abort"), NULL,
632 				B_WIDTH_AS_USUAL, B_STOP_ALERT);
633 			alert->SetShortcut(0, B_ESCAPE);
634 			if (alert->Go() == 0)
635 				return false;
636 	}
637 
638 	_QuitCopyEngine(false);
639 	fWorkerThread->PostMessage(B_QUIT_REQUESTED);
640 	be_app->PostMessage(B_QUIT_REQUESTED);
641 	return true;
642 }
643 
644 
645 // #pragma mark -
646 
647 
648 void
649 InstallerWindow::_ShowOptionalPackages()
650 {
651 	if (fPackagesLayoutItem && fSizeViewLayoutItem) {
652 		fPackagesLayoutItem->SetVisible(fPackagesSwitch->Value());
653 		fSizeViewLayoutItem->SetVisible(fPackagesSwitch->Value());
654 	}
655 }
656 
657 
658 void
659 InstallerWindow::_LaunchDriveSetup()
660 {
661 	if (be_roster->Launch(kDriveSetupSignature) != B_OK) {
662 		// Try really hard to launch it. It's very likely that this fails,
663 		// when we run from the CD and there is only an incomplete mime
664 		// database for example...
665 		BPath path;
666 		if (find_directory(B_SYSTEM_APPS_DIRECTORY, &path) != B_OK
667 			|| path.Append("DriveSetup") != B_OK) {
668 			path.SetTo("/boot/system/apps/DriveSetup");
669 		}
670 		BEntry entry(path.Path());
671 		entry_ref ref;
672 		if (entry.GetRef(&ref) != B_OK || be_roster->Launch(&ref) != B_OK) {
673 			BAlert* alert = new BAlert("error", B_TRANSLATE("DriveSetup, the "
674 				"application to configure disk partitions, could not be "
675 				"launched."), B_TRANSLATE("OK"));
676 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
677 			alert->Go();
678 		}
679 	}
680 }
681 
682 
683 void
684 InstallerWindow::_LaunchBootManager()
685 {
686 	// TODO: Currently BootManager always tries to install to the "first"
687 	// harddisk. If/when it later supports being installed to a certain
688 	// harddisk, we would have to pass it the disk that contains the target
689 	// partition here.
690 	if (be_roster->Launch(kBootManagerSignature) != B_OK) {
691 		// Try really hard to launch it. It's very likely that this fails,
692 		// when we run from the CD and there is only an incomplete mime
693 		// database for example...
694 		BPath path;
695 		if (find_directory(B_SYSTEM_APPS_DIRECTORY, &path) != B_OK
696 			|| path.Append("BootManager") != B_OK) {
697 			path.SetTo("/boot/system/apps/BootManager");
698 		}
699 		BEntry entry(path.Path());
700 		entry_ref ref;
701 		if (entry.GetRef(&ref) != B_OK || be_roster->Launch(&ref) != B_OK) {
702 			BAlert* alert = new BAlert(
703 				B_TRANSLATE("Failed to launch Boot Manager"),
704 				B_TRANSLATE("Boot Manager, the application to configure the "
705 					"Haiku boot menu, could not be launched."),
706 				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
707 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
708 			alert->Go();
709 		}
710 	}
711 }
712 
713 
714 void
715 InstallerWindow::_DisableInterface(bool disable)
716 {
717 	fLaunchDriveSetupButton->SetEnabled(!disable);
718 	fLaunchBootManagerItem->SetEnabled(!disable);
719 	fMakeBootableItem->SetEnabled(!disable);
720 	fSrcMenuField->SetEnabled(!disable);
721 	fDestMenuField->SetEnabled(!disable);
722 }
723 
724 
725 void
726 InstallerWindow::_ScanPartitions()
727 {
728 	_SetStatusMessage(B_TRANSLATE("Scanning for disks" B_UTF8_ELLIPSIS));
729 
730 	BMenuItem *item;
731 	while ((item = fSrcMenu->RemoveItem((int32)0)))
732 		delete item;
733 	while ((item = fDestMenu->RemoveItem((int32)0)))
734 		delete item;
735 
736 	fWorkerThread->ScanDisksPartitions(fSrcMenu, fDestMenu);
737 
738 	if (fSrcMenu->ItemAt(0) != NULL)
739 		_PublishPackages();
740 
741 	_UpdateControls();
742 }
743 
744 
745 void
746 InstallerWindow::_UpdateControls()
747 {
748 	PartitionMenuItem* srcItem = (PartitionMenuItem*)fSrcMenu->FindMarked();
749 	BString label;
750 	if (srcItem) {
751 		label = srcItem->MenuLabel();
752 	} else {
753 		if (fSrcMenu->CountItems() == 0)
754 			label = B_TRANSLATE_COMMENT("<none>", "No partition available");
755 		else
756 			label = ((PartitionMenuItem*)fSrcMenu->ItemAt(0))->MenuLabel();
757 	}
758 	fSrcMenuField->MenuItem()->SetLabel(label.String());
759 
760 	// Disable any unsuitable target items, check if at least one partition
761 	// is suitable.
762 	bool foundOneSuitableTarget = false;
763 	for (int32 i = fDestMenu->CountItems() - 1; i >= 0; i--) {
764 		PartitionMenuItem* dstItem
765 			= (PartitionMenuItem*)fDestMenu->ItemAt(i);
766 		if (srcItem != NULL && dstItem->ID() == srcItem->ID()) {
767 			// Prevent the user from having picked the same partition as source
768 			// and destination.
769 			dstItem->SetEnabled(false);
770 			dstItem->SetMarked(false);
771 		} else
772 			dstItem->SetEnabled(dstItem->IsValidTarget());
773 
774 		if (dstItem->IsEnabled())
775 			foundOneSuitableTarget = true;
776 	}
777 
778 	PartitionMenuItem* dstItem = (PartitionMenuItem*)fDestMenu->FindMarked();
779 	if (dstItem) {
780 		label = dstItem->MenuLabel();
781 	} else {
782 		if (fDestMenu->CountItems() == 0)
783 			label = B_TRANSLATE_COMMENT("<none>", "No partition available");
784 		else
785 			label = B_TRANSLATE("Please choose target");
786 	}
787 	fDestMenuField->MenuItem()->SetLabel(label.String());
788 
789 	if (srcItem != NULL && dstItem != NULL) {
790 		char message[255];
791 		sprintf(message, B_TRANSLATE("Press the Begin button to install from "
792 		"'%1s' onto '%2s'."), srcItem->Name(), dstItem->Name());
793 		_SetStatusMessage(message);
794 	} else if (srcItem != NULL) {
795 		_SetStatusMessage(B_TRANSLATE("Choose the disk you want to install "
796 			"onto from the pop-up menu. Then click \"Begin\"."));
797 	} else if (dstItem != NULL) {
798 		_SetStatusMessage(B_TRANSLATE("Choose the source disk from the "
799 			"pop-up menu. Then click \"Begin\"."));
800 	} else {
801 		_SetStatusMessage(B_TRANSLATE("Choose the source and destination disk "
802 			"from the pop-up menus. Then click \"Begin\"."));
803 	}
804 
805 	fInstallStatus = kReadyForInstall;
806 	fBeginButton->SetLabel(B_TRANSLATE("Begin"));
807 	fBeginButton->SetEnabled(srcItem && dstItem);
808 
809 	// adjust "Write Boot Sector" and "Set up boot menu" buttons
810 	if (dstItem != NULL) {
811 		char buffer[256];
812 		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Write boot sector to '%s'"),
813 			dstItem->Name());
814 		label = buffer;
815 	} else
816 		label = B_TRANSLATE("Write boot sector");
817 	fMakeBootableItem->SetEnabled(dstItem != NULL);
818 	fMakeBootableItem->SetLabel(label.String());
819 // TODO: Once bootman support writing to specific disks, enable this, since
820 // we would pass it the disk which contains the target partition.
821 //	fLaunchBootManagerItem->SetEnabled(dstItem != NULL);
822 
823 	if (!fEncouragedToSetupPartitions && !foundOneSuitableTarget) {
824 		// Focus the users attention on the DriveSetup button
825 		fEncouragedToSetupPartitions = true;
826 		PostMessage(ENCOURAGE_DRIVESETUP);
827 	}
828 }
829 
830 
831 void
832 InstallerWindow::_PublishPackages()
833 {
834 	fPackagesView->Clean();
835 	PartitionMenuItem *item = (PartitionMenuItem *)fSrcMenu->FindMarked();
836 	if (item == NULL)
837 		return;
838 
839 	BPath directory;
840 	BDiskDeviceRoster roster;
841 	BDiskDevice device;
842 	BPartition *partition;
843 	if (roster.GetPartitionWithID(item->ID(), &device, &partition) == B_OK) {
844 		if (partition->GetMountPoint(&directory) != B_OK)
845 			return;
846 	} else if (roster.GetDeviceWithID(item->ID(), &device) == B_OK) {
847 		if (device.GetMountPoint(&directory) != B_OK)
848 			return;
849 	} else
850 		return; // shouldn't happen
851 
852 	directory.Append(kPackagesDirectoryPath);
853 	BDirectory dir(directory.Path());
854 	if (dir.InitCheck() != B_OK)
855 		return;
856 
857 	BEntry packageEntry;
858 	BList packages;
859 	while (dir.GetNextEntry(&packageEntry) == B_OK) {
860 		Package* package = Package::PackageFromEntry(packageEntry);
861 		if (package != NULL)
862 			packages.AddItem(package);
863 	}
864 	packages.SortItems(_ComparePackages);
865 
866 	fPackagesView->AddPackages(packages, new BMessage(PACKAGE_CHECKBOX));
867 	PostMessage(PACKAGE_CHECKBOX);
868 }
869 
870 
871 void
872 InstallerWindow::_SetStatusMessage(const char *text)
873 {
874 	fStatusView->SetText(text);
875 
876 	// Make the status view taller if needed
877 	BSize size = fStatusView->ExplicitMinSize();
878 	float heightNeeded = fStatusView->TextHeight(0, fStatusView->CountLines()) + 15.0;
879 	if (heightNeeded > size.height)
880 		fStatusView->SetExplicitMinSize(BSize(size.width, heightNeeded));
881 }
882 
883 
884 void
885 InstallerWindow::_SetCopyEngineCancelSemaphore(sem_id id, bool alreadyLocked)
886 {
887 	if (fCopyEngineCancelSemaphore >= 0) {
888 		if (!alreadyLocked)
889 			acquire_sem(fCopyEngineCancelSemaphore);
890 		delete_sem(fCopyEngineCancelSemaphore);
891 	}
892 	fCopyEngineCancelSemaphore = id;
893 }
894 
895 
896 void
897 InstallerWindow::_QuitCopyEngine(bool askUser)
898 {
899 	if (fCopyEngineCancelSemaphore < 0)
900 		return;
901 
902 	// First of all block the copy engine, so that it doesn't continue
903 	// while the alert is showing, which would be irritating.
904 	acquire_sem(fCopyEngineCancelSemaphore);
905 
906 	bool quit = true;
907 	if (askUser) {
908 		BAlert* alert = new BAlert("cancel",
909 			B_TRANSLATE("Are you sure you want to to stop the installation?"),
910 			B_TRANSLATE_COMMENT("Continue", "In alert after pressing Stop"),
911 			B_TRANSLATE_COMMENT("Stop", "In alert after pressing Stop"), 0,
912 			B_WIDTH_AS_USUAL, B_STOP_ALERT);
913 		alert->SetShortcut(1, B_ESCAPE);
914 		quit = alert->Go() != 0;
915 	}
916 
917 	if (quit) {
918 		// Make it quit by having it's lock fail...
919 		_SetCopyEngineCancelSemaphore(-1, true);
920 	} else
921 		release_sem(fCopyEngineCancelSemaphore);
922 }
923 
924 
925 // #pragma mark -
926 
927 
928 int
929 InstallerWindow::_ComparePackages(const void* firstArg, const void* secondArg)
930 {
931 	const Group* group1 = *static_cast<const Group* const *>(firstArg);
932 	const Group* group2 = *static_cast<const Group* const *>(secondArg);
933 	const Package* package1 = dynamic_cast<const Package*>(group1);
934 	const Package* package2 = dynamic_cast<const Package*>(group2);
935 	int sameGroup = strcmp(group1->GroupName(), group2->GroupName());
936 	if (sameGroup != 0)
937 		return sameGroup;
938 	if (package2 == NULL)
939 		return -1;
940 	if (package1 == NULL)
941 		return 1;
942 	return strcmp(package1->Name(), package2->Name());
943 }
944 
945 
946