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", "number of files copied"),
472						(long int)currentCount, (long int)maximumCount);
473					trailingLabel << buffer;
474				} else {
475					trailingLabel <<
476						B_TRANSLATE_COMMENT("?? of ??", "Unknown progress");
477				}
478				fProgressBar->SetTo(progress, currentItem,
479					trailingLabel.String());
480			} else {
481				const char *status;
482				if (msg->FindString("status", &status) == B_OK) {
483					fLastStatus = fStatusView->Text();
484					_SetStatusMessage(status);
485				} else
486					_SetStatusMessage(fLastStatus.String());
487			}
488			break;
489		}
490		case MSG_INSTALL_FINISHED:
491		{
492
493			_SetCopyEngineCancelSemaphore(-1);
494
495			PartitionMenuItem* dstItem
496				= (PartitionMenuItem*)fDestMenu->FindMarked();
497
498			BString status;
499			if (be_roster->IsRunning(kDeskbarSignature)) {
500				fBeginButton->SetLabel(B_TRANSLATE("Quit"));
501				status.SetToFormat(B_TRANSLATE("Installation "
502					"completed. Boot sector has been written to '%s'. Press "
503					"'Quit' to leave the Installer or choose a new target "
504					"volume to perform another installation."),
505					dstItem ? dstItem->Name() : B_TRANSLATE_COMMENT("???",
506						"Unknown partition name"));
507			} else {
508				fBeginButton->SetLabel(B_TRANSLATE("Restart"));
509				status.SetToFormat(B_TRANSLATE("Installation "
510					"completed. Boot sector has been written to '%s'. Press "
511					"'Restart' to restart the computer or choose a new target "
512					"volume to perform another installation."),
513					dstItem ? dstItem->Name() : B_TRANSLATE_COMMENT("???",
514						"Unknown partition name"));
515			}
516
517			_SetStatusMessage(status.String());
518			fInstallStatus = kFinished;
519
520			_DisableInterface(false);
521			fProgressLayoutItem->SetVisible(false);
522			fPkgSwitchLayoutItem->SetVisible(true);
523			_ShowOptionalPackages();
524			break;
525		}
526		case B_SOME_APP_LAUNCHED:
527		case B_SOME_APP_QUIT:
528		{
529			const char *signature;
530			if (msg->FindString("be:signature", &signature) != B_OK)
531				break;
532			bool isDriveSetup = !strcasecmp(signature, kDriveSetupSignature);
533			bool isBootManager = !strcasecmp(signature, kBootManagerSignature);
534			if (isDriveSetup || isBootManager) {
535				bool scanPartitions = false;
536				if (isDriveSetup) {
537					bool launched = msg->what == B_SOME_APP_LAUNCHED;
538					// We need to scan partitions if DriveSetup has quit.
539					scanPartitions = fDriveSetupLaunched && !launched;
540					fDriveSetupLaunched = launched;
541				}
542				if (isBootManager)
543					fBootManagerLaunched = msg->what == B_SOME_APP_LAUNCHED;
544
545				fBeginButton->SetEnabled(
546					!fDriveSetupLaunched && !fBootManagerLaunched);
547				_DisableInterface(fDriveSetupLaunched || fBootManagerLaunched);
548				if (fDriveSetupLaunched && fBootManagerLaunched) {
549					_SetStatusMessage(B_TRANSLATE("Running BootManager 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 BootManager"
560						B_UTF8_ELLIPSIS
561						"\n\nClose BootManager to continue with the "
562						"installation."));
563				} else {
564					// If neither DriveSetup nor Bootman is running, we need
565					// to scan partitions in case DriveSetup has quit, or
566					// we need to update the guidance message, unless install
567					// was already finished.
568					if (scanPartitions)
569						_ScanPartitions();
570					else if (fInstallStatus != kFinished)
571						_UpdateControls();
572					else
573						PostMessage(MSG_INSTALL_FINISHED);
574				}
575			}
576			break;
577		}
578		case MSG_WRITE_BOOT_SECTOR:
579			fWorkerThread->WriteBootSector(fDestMenu);
580			break;
581
582		default:
583			BWindow::MessageReceived(msg);
584			break;
585	}
586}
587
588
589bool
590InstallerWindow::QuitRequested()
591{
592	if ((Flags() & B_NOT_MINIMIZABLE) != 0) {
593		// This means Deskbar is not running, i.e. Installer is the only
594		// thing on the screen and we will reboot the machine once it quits.
595
596		if (fDriveSetupLaunched && fBootManagerLaunched) {
597			BAlert* alert = new BAlert(B_TRANSLATE("Quit BootManager and "
598				"DriveSetup"),	B_TRANSLATE("Please close the BootManager "
599				"and DriveSetup windows before closing the Installer window."),
600				B_TRANSLATE("OK"));
601			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
602			alert->Go();
603			return false;
604		}
605		if (fDriveSetupLaunched) {
606			BAlert* alert = new BAlert(B_TRANSLATE("Quit DriveSetup"),
607				B_TRANSLATE("Please close the DriveSetup window before "
608				"closing the Installer window."), B_TRANSLATE("OK"));
609			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
610			alert->Go();
611			return false;
612		}
613		if (fBootManagerLaunched) {
614			BAlert* alert = new BAlert(B_TRANSLATE("Quit BootManager"),
615				B_TRANSLATE("Please close the BootManager window before "
616				"closing the Installer window."), B_TRANSLATE("OK"));
617			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
618			alert->Go();
619			return false;
620		}
621		if (fInstallStatus != kFinished) {
622			BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("Installer"),
623				B_TRANSLATE("Are you sure you want to stop the installation?"),
624				B_TRANSLATE("Cancel"), B_TRANSLATE("Stop"), NULL,
625				B_WIDTH_AS_USUAL, B_STOP_ALERT);
626			alert->SetShortcut(0, B_ESCAPE);
627			if (alert->Go() == 0)
628				return false;
629		}
630	} else if (fInstallStatus == kInstalling) {
631			BAlert* alert = new BAlert(B_TRANSLATE_SYSTEM_NAME("Installer"),
632				B_TRANSLATE("The installation is not complete yet!\n"
633                                "Are you sure you want to stop it?"),
634				B_TRANSLATE("Cancel"), B_TRANSLATE("Stop"), NULL,
635				B_WIDTH_AS_USUAL, B_STOP_ALERT);
636			alert->SetShortcut(0, B_ESCAPE);
637			if (alert->Go() == 0)
638				return false;
639	}
640
641	_QuitCopyEngine(false);
642
643	BMessage quitWithInstallStatus(B_QUIT_REQUESTED);
644	quitWithInstallStatus.AddBool("install_complete",
645		fInstallStatus == kFinished);
646
647	fWorkerThread->PostMessage(&quitWithInstallStatus);
648	be_app->PostMessage(&quitWithInstallStatus);
649	return true;
650}
651
652
653// #pragma mark -
654
655
656void
657InstallerWindow::_ShowOptionalPackages()
658{
659	if (fPackagesLayoutItem && fSizeViewLayoutItem) {
660		fPackagesLayoutItem->SetVisible(fPackagesSwitch->Value());
661		fSizeViewLayoutItem->SetVisible(fPackagesSwitch->Value());
662	}
663}
664
665
666void
667InstallerWindow::_LaunchDriveSetup()
668{
669	if (be_roster->Launch(kDriveSetupSignature) != B_OK) {
670		// Try really hard to launch it. It's very likely that this fails,
671		// when we run from the CD and there is only an incomplete mime
672		// database for example...
673		BPath path;
674		if (find_directory(B_SYSTEM_APPS_DIRECTORY, &path) != B_OK
675			|| path.Append("DriveSetup") != B_OK) {
676			path.SetTo("/boot/system/apps/DriveSetup");
677		}
678		BEntry entry(path.Path());
679		entry_ref ref;
680		if (entry.GetRef(&ref) != B_OK || be_roster->Launch(&ref) != B_OK) {
681			BAlert* alert = new BAlert("error", B_TRANSLATE("DriveSetup, the "
682				"application to configure disk partitions, could not be "
683				"launched."), B_TRANSLATE("OK"));
684			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
685			alert->Go();
686		}
687	}
688}
689
690
691void
692InstallerWindow::_LaunchBootManager()
693{
694	// TODO: Currently BootManager always tries to install to the "first"
695	// harddisk. If/when it later supports being installed to a certain
696	// harddisk, we would have to pass it the disk that contains the target
697	// partition here.
698	if (be_roster->Launch(kBootManagerSignature) != B_OK) {
699		// Try really hard to launch it. It's very likely that this fails,
700		// when we run from the CD and there is only an incomplete mime
701		// database for example...
702		BPath path;
703		if (find_directory(B_SYSTEM_APPS_DIRECTORY, &path) != B_OK
704			|| path.Append("BootManager") != B_OK) {
705			path.SetTo("/boot/system/apps/BootManager");
706		}
707		BEntry entry(path.Path());
708		entry_ref ref;
709		if (entry.GetRef(&ref) != B_OK || be_roster->Launch(&ref) != B_OK) {
710			BAlert* alert = new BAlert(
711				B_TRANSLATE("Failed to launch BootManager"),
712				B_TRANSLATE("BootManager, the application to configure the "
713					"Haiku boot menu, could not be launched."),
714				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
715			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
716			alert->Go();
717		}
718	}
719}
720
721
722void
723InstallerWindow::_DisableInterface(bool disable)
724{
725	fLaunchDriveSetupButton->SetEnabled(!disable);
726	fLaunchBootManagerItem->SetEnabled(!disable);
727	fMakeBootableItem->SetEnabled(!disable);
728	fSrcMenuField->SetEnabled(!disable);
729	fDestMenuField->SetEnabled(!disable);
730}
731
732
733void
734InstallerWindow::_ScanPartitions()
735{
736	_SetStatusMessage(B_TRANSLATE("Scanning for disks" B_UTF8_ELLIPSIS));
737
738	BMenuItem *item;
739	while ((item = fSrcMenu->RemoveItem((int32)0)))
740		delete item;
741	while ((item = fDestMenu->RemoveItem((int32)0)))
742		delete item;
743
744	fWorkerThread->ScanDisksPartitions(fSrcMenu, fDestMenu);
745
746	if (fSrcMenu->ItemAt(0) != NULL)
747		_PublishPackages();
748
749	_UpdateControls();
750}
751
752
753void
754InstallerWindow::_UpdateControls()
755{
756	PartitionMenuItem* srcItem = (PartitionMenuItem*)fSrcMenu->FindMarked();
757	BString label;
758	if (srcItem) {
759		label = srcItem->MenuLabel();
760	} else {
761		if (fSrcMenu->CountItems() == 0)
762			label = B_TRANSLATE_COMMENT("<none>", "No partition available");
763		else
764			label = ((PartitionMenuItem*)fSrcMenu->ItemAt(0))->MenuLabel();
765	}
766	fSrcMenuField->MenuItem()->SetLabel(label.String());
767
768	// Disable any unsuitable target items, check if at least one partition
769	// is suitable.
770	bool foundOneSuitableTarget = false;
771	for (int32 i = fDestMenu->CountItems() - 1; i >= 0; i--) {
772		PartitionMenuItem* dstItem
773			= (PartitionMenuItem*)fDestMenu->ItemAt(i);
774		if (srcItem != NULL && dstItem->ID() == srcItem->ID()) {
775			// Prevent the user from having picked the same partition as source
776			// and destination.
777			dstItem->SetEnabled(false);
778			dstItem->SetMarked(false);
779		} else
780			dstItem->SetEnabled(dstItem->IsValidTarget());
781
782		if (dstItem->IsEnabled())
783			foundOneSuitableTarget = true;
784	}
785
786	PartitionMenuItem* dstItem = (PartitionMenuItem*)fDestMenu->FindMarked();
787	if (dstItem) {
788		label = dstItem->MenuLabel();
789	} else {
790		if (fDestMenu->CountItems() == 0)
791			label = B_TRANSLATE_COMMENT("<none>", "No partition available");
792		else
793			label = B_TRANSLATE("Please choose target");
794	}
795	fDestMenuField->MenuItem()->SetLabel(label.String());
796
797	BString statusText;
798	if (srcItem != NULL && dstItem != NULL) {
799		statusText.SetToFormat(B_TRANSLATE("Press the 'Begin' button to install "
800			"from '%1s' onto '%2s'."), srcItem->Name(), dstItem->Name());
801	} else if (srcItem != NULL) {
802		BString partitionRequiredHaiku = B_TRANSLATE(
803			"Haiku has to be installed on a partition that uses "
804			"the Be File System, but there are currently no such "
805			"partitions available on your system.");
806
807		BString partitionRequiredDebranded = B_TRANSLATE(
808			"This operating system has to be installed on a partition "
809			"that uses the Be File System, but there are currently "
810			"no such partitions available on your system.");
811
812		if (!foundOneSuitableTarget) {
813#ifdef HAIKU_DISTRO_COMPATIBILITY_OFFICIAL
814			statusText.Append(partitionRequiredHaiku);
815#else
816			statusText.Append(partitionRequiredDebranded);
817#endif
818			statusText.Append(" ");
819			statusText.Append(B_TRANSLATE(
820				"Click on 'Set up partitions" B_UTF8_ELLIPSIS
821				"' to create one."));
822		} else {
823			statusText = B_TRANSLATE(
824				"Choose the disk you want to install "
825				"onto from the pop-up menu. Then click 'Begin'.");
826		}
827	} else if (dstItem != NULL) {
828		statusText = B_TRANSLATE("Choose the source disk from the "
829			"pop-up menu. Then click 'Begin'.");
830	} else {
831		statusText = B_TRANSLATE("Choose the source and destination disk "
832			"from the pop-up menus. Then click 'Begin'.");
833	}
834
835	_SetStatusMessage(statusText.String());
836
837	fInstallStatus = kReadyForInstall;
838	fBeginButton->SetLabel(B_TRANSLATE("Begin"));
839	fBeginButton->SetEnabled(srcItem && dstItem);
840
841	// adjust "Write Boot Sector" and "Set up boot menu" buttons
842	if (dstItem != NULL) {
843		char buffer[256];
844		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Write boot sector to '%s'"),
845			dstItem->Name());
846		label = buffer;
847	} else
848		label = B_TRANSLATE("Write boot sector");
849	fMakeBootableItem->SetEnabled(dstItem != NULL);
850	fMakeBootableItem->SetLabel(label.String());
851// TODO: Once bootman support writing to specific disks, enable this, since
852// we would pass it the disk which contains the target partition.
853//	fLaunchBootManagerItem->SetEnabled(dstItem != NULL);
854
855	if (!fEncouragedToSetupPartitions && !foundOneSuitableTarget) {
856		// Focus the users attention on the DriveSetup button
857		fEncouragedToSetupPartitions = true;
858		PostMessage(ENCOURAGE_DRIVESETUP);
859	}
860}
861
862
863void
864InstallerWindow::_PublishPackages()
865{
866	fPackagesView->Clean();
867	PartitionMenuItem *item = (PartitionMenuItem *)fSrcMenu->FindMarked();
868	if (item == NULL)
869		return;
870
871	BPath directory;
872	BDiskDeviceRoster roster;
873	BDiskDevice device;
874	BPartition *partition;
875	if (roster.GetPartitionWithID(item->ID(), &device, &partition) == B_OK) {
876		if (partition->GetMountPoint(&directory) != B_OK)
877			return;
878	} else if (roster.GetDeviceWithID(item->ID(), &device) == B_OK) {
879		if (device.GetMountPoint(&directory) != B_OK)
880			return;
881	} else
882		return; // shouldn't happen
883
884	directory.Append(kPackagesDirectoryPath);
885	BDirectory dir(directory.Path());
886	if (dir.InitCheck() != B_OK)
887		return;
888
889	BEntry packageEntry;
890	BList packages;
891	while (dir.GetNextEntry(&packageEntry) == B_OK) {
892		Package* package = Package::PackageFromEntry(packageEntry);
893		if (package != NULL)
894			packages.AddItem(package);
895	}
896	packages.SortItems(_ComparePackages);
897
898	fPackagesView->AddPackages(packages, new BMessage(PACKAGE_CHECKBOX));
899	PostMessage(PACKAGE_CHECKBOX);
900}
901
902
903void
904InstallerWindow::_SetStatusMessage(const char *text)
905{
906	fStatusView->SetText(text);
907	fStatusView->InvalidateLayout();
908		// In case the status message makes the text view higher than the
909		// logo, then we need to resize te whole window to fit it.
910}
911
912
913void
914InstallerWindow::_SetCopyEngineCancelSemaphore(sem_id id, bool alreadyLocked)
915{
916	if (fCopyEngineCancelSemaphore >= 0) {
917		if (!alreadyLocked)
918			acquire_sem(fCopyEngineCancelSemaphore);
919		delete_sem(fCopyEngineCancelSemaphore);
920	}
921	fCopyEngineCancelSemaphore = id;
922}
923
924
925void
926InstallerWindow::_QuitCopyEngine(bool askUser)
927{
928	if (fCopyEngineCancelSemaphore < 0)
929		return;
930
931	// First of all block the copy engine, so that it doesn't continue
932	// while the alert is showing, which would be irritating.
933	acquire_sem(fCopyEngineCancelSemaphore);
934
935	bool quit = true;
936	if (askUser) {
937		BAlert* alert = new BAlert("cancel",
938			B_TRANSLATE("Are you sure you want to to stop the installation?"),
939			B_TRANSLATE_COMMENT("Continue", "In alert after pressing Stop"),
940			B_TRANSLATE_COMMENT("Stop", "In alert after pressing Stop"), 0,
941			B_WIDTH_AS_USUAL, B_STOP_ALERT);
942		alert->SetShortcut(1, B_ESCAPE);
943		quit = alert->Go() != 0;
944	}
945
946	if (quit) {
947		// Make it quit by having it's lock fail...
948		_SetCopyEngineCancelSemaphore(-1, true);
949	} else
950		release_sem(fCopyEngineCancelSemaphore);
951}
952
953
954// #pragma mark -
955
956
957int
958InstallerWindow::_ComparePackages(const void* firstArg, const void* secondArg)
959{
960	const Group* group1 = *static_cast<const Group* const *>(firstArg);
961	const Group* group2 = *static_cast<const Group* const *>(secondArg);
962	const Package* package1 = dynamic_cast<const Package*>(group1);
963	const Package* package2 = dynamic_cast<const Package*>(group2);
964	int sameGroup = strcmp(group1->GroupName(), group2->GroupName());
965	if (sameGroup != 0)
966		return sameGroup;
967	if (package2 == NULL)
968		return -1;
969	if (package1 == NULL)
970		return 1;
971	return strcmp(package1->Name(), package2->Name());
972}
973
974
975