xref: /haiku/src/apps/packageinstaller/PackageView.cpp (revision a381c8a06378de22ff08adf4282b4e3f7e50d250)
1 /*
2  * Copyright (c) 2007, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Author:
6  *		Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
7  */
8 
9 
10 #include "InstalledPackageInfo.h"
11 #include "PackageImageViewer.h"
12 #include "PackageTextViewer.h"
13 #include "PackageView.h"
14 
15 #include <Alert.h>
16 #include <Button.h>
17 #include <Directory.h>
18 #include <FindDirectory.h>
19 #include <MenuItem.h>
20 #include <Path.h>
21 #include <PopUpMenu.h>
22 #include <ScrollView.h>
23 #include <TextView.h>
24 #include <Volume.h>
25 #include <VolumeRoster.h>
26 #include <Window.h>
27 
28 #include <GroupLayout.h>
29 #include <GroupLayoutBuilder.h>
30 #include <GroupView.h>
31 
32 #include <fs_info.h>
33 #include <stdio.h> // For debugging
34 
35 // Macro reserved for later localization
36 #define T(x) x
37 
38 const float kMaxDescHeight = 125.0f;
39 const uint32 kSeparatorIndex = 3;
40 
41 
42 
43 static void
44 convert_size(uint64 size, char *buffer, uint32 n)
45 {
46 	if (size < 1024)
47 		snprintf(buffer, n, "%llu bytes", size);
48 	else if (size < 1024 * 1024)
49 		snprintf(buffer, n, "%.1f KiB", size / 1024.0f);
50 	else if (size < 1024 * 1024 * 1024)
51 		snprintf(buffer, n, "%.1f MiB", size / (1024.0f*1024.0f));
52 	else
53 		snprintf(buffer, n, "%.1f GiB", size / (1024.0f*1024.0f*1024.0f));
54 }
55 
56 
57 
58 // #pragma mark -
59 
60 
61 PackageView::PackageView(BRect frame, const entry_ref *ref)
62 	:	BView(frame, "package_view", B_FOLLOW_NONE, 0),
63 		//BView("package_view", B_WILL_DRAW, new BGroupLayout(B_HORIZONTAL)),
64 	fOpenPanel(new BFilePanel(B_OPEN_PANEL, NULL, NULL,
65 		B_DIRECTORY_NODE, false)),
66 	fInfo(ref)
67 {
68 	_InitView();
69 
70 	// Check whether the package has been successfuly parsed
71 	status_t ret = fInfo.InitCheck();
72 	if (ret == B_OK)
73 		_InitProfiles();
74 	else if (ret != B_NO_INIT) {
75 		BAlert *warning = new BAlert(T("parsing_failed"),
76 			T("I was unable to read the given package file.\nOne of the possible "
77 				"reasons for this might be that the requested file is not a valid "
78 				"BeOS .pkg package."), T("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
79 			B_WARNING_ALERT);
80 		warning->Go();
81 
82 		BWindow *parent = Window();
83 		if (parent && parent->Lock())
84 			parent->Quit();
85 	}
86 
87 	ResizeTo(Bounds().Width(), fInstall->Frame().bottom + 4);
88 }
89 
90 
91 PackageView::~PackageView()
92 {
93 	delete fOpenPanel;
94 }
95 
96 
97 void
98 PackageView::AttachedToWindow()
99 {
100 	// Set the window title
101 	BWindow *parent = Window();
102 	BString title;
103 	BString name = fInfo.GetName();
104 	if (name.CountChars() == 0) {
105 		title = T("Package installer");
106 	}
107 	else {
108 		title = T("Install ");
109 		title += name;
110 	}
111 	parent->SetTitle(title.String());
112 	fInstall->SetTarget(this);
113 
114 	fOpenPanel->SetTarget(BMessenger(this));
115 	fInstallTypes->SetTargetForItems(this);
116 
117 	if (fInfo.InitCheck() == B_OK) {
118 		// If the package is valid, we can set up the default group and all
119 		// other things. If not, then the application will close just after
120 		// attaching the view to the window
121 		_GroupChanged(0);
122 
123 		fStatusWindow = new PackageStatus(T("Installation progress"));
124 
125 		// Show the splash screen, if present
126 		BMallocIO *image = fInfo.GetSplashScreen();
127 		if (image) {
128 			PackageImageViewer *imageViewer = new PackageImageViewer(image);
129 			imageViewer->Go();
130 		}
131 
132 		// Show the disclaimer/info text popup, if present
133 		BString disclaimer = fInfo.GetDisclaimer();
134 		if (disclaimer.Length() != 0) {
135 	  		PackageTextViewer *text = new PackageTextViewer(disclaimer.String());
136 			int32 selection = text->Go();
137 			// The user didn't accept our disclaimer, this means we cannot continue.
138 	  		if (selection == 0) {
139 				BWindow *parent = Window();
140 				if (parent && parent->Lock())
141 					parent->Quit();
142   			}
143 		}
144 	}
145 }
146 
147 
148 void
149 PackageView::MessageReceived(BMessage *msg)
150 {
151 	switch (msg->what) {
152 		case P_MSG_INSTALL:
153 		{
154 			fInstall->SetEnabled(false);
155 			fStatusWindow->Show();
156 			BAlert *notify;
157 			status_t ret = Install();
158 			if (ret == B_OK) {
159 				notify = new BAlert("installation_success",
160 					T("The package you requested has been successfully installed "
161 						"on your system."), T("OK"));
162 
163 				notify->Go();
164 				fStatusWindow->Hide();
165 
166 				BWindow *parent = Window();
167 				if (parent && parent->Lock())
168 					parent->Quit();
169 			}
170 			else if (ret == B_FILE_EXISTS)
171 				notify = new BAlert("installation_aborted",
172 					T("The installation of the package has been aborted."), T("OK"));
173 			else
174 				notify = new BAlert("installation_failed", // TODO: Review this
175 					T("The requested package failed to install on your system. This "
176 						"might be a problem with the target package file. Please consult "
177 						"this issue with the package distributor."), T("OK"), NULL,
178 					NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
179 			notify->Go();
180 			fStatusWindow->Hide();
181 			fInstall->SetEnabled(true);
182 
183 			break;
184 		}
185 		case P_MSG_PATH_CHANGED:
186 		{
187 			BString path;
188 			if (msg->FindString("path", &path) == B_OK) {
189 				fCurrentPath.SetTo(path.String());
190 			}
191 			break;
192 		}
193 		case P_MSG_OPEN_PANEL:
194 			fOpenPanel->Show();
195 			break;
196 		case P_MSG_GROUP_CHANGED:
197 		{
198 			int32 index;
199 			if (msg->FindInt32("index", &index) == B_OK) {
200 				_GroupChanged(index);
201 			}
202 			break;
203 		}
204 		case B_REFS_RECEIVED:
205 		{
206 			entry_ref ref;
207 			if (msg->FindRef("refs", &ref) == B_OK) {
208 				BPath path(&ref);
209 
210 				BMenuItem * item = fDestField->MenuItem();
211 				dev_t device = dev_for_path(path.Path());
212 				BVolume volume(device);
213 				if (volume.InitCheck() != B_OK)
214 					break;
215 
216 				BString name = path.Path();
217 				char sizeString[32];
218 
219 				convert_size(volume.FreeBytes(), sizeString, 32);
220 				name << " (" << sizeString << " free)";
221 
222 				item->SetLabel(name.String());
223 				fCurrentPath.SetTo(path.Path());
224 			}
225 			break;
226 		}
227 		case B_SIMPLE_DATA:
228 			if (msg->WasDropped()) {
229 				uint32 type;
230 				int32 count;
231 				status_t ret = msg->GetInfo("refs", &type, &count);
232 				// Check whether the message means someone dropped a file
233 				// to our view
234 				if (ret == B_OK && type == B_REF_TYPE) {
235 					// If it is, send it along with the refs to the application
236 					msg->what = B_REFS_RECEIVED;
237 					be_app->PostMessage(msg);
238 				}
239 			}
240 		default:
241 			BView::MessageReceived(msg);
242 			break;
243 	}
244 }
245 
246 
247 status_t
248 PackageView::Install()
249 {
250 	pkg_profile *type = static_cast<pkg_profile *>(fInfo.GetProfile(fCurrentType));
251 	uint32 n = type->items.CountItems();
252 
253 	fStatusWindow->Reset(n + 4);
254 
255 	fStatusWindow->StageStep(1, "Preparing package");
256 
257 	InstalledPackageInfo packageInfo(fInfo.GetName(), fInfo.GetVersion());
258 
259 	status_t err = packageInfo.InitCheck();
260 	if (err == B_OK) {
261 		// The package is already installed, inform the user
262 		BAlert *reinstall = new BAlert("reinstall",
263 			T("The given package seems to be already installed on your system. "
264 				"Would you like to uninstall the existing one and continue the "
265 				"installation?"), T("Yes"), T("No"));
266 
267 		if (reinstall->Go() == 0) {
268 			// Uninstall the package
269 			err = packageInfo.Uninstall();
270 			if (err != B_OK)
271 				return err;
272 
273 			err = packageInfo.SetTo(fInfo.GetName(), fInfo.GetVersion(), true);
274 			if (err != B_OK)
275 				return err;
276 		}
277 		else {
278 			// Abort the installation
279 			return B_FILE_EXISTS;
280 		}
281 	}
282 	else if (err == B_ENTRY_NOT_FOUND) {
283 		err = packageInfo.SetTo(fInfo.GetName(), fInfo.GetVersion(), true);
284 		if (err != B_OK)
285 			return err;
286 	}
287 	else if (fStatusWindow->Stopped())
288 		return B_FILE_EXISTS;
289 	else
290 		return err;
291 
292 	fStatusWindow->StageStep(1, "Installing files and directories");
293 
294 	// Install files and directories
295 	PkgItem *iter;
296 	BPath installedTo;
297 	uint32 i;
298 	BString label;
299 
300 	packageInfo.SetName(fInfo.GetName());
301 	// TODO: Here's a small problem, since right now it's not quite sure
302 	//		which description is really used as such. The one displayed on
303 	//		the installer is mostly package installation description, but
304 	//		most people use it for describing the application in more detail
305 	//		then in the short description.
306 	//		For now, we'll use the short description if possible.
307 	BString description = fInfo.GetShortDescription();
308 	if (description.Length() <= 0)
309 		description = fInfo.GetDescription();
310 	packageInfo.SetDescription(description.String());
311 	packageInfo.SetSpaceNeeded(type->space_needed);
312 
313 	for (i = 0;i < n;i++) {
314 		iter = static_cast<PkgItem *>(type->items.ItemAt(i));
315 		err = iter->WriteToPath(fCurrentPath.Path(), &installedTo);
316 		if (err != B_OK)
317 			return err;
318 		if (fStatusWindow->Stopped())
319 			return B_FILE_EXISTS;
320 		label = "";
321 		label << (uint32)(i + 1) << " of " << (uint32)n;
322 		fStatusWindow->StageStep(1, NULL, label.String());
323 
324 		packageInfo.AddItem(installedTo.Path());
325 	}
326 
327 	fStatusWindow->StageStep(1, "Finishing installation", "");
328 
329 	err = packageInfo.Save();
330 	if (err != B_OK)
331 		return err;
332 
333 	fStatusWindow->StageStep(1, "Done");
334 
335 	return B_OK;
336 }
337 
338 
339 /*
340 void
341 PackageView::_InitView()
342 {
343 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
344 
345 	BTextView *description = new BTextView(BRect(0, 0, 20, 20), "description",
346 		BRect(4, 4, 16, 16), B_FOLLOW_NONE, B_WILL_DRAW);
347 	description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
348 	description->SetText(fInfo.GetDescription());
349 	description->MakeEditable(false);
350 	description->MakeSelectable(false);
351 
352 	fInstallTypes = new BPopUpMenu("none");
353 
354 	BMenuField *installType = new BMenuField("install_type",
355 		T("Installation type:"), fInstallTypes, 0);
356 	installType->SetAlignment(B_ALIGN_RIGHT);
357 	installType->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_MIDDLE));
358 
359 	fInstallDesc = new BTextView(BRect(0, 0, 10, 10), "install_desc",
360 		BRect(2, 2, 8, 8), B_FOLLOW_NONE, B_WILL_DRAW);
361 	fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
362 	fInstallDesc->MakeEditable(false);
363 	fInstallDesc->MakeSelectable(false);
364 	fInstallDesc->SetText(T("No installation type selected"));
365 	fInstallDesc->TextHeight(0, fInstallDesc->TextLength());
366 
367 	fInstall = new BButton("install_button", T("Install"),
368 			new BMessage(P_MSG_INSTALL));
369 
370 	BView *installField = BGroupLayoutBuilder(B_VERTICAL, 5.0f)
371 		.AddGroup(B_HORIZONTAL)
372 			.Add(installType)
373 			.AddGlue()
374 		.End()
375 		.Add(fInstallDesc);
376 
377 	BBox *installBox = new BBox("install_box");
378 	installBox->AddChild(installField);
379 
380 	BView *root = BGroupLayoutBuilder(B_VERTICAL, 3.0f)
381 		.Add(description)
382 		.Add(installBox)
383 		.AddGroup(B_HORIZONTAL)
384 			.AddGlue()
385 			.Add(fInstall)
386 		.End();
387 
388 	AddChild(root);
389 
390 	fInstall->MakeDefault(true);
391 }*/
392 
393 
394 void
395 PackageView::_InitView()
396 {
397 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
398 
399 	BRect rect = Bounds();
400 	BTextView *description = new BTextView(rect, "description",
401 		rect.InsetByCopy(5, 5), B_FOLLOW_NONE, B_WILL_DRAW);
402 	description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
403 	description->SetText(fInfo.GetDescription());
404 	description->MakeEditable(false);
405 	description->MakeSelectable(false);
406 
407 	float length = description->TextHeight(0, description->TextLength()) + 5;
408 	if (length > kMaxDescHeight) {
409 		// Set a scroller for the description.
410 		description->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, kMaxDescHeight);
411 		BScrollView *scroller = new BScrollView("desciption_view", description,
412 				B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, B_NO_BORDER);
413 
414 		AddChild(scroller);
415 		rect = scroller->Frame();
416 	}
417 	else {
418 		description->ResizeTo(rect.Width(), length);
419 		AddChild(description);
420 		rect = description->Frame();
421 	}
422 
423 	rect.top = rect.bottom + 2;
424 	rect.bottom += 100;
425 	BBox *installBox = new BBox(rect.InsetByCopy(2, 2), "install_box");
426 
427 	fInstallTypes = new BPopUpMenu("none");
428 
429 	BMenuField *installType = new BMenuField(BRect(2, 2, 100, 50), "install_type",
430 		T("Installation type:"), fInstallTypes, false);
431 	installType->SetDivider(installType->StringWidth(installType->Label()) + 8);
432 	installType->SetAlignment(B_ALIGN_RIGHT);
433 	installType->ResizeToPreferred();
434 
435 	installBox->AddChild(installType);
436 
437 	rect = installBox->Bounds().InsetBySelf(4, 4);
438 	rect.top = installType->Frame().bottom;
439 	fInstallDesc = new BTextView(rect, "install_desc",
440 		BRect(2, 2, rect.Width() - 2, rect.Height() - 2), B_FOLLOW_NONE,
441 		B_WILL_DRAW);
442 	fInstallDesc->MakeEditable(false);
443 	fInstallDesc->MakeSelectable(false);
444 	fInstallDesc->SetText(T("No installation type selected"));
445 	fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
446 
447 	fInstallDesc->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, 60);
448 	BScrollView *scroller = new BScrollView("desciption_view", fInstallDesc,
449 				B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, B_NO_BORDER);
450 
451 	installBox->ResizeTo(installBox->Bounds().Width(),
452 		scroller->Frame().bottom + 10);
453 
454 	installBox->AddChild(scroller);
455 
456 	AddChild(installBox);
457 
458 	fDestination = new BPopUpMenu("none");
459 
460 	rect = installBox->Frame();
461 	rect.top = rect.bottom + 5;
462 	rect.bottom += 35;
463 	fDestField = new BMenuField(rect, "install_to", T("Install to:"),
464 			fDestination, false);
465 	fDestField->SetDivider(fDestField->StringWidth(fDestField->Label()) + 8);
466 	fDestField->SetAlignment(B_ALIGN_RIGHT);
467 	fDestField->ResizeToPreferred();
468 
469 	AddChild(fDestField);
470 
471 	fInstall = new BButton(rect, "install_button", T("Install"),
472 			new BMessage(P_MSG_INSTALL));
473 	fInstall->ResizeToPreferred();
474 	AddChild(fInstall);
475 	fInstall->MoveTo(Bounds().Width() - fInstall->Bounds().Width() - 10, rect.top + 2);
476 	fInstall->MakeDefault(true);
477 }
478 
479 
480 void
481 PackageView::_InitProfiles()
482 {
483 	// Set all profiles
484 	int i = 0, num = fInfo.GetProfileCount();
485 	pkg_profile *prof;
486 	BMenuItem *item = 0;
487 	char sizeString[32];
488 	BString name = "";
489 	BMessage *message;
490 
491 	if (num > 0) { // Add the default item
492 		prof = fInfo.GetProfile(0);
493 		convert_size(prof->space_needed, sizeString, 32);
494 		name << prof->name << " (" << sizeString << ")";
495 
496 		message = new BMessage(P_MSG_GROUP_CHANGED);
497 		message->AddInt32("index", 0);
498 		item = new BMenuItem(name.String(), message);
499 		fInstallTypes->AddItem(item);
500 		item->SetMarked(true);
501 		fCurrentType = 0;
502 	}
503 
504 	for (i = 1; i < num; i++) {
505 		prof = fInfo.GetProfile(i);
506 
507 		if (prof) {
508 			convert_size(prof->space_needed, sizeString, 32);
509 			name = prof->name;
510 			name << " (" << sizeString << ")";
511 
512 			message = new BMessage(P_MSG_GROUP_CHANGED);
513 			message->AddInt32("index", i);
514 			item = new BMenuItem(name.String(), message);
515 			fInstallTypes->AddItem(item);
516 		}
517 		else
518 			fInstallTypes->AddSeparatorItem();
519 	}
520 }
521 
522 
523 status_t
524 PackageView::_GroupChanged(int32 index)
525 {
526 	if (index < 0)
527 		return B_ERROR;
528 
529 	BMenuItem *iter;
530 	int32 i, num = fDestination->CountItems();
531 
532 	// Clear the choice list
533 	for (i = 0;i < num;i++) {
534 		iter = fDestination->RemoveItem((int32)0);
535 		delete iter;
536 	}
537 
538 	fCurrentType = index;
539 	pkg_profile *prof = fInfo.GetProfile(index);
540 	BString test;
541 	fInstallDesc->SetText(prof->description.String());
542 
543 	if (prof) {
544 		BMenuItem *item = 0;
545 		BPath path;
546 		BMessage *temp;
547 		BVolume volume;
548 
549 		if (prof->path_type == P_INSTALL_PATH) {
550 			dev_t device;
551 			BString name;
552 			char sizeString[32];
553 
554 			if (find_directory(B_BEOS_APPS_DIRECTORY, &path) == B_OK) {
555 				device = dev_for_path(path.Path());
556 				if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) {
557 					temp = new BMessage(P_MSG_PATH_CHANGED);
558 					temp->AddString("path", BString(path.Path()));
559 
560 					convert_size(volume.FreeBytes(), sizeString, 32);
561 					name = path.Path();
562 					name << " (" << sizeString << " free)";
563 					item = new BMenuItem(name.String(), temp);
564 					item->SetTarget(this);
565 					fDestination->AddItem(item);
566 				}
567 			}
568 			if (find_directory(B_APPS_DIRECTORY, &path) == B_OK) {
569 				device = dev_for_path(path.Path());
570 				if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) {
571 					temp = new BMessage(P_MSG_PATH_CHANGED);
572 					temp->AddString("path", BString(path.Path()));
573 
574 					convert_size(volume.FreeBytes(), sizeString, 32);
575 					name = path.Path();
576 					name << " (" << sizeString << " free)";
577 					item = new BMenuItem(name.String(), temp);
578 					item->SetTarget(this);
579 					fDestination->AddItem(item);
580 				}
581 			}
582 
583 			if (item) {
584 				item->SetMarked(true);
585 				fCurrentPath.SetTo(path.Path());
586 			}
587 			fDestination->AddSeparatorItem();
588 
589 			item = new BMenuItem("Other...", new BMessage(P_MSG_OPEN_PANEL));
590 			item->SetTarget(this);
591 			fDestination->AddItem(item);
592 
593 			fDestField->SetEnabled(true);
594 		}
595 		else if (prof->path_type == P_USER_PATH) {
596 			BString name;
597 			char sizeString[32], volumeName[B_FILE_NAME_LENGTH];
598 			BVolumeRoster roster;
599 			BDirectory mountPoint;
600 
601 			while (roster.GetNextVolume(&volume) != B_BAD_VALUE) {
602 				if (volume.IsReadOnly() ||
603 						volume.GetRootDirectory(&mountPoint) != B_OK)
604 					continue;
605 
606 				if (path.SetTo(&mountPoint, NULL) != B_OK)
607 					continue;
608 
609 				temp = new BMessage(P_MSG_PATH_CHANGED);
610 				temp->AddString("path", BString(path.Path()));
611 
612 				convert_size(volume.FreeBytes(), sizeString, 32);
613 				volume.GetName(volumeName);
614 				name = volumeName;
615 				name << " (" << sizeString << " free)";
616 				item = new BMenuItem(name.String(), temp);
617 				item->SetTarget(this);
618 				fDestination->AddItem(item);
619 			}
620 
621 			fDestField->SetEnabled(true);
622 		}
623 		else
624 			fDestField->SetEnabled(false);
625 	}
626 
627 	return B_OK;
628 }
629 
630