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