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