xref: /haiku/src/apps/installer/InstallerWindow.cpp (revision b6b0567fbd186f8ce8a0c90bdc7a7b5b4c649678)
1 /*
2  * Copyright 2009, 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 #include "InstallerWindow.h"
8 
9 #include <stdio.h>
10 #include <string.h>
11 
12 #include <Alert.h>
13 #include <Application.h>
14 #include <Autolock.h>
15 #include <Box.h>
16 #include <Button.h>
17 #include <ClassInfo.h>
18 #include <Directory.h>
19 #include <FindDirectory.h>
20 #include <GridLayoutBuilder.h>
21 #include <GroupLayoutBuilder.h>
22 #include <LayoutUtils.h>
23 #include <MenuBar.h>
24 #include <MenuField.h>
25 #include <Path.h>
26 #include <PopUpMenu.h>
27 #include <Roster.h>
28 #include <Screen.h>
29 #include <ScrollView.h>
30 #include <SeparatorView.h>
31 #include <SpaceLayoutItem.h>
32 #include <StatusBar.h>
33 #include <String.h>
34 #include <TextView.h>
35 #include <TranslationUtils.h>
36 #include <TranslatorFormats.h>
37 
38 #include "tracker_private.h"
39 
40 #include "DialogPane.h"
41 #include "PackageViews.h"
42 #include "PartitionMenuItem.h"
43 #include "WorkerThread.h"
44 
45 
46 #define DRIVESETUP_SIG "application/x-vnd.Haiku-DriveSetup"
47 
48 const uint32 BEGIN_MESSAGE = 'iBGN';
49 const uint32 SHOW_BOTTOM_MESSAGE = 'iSBT';
50 const uint32 SETUP_MESSAGE = 'iSEP';
51 const uint32 START_SCAN = 'iSSC';
52 const uint32 PACKAGE_CHECKBOX = 'iPCB';
53 
54 class LogoView : public BView {
55 public:
56 								LogoView(const BRect& frame);
57 								LogoView();
58 	virtual						~LogoView();
59 
60 	virtual	void				Draw(BRect update);
61 
62 	virtual	void				GetPreferredSize(float* _width, float* _height);
63 
64 private:
65 			void				_Init();
66 
67 			BBitmap*			fLogo;
68 };
69 
70 
71 LogoView::LogoView(const BRect& frame)
72 	:
73 	BView(frame, "logoview", B_FOLLOW_LEFT | B_FOLLOW_TOP,
74 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
75 {
76 	_Init();
77 }
78 
79 
80 LogoView::LogoView()
81 	:
82 	BView("logoview", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
83 {
84 	_Init();
85 }
86 
87 
88 LogoView::~LogoView(void)
89 {
90 	delete fLogo;
91 }
92 
93 
94 void
95 LogoView::Draw(BRect update)
96 {
97 	if (fLogo == NULL)
98 		return;
99 
100 	BRect bounds(Bounds());
101 	BPoint placement;
102 	placement.x = (bounds.left + bounds.right - fLogo->Bounds().Width()) / 2;
103 	placement.y = (bounds.top + bounds.bottom - fLogo->Bounds().Height()) / 2;
104 
105 	DrawBitmap(fLogo, placement);
106 }
107 
108 
109 void
110 LogoView::GetPreferredSize(float* _width, float* _height)
111 {
112 	float width = 0.0;
113 	float height = 0.0;
114 	if (fLogo) {
115 		width = fLogo->Bounds().Width();
116 		height = fLogo->Bounds().Height();
117 	}
118 	if (_width)
119 		*_width = width;
120 	if (_height)
121 		*_height = height;
122 }
123 
124 
125 void
126 LogoView::_Init()
127 {
128 	fLogo = BTranslationUtils::GetBitmap(B_PNG_FORMAT, "haikulogo.png");
129 }
130 
131 
132 // #pragma mark -
133 
134 
135 static BLayoutItem*
136 layout_item_for(BView* view)
137 {
138 	BLayout* layout = view->Parent()->GetLayout();
139 	int32 index = layout->IndexOfView(view);
140 	return layout->ItemAt(index);
141 }
142 
143 
144 InstallerWindow::InstallerWindow()
145 	: BWindow(BRect(-2000, -2000, -1800, -1800), "Installer", B_TITLED_WINDOW,
146 		B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS),
147 	fNeedsToCenterOnScreen(true),
148 	fEncouragedToSetupPartitions(false),
149 	fDriveSetupLaunched(false),
150 	fInstallStatus(kReadyForInstall),
151 	fWorkerThread(new WorkerThread(this)),
152 	fCopyEngineCancelSemaphore(-1)
153 {
154 	LogoView* logoView = new LogoView();
155 
156 	fStatusView = new BTextView("statusView", be_plain_font, NULL, B_WILL_DRAW);
157 	fStatusView->SetInsets(10, 10, 10, 10);
158 	fStatusView->MakeEditable(false);
159 	fStatusView->MakeSelectable(false);
160 
161 	BSize logoSize = logoView->MinSize();
162 	logoView->SetExplicitMaxSize(logoSize);
163 	fStatusView->SetExplicitMinSize(BSize(logoSize.width * 0.66, B_SIZE_UNSET));
164 
165 	fDestMenu = new BPopUpMenu("scanning" B_UTF8_ELLIPSIS, true, false);
166 	fSrcMenu = new BPopUpMenu("scanning" B_UTF8_ELLIPSIS, true, false);
167 
168 	fSrcMenuField = new BMenuField("srcMenuField", "Install from: ", fSrcMenu,
169 		NULL);
170 	fSrcMenuField->SetAlignment(B_ALIGN_RIGHT);
171 
172 	fDestMenuField = new BMenuField("destMenuField", "Onto: ", fDestMenu, NULL);
173 	fDestMenuField->SetAlignment(B_ALIGN_RIGHT);
174 
175 	fPackagesSwitch = new PaneSwitch("options_button");
176 	fPackagesSwitch->SetLabels("Hide Optional Packages",
177 		"Show Optional Packages");
178 	fPackagesSwitch->SetMessage(new BMessage(SHOW_BOTTOM_MESSAGE));
179 	fPackagesSwitch->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED,
180 		B_SIZE_UNSET));
181 	fPackagesSwitch->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
182 		B_ALIGN_TOP));
183 
184 	fPackagesView = new PackagesView("packages_view");
185 	BScrollView* packagesScrollView = new BScrollView("packagesScroll",
186 		fPackagesView, B_WILL_DRAW, false, true);
187 
188 	const char* requiredDiskSpaceString
189 		= "Additional disk space required: 0.0 KB";
190 	fSizeView = new BStringView("size_view", requiredDiskSpaceString);
191 	fSizeView->SetAlignment(B_ALIGN_RIGHT);
192 	fSizeView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
193 	fSizeView->SetExplicitAlignment(
194 		BAlignment(B_ALIGN_RIGHT, B_ALIGN_MIDDLE));
195 
196 	fProgressBar = new BStatusBar("progress", "Install Progress:  ");
197 	fProgressBar->SetMaxValue(100.0);
198 
199 	fBeginButton = new BButton("begin_button", "Begin",
200 		new BMessage(BEGIN_MESSAGE));
201 	fBeginButton->MakeDefault(true);
202 	fBeginButton->SetEnabled(false);
203 
204 	fSetupButton = new BButton("setup_button",
205 		"Setup partitions" B_UTF8_ELLIPSIS, new BMessage(SETUP_MESSAGE));
206 
207 	fMakeBootableButton = new BButton("makebootable_button",
208 		"Write Boot Sector", new BMessage(MSG_WRITE_BOOT_SECTOR));
209 	fMakeBootableButton->SetEnabled(false);
210 
211 	SetLayout(new BGroupLayout(B_HORIZONTAL));
212 	AddChild(BGroupLayoutBuilder(B_VERTICAL)
213 		.Add(BGroupLayoutBuilder(B_HORIZONTAL)
214 			.Add(logoView)
215 			.Add(fStatusView)
216 		)
217 		.Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER))
218 		.Add(BGroupLayoutBuilder(B_VERTICAL, 10)
219 			.Add(BGridLayoutBuilder(0, 10)
220 				.Add(fSrcMenuField->CreateLabelLayoutItem(), 0, 0)
221 				.Add(fSrcMenuField->CreateMenuBarLayoutItem(), 1, 0)
222 				.Add(fDestMenuField->CreateLabelLayoutItem(), 0, 1)
223 				.Add(fDestMenuField->CreateMenuBarLayoutItem(), 1, 1)
224 
225 				.Add(BSpaceLayoutItem::CreateVerticalStrut(5), 0, 2, 2)
226 
227 				.Add(fPackagesSwitch, 0, 3, 2)
228 				.Add(packagesScrollView, 0, 4, 2)
229 				.Add(fProgressBar, 0, 5, 2)
230 				.Add(fSizeView, 0, 6, 2)
231 			)
232 
233 			.Add(BGroupLayoutBuilder(B_HORIZONTAL, 10)
234 				.Add(fSetupButton)
235 				.Add(fMakeBootableButton)
236 				.AddGlue()
237 				.Add(fBeginButton)
238 			)
239 			.SetInsets(10, 10, 10, 10)
240 		)
241 	);
242 
243 	// Make the optional packages and progress bar invisible on start
244 	fPackagesLayoutItem = layout_item_for(packagesScrollView);
245 	fPkgSwitchLayoutItem = layout_item_for(fPackagesSwitch);
246 	fSizeViewLayoutItem = layout_item_for(fSizeView);
247 	fProgressLayoutItem = layout_item_for(fProgressBar);
248 
249 	fPackagesLayoutItem->SetVisible(false);
250 	fSizeViewLayoutItem->SetVisible(false);
251 	fProgressLayoutItem->SetVisible(false);
252 
253 	// finish creating window
254 	if (!be_roster->IsRunning(kDeskbarSignature))
255 		SetFlags(Flags() | B_NOT_MINIMIZABLE);
256 
257 	Show();
258 
259 	fDriveSetupLaunched = be_roster->IsRunning(DRIVESETUP_SIG);
260 
261 	if (Lock()) {
262 		fSetupButton->SetEnabled(!fDriveSetupLaunched);
263 		Unlock();
264 	}
265 
266 	be_roster->StartWatching(this);
267 
268 	PostMessage(START_SCAN);
269 }
270 
271 
272 InstallerWindow::~InstallerWindow()
273 {
274 	_SetCopyEngineCancelSemaphore(-1);
275 	be_roster->StopWatching(this);
276 }
277 
278 
279 void
280 InstallerWindow::FrameResized(float width, float height)
281 {
282 	BWindow::FrameResized(width, height);
283 
284 	if (fNeedsToCenterOnScreen) {
285 		// We have created ourselves off-screen, since the size adoption
286 		// because of the layout management may happen after Show(). We
287 		// assume that the first frame event is because of this adoption and
288 		// move ourselves to the screen center...
289 		fNeedsToCenterOnScreen = false;
290 		BRect frame = BScreen(this).Frame();
291 		MoveTo(frame.left + (frame.Width() - Frame().Width()) / 2,
292 			frame.top + (frame.Height() - Frame().Height()) / 2);
293 	}
294 }
295 
296 
297 void
298 InstallerWindow::MessageReceived(BMessage *msg)
299 {
300 	switch (msg->what) {
301 		case MSG_RESET:
302 		{
303 			_SetCopyEngineCancelSemaphore(-1);
304 
305 			status_t error;
306 			if (msg->FindInt32("error", &error) == B_OK) {
307 				char errorMessage[2048];
308 				snprintf(errorMessage, sizeof(errorMessage), "An error was "
309 					"encountered and the installation was not completed:\n\n"
310 					"Error:  %s", strerror(error));
311 				(new BAlert("error", errorMessage, "Ok"))->Go();
312 			}
313 
314 			_DisableInterface(false);
315 
316 			fProgressLayoutItem->SetVisible(false);
317 			fPkgSwitchLayoutItem->SetVisible(true);
318 			_ShowOptionalPackages();
319 			_UpdateControls();
320 			break;
321 		}
322 		case START_SCAN:
323 			_ScanPartitions();
324 			break;
325 		case BEGIN_MESSAGE:
326 			switch (fInstallStatus) {
327 				case kReadyForInstall:
328 				{
329 					_SetCopyEngineCancelSemaphore(create_sem(1,
330 						"copy engine cancel"));
331 
332 					BList* list = new BList();
333 					int32 size = 0;
334 					fPackagesView->GetPackagesToInstall(list, &size);
335 					fWorkerThread->SetLock(fCopyEngineCancelSemaphore);
336 					fWorkerThread->SetPackagesList(list);
337 					fWorkerThread->SetSpaceRequired(size);
338 					fInstallStatus = kInstalling;
339 					fWorkerThread->StartInstall();
340 					fBeginButton->SetLabel("Stop");
341 					_DisableInterface(true);
342 
343 					fProgressBar->SetTo(0.0, NULL, NULL);
344 
345 					fPkgSwitchLayoutItem->SetVisible(false);
346 					fPackagesLayoutItem->SetVisible(false);
347 					fSizeViewLayoutItem->SetVisible(false);
348 					fProgressLayoutItem->SetVisible(true);
349 					break;
350 				}
351 				case kInstalling:
352 				{
353 					_QuitCopyEngine(true);
354 					break;
355 				}
356 				case kFinished:
357 					PostMessage(B_QUIT_REQUESTED);
358 					break;
359 				case kCancelled:
360 					break;
361 			}
362 			break;
363 		case SHOW_BOTTOM_MESSAGE:
364 			_ShowOptionalPackages();
365 			break;
366 		case SOURCE_PARTITION:
367 			_PublishPackages();
368 			_UpdateControls();
369 			break;
370 		case TARGET_PARTITION:
371 			_UpdateControls();
372 			break;
373 		case SETUP_MESSAGE:
374 			_LaunchDriveSetup();
375 			break;
376 		case PACKAGE_CHECKBOX:
377 		{
378 			char buffer[15];
379 			fPackagesView->GetTotalSizeAsString(buffer);
380 			char string[255];
381 			sprintf(string, "Additional disk space required: %s", buffer);
382 			fSizeView->SetText(string);
383 			break;
384 		}
385 		case MSG_STATUS_MESSAGE:
386 		{
387 // TODO: Was this supposed to prevent status messages still arriving
388 // after the copy engine was shut down?
389 //			if (fInstallStatus != kInstalling)
390 //				break;
391 			float progress;
392 			if (msg->FindFloat("progress", &progress) == B_OK) {
393 				const char* currentItem;
394 				if (msg->FindString("item", &currentItem) != B_OK)
395 					currentItem = "???";
396 				BString trailingLabel;
397 				int32 currentCount;
398 				int32 maximumCount;
399 				if (msg->FindInt32("current", &currentCount) == B_OK
400 					&& msg->FindInt32("maximum", &maximumCount) == B_OK) {
401 					trailingLabel << currentCount << " of " << maximumCount;
402 				} else {
403 					trailingLabel << "?? of ??";
404 				}
405 				fProgressBar->SetTo(progress, currentItem,
406 					trailingLabel.String());
407 			} else {
408 				const char *status;
409 				if (msg->FindString("status", &status) == B_OK) {
410 					fLastStatus = fStatusView->Text();
411 					_SetStatusMessage(status);
412 				} else
413 					_SetStatusMessage(fLastStatus.String());
414 			}
415 			break;
416 		}
417 		case MSG_INSTALL_FINISHED:
418 		{
419 
420 			_SetCopyEngineCancelSemaphore(-1);
421 
422 			fBeginButton->SetLabel("Quit");
423 
424 			PartitionMenuItem* dstItem
425 				= (PartitionMenuItem*)fDestMenu->FindMarked();
426 
427 			const char* quitString;
428 			if (be_roster->IsRunning(kDeskbarSignature))
429 				quitString = "leave the Installer";
430 			else
431 				quitString = "restart the computer";
432 
433 			char status[1024];
434 			snprintf(status, sizeof(status), "Installation completed. "
435 				"Boot sector has been written to '%s'. Press Quit to %s "
436 				"or chose a new target volume to perform another "
437 				"installation.", dstItem ? dstItem->Name() : "???", quitString);
438 			_SetStatusMessage(status);
439 			fInstallStatus = kFinished;
440 			_DisableInterface(false);
441 			fProgressLayoutItem->SetVisible(false);
442 			fPkgSwitchLayoutItem->SetVisible(true);
443 			_ShowOptionalPackages();
444 			break;
445 		}
446 		case B_SOME_APP_LAUNCHED:
447 		case B_SOME_APP_QUIT:
448 		{
449 			const char *signature;
450 			if (msg->FindString("be:signature", &signature) == B_OK
451 				&& strcasecmp(signature, DRIVESETUP_SIG) == 0) {
452 				fDriveSetupLaunched = msg->what == B_SOME_APP_LAUNCHED;
453 				fBeginButton->SetEnabled(!fDriveSetupLaunched);
454 				_DisableInterface(fDriveSetupLaunched);
455 				if (fDriveSetupLaunched)
456 					_SetStatusMessage("Running DriveSetup" B_UTF8_ELLIPSIS
457 						"\n\nClose DriveSetup to continue with the "
458 						"installation.");
459 				else
460 					_ScanPartitions();
461 			}
462 			break;
463 		}
464 		case MSG_WRITE_BOOT_SECTOR:
465 			fWorkerThread->WriteBootSector(fDestMenu);
466 			break;
467 
468 		default:
469 			BWindow::MessageReceived(msg);
470 			break;
471 	}
472 }
473 
474 
475 bool
476 InstallerWindow::QuitRequested()
477 {
478 	if (fDriveSetupLaunched) {
479 		(new BAlert("driveSetup",
480 			"Please close the DriveSetup window before closing the "
481 			"Installer window.", "Ok"))->Go();
482 		return false;
483 	}
484 	_QuitCopyEngine(false);
485 	fWorkerThread->PostMessage(B_QUIT_REQUESTED);
486 	be_app->PostMessage(B_QUIT_REQUESTED);
487 	return true;
488 }
489 
490 
491 // #pragma mark -
492 
493 
494 void
495 InstallerWindow::_ShowOptionalPackages()
496 {
497 	if (fPackagesLayoutItem && fSizeViewLayoutItem) {
498 		fPackagesLayoutItem->SetVisible(fPackagesSwitch->Value());
499 		fSizeViewLayoutItem->SetVisible(fPackagesSwitch->Value());
500 	}
501 }
502 
503 
504 void
505 InstallerWindow::_LaunchDriveSetup()
506 {
507 	if (be_roster->Launch(DRIVESETUP_SIG) != B_OK) {
508 		// Try really hard to launch it. It's very likely that this fails,
509 		// when we run from the CD and there is only an incomplete mime
510 		// database for example...
511 		BPath path;
512 		if (find_directory(B_SYSTEM_APPS_DIRECTORY, &path) != B_OK
513 			|| path.Append("DriveSetup") != B_OK) {
514 			path.SetTo("/boot/system/apps/DriveSetup");
515 		}
516 		BEntry entry(path.Path());
517 		entry_ref ref;
518 		if (entry.GetRef(&ref) != B_OK || be_roster->Launch(&ref) != B_OK) {
519 			BAlert* alert = new BAlert("error", "DriveSetup, the application "
520 				"to configure disk partitions, could not be launched.",
521 				"Ok");
522 			alert->Go();
523 		}
524 	}
525 }
526 
527 
528 void
529 InstallerWindow::_DisableInterface(bool disable)
530 {
531 	fSetupButton->SetEnabled(!disable);
532 	fMakeBootableButton->SetEnabled(!disable);
533 	fSrcMenuField->SetEnabled(!disable);
534 	fDestMenuField->SetEnabled(!disable);
535 }
536 
537 
538 void
539 InstallerWindow::_ScanPartitions()
540 {
541 	_SetStatusMessage("Scanning for disks" B_UTF8_ELLIPSIS);
542 
543 	BMenuItem *item;
544 	while ((item = fSrcMenu->RemoveItem((int32)0)))
545 		delete item;
546 	while ((item = fDestMenu->RemoveItem((int32)0)))
547 		delete item;
548 
549 	fWorkerThread->ScanDisksPartitions(fSrcMenu, fDestMenu);
550 
551 	if (fSrcMenu->ItemAt(0)) {
552 		_PublishPackages();
553 	}
554 	_UpdateControls();
555 }
556 
557 
558 void
559 InstallerWindow::_UpdateControls()
560 {
561 	PartitionMenuItem* srcItem = (PartitionMenuItem*)fSrcMenu->FindMarked();
562 	BString label;
563 	if (srcItem) {
564 		label = srcItem->MenuLabel();
565 	} else {
566 		if (fSrcMenu->CountItems() == 0)
567 			label = "<none>";
568 		else
569 			label = ((PartitionMenuItem*)fSrcMenu->ItemAt(0))->MenuLabel();
570 	}
571 	fSrcMenuField->MenuItem()->SetLabel(label.String());
572 
573 	// Disable any unsuitable target items, check if at least one partition
574 	// is suitable.
575 	bool foundOneSuitableTarget = false;
576 	for (int32 i = fDestMenu->CountItems() - 1; i >= 0; i--) {
577 		PartitionMenuItem* dstItem
578 			= (PartitionMenuItem*)fDestMenu->ItemAt(i);
579 		if (srcItem != NULL && dstItem->ID() == srcItem->ID()) {
580 			// Prevent the user from having picked the same partition as source
581 			// and destination.
582 			dstItem->SetEnabled(false);
583 			dstItem->SetMarked(false);
584 		} else
585 			dstItem->SetEnabled(dstItem->IsValidTarget());
586 
587 		if (dstItem->IsEnabled())
588 			foundOneSuitableTarget = true;
589 	}
590 
591 	PartitionMenuItem* dstItem = (PartitionMenuItem*)fDestMenu->FindMarked();
592 	if (dstItem) {
593 		label = dstItem->MenuLabel();
594 	} else {
595 		if (fDestMenu->CountItems() == 0)
596 			label = "<none>";
597 		else
598 			label = "Please Choose Target";
599 	}
600 	fDestMenuField->MenuItem()->SetLabel(label.String());
601 
602 	if (srcItem && dstItem) {
603 		char message[255];
604 		sprintf(message, "Press the Begin button to install from '%s' onto "
605 			"'%s'.", srcItem->Name(), dstItem->Name());
606 		_SetStatusMessage(message);
607 	} else if (srcItem) {
608 		_SetStatusMessage("Choose the disk you want to install onto from the "
609 			"pop-up menu. Then click \"Begin\".");
610 	} else if (dstItem) {
611 		_SetStatusMessage("Choose the source disk from the "
612 			"pop-up menu. Then click \"Begin\".");
613 	} else {
614 		_SetStatusMessage("Choose the source and destination disk from the "
615 			"pop-up menus. Then click \"Begin\".");
616 	}
617 
618 	fInstallStatus = kReadyForInstall;
619 	fBeginButton->SetLabel("Begin");
620 	fBeginButton->SetEnabled(srcItem && dstItem);
621 
622 	// adjust "Write Boot Sector" button
623 	label = "Write Boot Sector";
624 	if (dstItem)
625 		label << " to \'" <<dstItem->Name() << '\'';
626 	fMakeBootableButton->SetEnabled(dstItem);
627 	fMakeBootableButton->SetLabel(label.String());
628 
629 	if (!fEncouragedToSetupPartitions && !foundOneSuitableTarget) {
630 		// Focus the users attention on the DriveSetup button
631 		fEncouragedToSetupPartitions = true;
632 		(new BAlert("use drive setup", "No partitions have been found that "
633 			"are suitable for installation. Please setup partitions and "
634 			"initialize at least one partition with the Be File System." ,
635 			"Ok"))->Go();
636 	}
637 }
638 
639 
640 void
641 InstallerWindow::_PublishPackages()
642 {
643 	fPackagesView->Clean();
644 	PartitionMenuItem *item = (PartitionMenuItem *)fSrcMenu->FindMarked();
645 	if (!item)
646 		return;
647 
648 #ifdef __HAIKU__
649 	BPath directory;
650 	BDiskDeviceRoster roster;
651 	BDiskDevice device;
652 	BPartition *partition;
653 	if (roster.GetPartitionWithID(item->ID(), &device, &partition) == B_OK) {
654 		if (partition->GetMountPoint(&directory) != B_OK)
655 			return;
656 	} else if (roster.GetDeviceWithID(item->ID(), &device) == B_OK) {
657 		if (device.GetMountPoint(&directory) != B_OK)
658 			return;
659 	} else
660 		return; // shouldn't happen
661 #else
662 	BPath directory = "/BeOS 5 PE Max Edition V3.1 beta";
663 #endif
664 
665 	directory.Append(PACKAGES_DIRECTORY);
666 	BDirectory dir(directory.Path());
667 	if (dir.InitCheck() != B_OK)
668 		return;
669 
670 	BEntry packageEntry;
671 	BList packages;
672 	while (dir.GetNextEntry(&packageEntry) == B_OK) {
673 		Package *package = Package::PackageFromEntry(packageEntry);
674 		if (package) {
675 			packages.AddItem(package);
676 		}
677 	}
678 	packages.SortItems(_ComparePackages);
679 
680 	fPackagesView->AddPackages(packages, new BMessage(PACKAGE_CHECKBOX));
681 	PostMessage(PACKAGE_CHECKBOX);
682 }
683 
684 
685 void
686 InstallerWindow::_SetStatusMessage(const char *text)
687 {
688 	fStatusView->SetText(text);
689 }
690 
691 
692 void
693 InstallerWindow::_SetCopyEngineCancelSemaphore(sem_id id, bool alreadyLocked)
694 {
695 	if (fCopyEngineCancelSemaphore >= 0) {
696 		if (!alreadyLocked)
697 			acquire_sem(fCopyEngineCancelSemaphore);
698 		delete_sem(fCopyEngineCancelSemaphore);
699 	}
700 	fCopyEngineCancelSemaphore = id;
701 }
702 
703 
704 void
705 InstallerWindow::_QuitCopyEngine(bool askUser)
706 {
707 	if (fCopyEngineCancelSemaphore < 0)
708 		return;
709 
710 	// First of all block the copy engine, so that it doesn't continue
711 	// while the alert is showing, which would be irritating.
712 	acquire_sem(fCopyEngineCancelSemaphore);
713 
714 	bool quit = true;
715 	if (askUser) {
716 		quit = (new BAlert("cancel",
717 			"Are you sure you want to to stop the installation?",
718 			"Continue", "Stop", 0,
719 			B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0;
720 	}
721 
722 	if (quit) {
723 		// Make it quit by having it's lock fail...
724 		_SetCopyEngineCancelSemaphore(-1, true);
725 	} else
726 		release_sem(fCopyEngineCancelSemaphore);
727 }
728 
729 
730 // #pragma mark -
731 
732 
733 int
734 InstallerWindow::_ComparePackages(const void *firstArg, const void *secondArg)
735 {
736 	const Group *group1 = *static_cast<const Group * const *>(firstArg);
737 	const Group *group2 = *static_cast<const Group * const *>(secondArg);
738 	const Package *package1 = dynamic_cast<const Package *>(group1);
739 	const Package *package2 = dynamic_cast<const Package *>(group2);
740 	int sameGroup = strcmp(group1->GroupName(), group2->GroupName());
741 	if (sameGroup != 0)
742 		return sameGroup;
743 	if (!package2)
744 		return -1;
745 	if (!package1)
746 		return 1;
747 	return strcmp(package1->Name(), package2->Name());
748 }
749 
750 
751