1 /* 2 * Copyright 2007-2010, 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 <Catalog.h> 18 #include <Directory.h> 19 #include <FindDirectory.h> 20 #include <Locale.h> 21 #include <MenuItem.h> 22 #include <Path.h> 23 #include <PopUpMenu.h> 24 #include <ScrollView.h> 25 #include <TextView.h> 26 #include <Volume.h> 27 #include <VolumeRoster.h> 28 #include <Window.h> 29 30 #include <GroupLayout.h> 31 #include <GroupLayoutBuilder.h> 32 #include <GroupView.h> 33 34 #include <fs_info.h> 35 #include <stdio.h> // For debugging 36 37 38 #undef B_TRANSLATE_CONTEXT 39 #define B_TRANSLATE_CONTEXT "PackageView" 40 41 const float kMaxDescHeight = 125.0f; 42 const uint32 kSeparatorIndex = 3; 43 44 45 static void 46 convert_size(uint64 size, char *buffer, uint32 n) 47 { 48 if (size < 1024) 49 snprintf(buffer, n, B_TRANSLATE("%llu bytes"), size); 50 else if (size < 1024 * 1024) 51 snprintf(buffer, n, B_TRANSLATE("%.1f KiB"), size / 1024.0f); 52 else if (size < 1024 * 1024 * 1024) 53 snprintf(buffer, n, B_TRANSLATE("%.1f MiB"), 54 size / (1024.0f * 1024.0f)); 55 else { 56 snprintf(buffer, n, B_TRANSLATE("%.1f GiB"), 57 size / (1024.0f * 1024.0f * 1024.0f)); 58 } 59 } 60 61 62 63 // #pragma mark - 64 65 66 PackageView::PackageView(BRect frame, const entry_ref *ref) 67 : 68 BView(frame, "package_view", B_FOLLOW_NONE, 0), 69 fOpenPanel(new BFilePanel(B_OPEN_PANEL, NULL, NULL, B_DIRECTORY_NODE, 70 false)), 71 fInfo(ref), 72 fInstallProcess(this) 73 { 74 _InitView(); 75 76 // Check whether the package has been successfuly parsed 77 status_t ret = fInfo.InitCheck(); 78 if (ret == B_OK) 79 _InitProfiles(); 80 81 ResizeTo(Bounds().Width(), fInstall->Frame().bottom + 4); 82 } 83 84 85 PackageView::~PackageView() 86 { 87 delete fOpenPanel; 88 } 89 90 91 void 92 PackageView::AttachedToWindow() 93 { 94 status_t ret = fInfo.InitCheck(); 95 if (ret != B_OK && ret != B_NO_INIT) { 96 BAlert *warning = new BAlert("parsing_failed", 97 B_TRANSLATE("The package file is not readable.\nOne of the " 98 "possible reasons for this might be that the requested file " 99 "is not a valid BeOS .pkg package."), B_TRANSLATE("OK"), 100 NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 101 warning->Go(); 102 103 Window()->PostMessage(B_QUIT_REQUESTED); 104 return; 105 } 106 107 // Set the window title 108 BWindow *parent = Window(); 109 BString title; 110 BString name = fInfo.GetName(); 111 if (name.CountChars() == 0) { 112 title = B_TRANSLATE("Package installer"); 113 } 114 else { 115 title = B_TRANSLATE("Install "); 116 title += name; 117 } 118 parent->SetTitle(title.String()); 119 fInstall->SetTarget(this); 120 121 fOpenPanel->SetTarget(BMessenger(this)); 122 fInstallTypes->SetTargetForItems(this); 123 124 if (fInfo.InitCheck() == B_OK) { 125 // If the package is valid, we can set up the default group and all 126 // other things. If not, then the application will close just after 127 // attaching the view to the window 128 _GroupChanged(0); 129 130 fStatusWindow = new PackageStatus 131 (B_TRANSLATE("Installation progress"), 132 NULL, NULL, this); 133 134 // Show the splash screen, if present 135 BMallocIO *image = fInfo.GetSplashScreen(); 136 if (image) { 137 PackageImageViewer *imageViewer = new PackageImageViewer(image); 138 imageViewer->Go(); 139 } 140 141 // Show the disclaimer/info text popup, if present 142 BString disclaimer = fInfo.GetDisclaimer(); 143 if (disclaimer.Length() != 0) { 144 PackageTextViewer *text = new PackageTextViewer( 145 disclaimer.String()); 146 int32 selection = text->Go(); 147 // The user didn't accept our disclaimer, this means we cannot 148 // continue. 149 if (selection == 0) { 150 BWindow *parent = Window(); 151 if (parent && parent->Lock()) 152 parent->Quit(); 153 } 154 } 155 } 156 } 157 158 159 void 160 PackageView::MessageReceived(BMessage *msg) 161 { 162 switch (msg->what) { 163 case P_MSG_INSTALL: 164 { 165 fInstall->SetEnabled(false); 166 fInstallTypes->SetEnabled(false); 167 fDestination->SetEnabled(false); 168 fStatusWindow->Show(); 169 170 fInstallProcess.Start(); 171 break; 172 } 173 case P_MSG_PATH_CHANGED: 174 { 175 BString path; 176 if (msg->FindString("path", &path) == B_OK) { 177 fCurrentPath.SetTo(path.String()); 178 } 179 break; 180 } 181 case P_MSG_OPEN_PANEL: 182 fOpenPanel->Show(); 183 break; 184 case P_MSG_GROUP_CHANGED: 185 { 186 int32 index; 187 if (msg->FindInt32("index", &index) == B_OK) { 188 _GroupChanged(index); 189 } 190 break; 191 } 192 case P_MSG_I_FINISHED: 193 { 194 BAlert *notify = new BAlert("installation_success", 195 B_TRANSLATE("The package you requested has been successfully " 196 "installed on your system."), 197 B_TRANSLATE("OK")); 198 199 notify->Go(); 200 fStatusWindow->Hide(); 201 fInstall->SetEnabled(true); 202 fInstallTypes->SetEnabled(true); 203 fDestination->SetEnabled(true); 204 fInstallProcess.Stop(); 205 206 BWindow *parent = Window(); 207 if (parent && parent->Lock()) 208 parent->Quit(); 209 break; 210 } 211 case P_MSG_I_ABORT: 212 { 213 BAlert *notify = new BAlert("installation_aborted", 214 B_TRANSLATE( 215 "The installation of the package has been aborted."), 216 B_TRANSLATE("OK")); 217 notify->Go(); 218 fStatusWindow->Hide(); 219 fInstall->SetEnabled(true); 220 fInstallTypes->SetEnabled(true); 221 fDestination->SetEnabled(true); 222 fInstallProcess.Stop(); 223 break; 224 } 225 case P_MSG_I_ERROR: 226 { 227 // TODO: Review this 228 BAlert *notify = new BAlert("installation_failed", 229 B_TRANSLATE("The requested package failed to install on your " 230 "system. This might be a problem with the target package " 231 "file. Please consult this issue with the package " 232 "distributor."), 233 B_TRANSLATE("OK"), 234 NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 235 fprintf(stderr, 236 B_TRANSLATE("Error while installing the package\n")); 237 notify->Go(); 238 fStatusWindow->Hide(); 239 fInstall->SetEnabled(true); 240 fInstallTypes->SetEnabled(true); 241 fDestination->SetEnabled(true); 242 fInstallProcess.Stop(); 243 break; 244 } 245 case P_MSG_STOP: 246 { 247 // This message is sent to us by the PackageStatus window, informing 248 // user interruptions. 249 // We actually use this message only when a post installation script 250 // is running and we want to kill it while it's still running 251 fStatusWindow->Hide(); 252 fInstall->SetEnabled(true); 253 fInstallTypes->SetEnabled(true); 254 fDestination->SetEnabled(true); 255 fInstallProcess.Stop(); 256 break; 257 } 258 case B_REFS_RECEIVED: 259 { 260 entry_ref ref; 261 if (msg->FindRef("refs", &ref) == B_OK) { 262 BPath path(&ref); 263 264 BMenuItem * item = fDestField->MenuItem(); 265 dev_t device = dev_for_path(path.Path()); 266 BVolume volume(device); 267 if (volume.InitCheck() != B_OK) 268 break; 269 270 BString name = path.Path(); 271 char sizeString[48]; 272 273 convert_size(volume.FreeBytes(), sizeString, 48); 274 char buffer[512]; 275 snprintf(buffer, sizeof(buffer), "(%s free)", sizeString); 276 name << buffer; 277 278 item->SetLabel(name.String()); 279 fCurrentPath.SetTo(path.Path()); 280 } 281 break; 282 } 283 case B_SIMPLE_DATA: 284 if (msg->WasDropped()) { 285 uint32 type; 286 int32 count; 287 status_t ret = msg->GetInfo("refs", &type, &count); 288 // Check whether the message means someone dropped a file 289 // to our view 290 if (ret == B_OK && type == B_REF_TYPE) { 291 // If it is, send it along with the refs to the application 292 msg->what = B_REFS_RECEIVED; 293 be_app->PostMessage(msg); 294 } 295 } 296 default: 297 BView::MessageReceived(msg); 298 break; 299 } 300 } 301 302 303 int32 304 PackageView::ItemExists(PackageItem &item, BPath &path, int32 &policy) 305 { 306 int32 choice = P_EXISTS_NONE; 307 308 switch (policy) { 309 case P_EXISTS_OVERWRITE: 310 choice = P_EXISTS_OVERWRITE; 311 break; 312 313 case P_EXISTS_SKIP: 314 choice = P_EXISTS_SKIP; 315 break; 316 317 case P_EXISTS_ASK: 318 case P_EXISTS_NONE: 319 { 320 const char* formatString; 321 switch (item.ItemKind()){ 322 case P_KIND_SCRIPT: 323 formatString = B_TRANSLATE("The script named \'%s\' " 324 "already exits in the given path.\nReplace the script " 325 "with the one from this package or skip it?"); 326 break; 327 case P_KIND_FILE: 328 formatString = B_TRANSLATE("The file named \'%s\' already " 329 "exits in the given path.\nReplace the file with the " 330 "one from this package or skip it?"); 331 break; 332 case P_KIND_DIRECTORY: 333 formatString = B_TRANSLATE("The directory named \'%s\' " 334 "already exits in the given path.\nReplace the " 335 "directory with one from this package or skip it?"); 336 break; 337 case P_KIND_SYM_LINK: 338 formatString = B_TRANSLATE("The symbolic link named \'%s\' " 339 "already exists in the give path.\nReplace the link " 340 "with the one from this package or skip it?"); 341 break; 342 default: 343 formatString = B_TRANSLATE("The item named \'%s\' already " 344 "exits in the given path.\nReplace the item with the " 345 "one from this package or skip it?"); 346 break; 347 } 348 char buffer[512]; 349 snprintf(buffer, sizeof(buffer), formatString, path.Leaf()); 350 351 BString alertString = buffer; 352 353 BAlert *alert = new BAlert("file_exists", alertString.String(), 354 B_TRANSLATE("Replace"), 355 B_TRANSLATE("Skip"), 356 B_TRANSLATE("Abort")); 357 358 choice = alert->Go(); 359 switch (choice) { 360 case 0: 361 choice = P_EXISTS_OVERWRITE; 362 break; 363 case 1: 364 choice = P_EXISTS_SKIP; 365 break; 366 default: 367 return P_EXISTS_ABORT; 368 } 369 370 if (policy == P_EXISTS_NONE) { 371 // TODO: Maybe add 'No, but ask again' type of choice as well? 372 alertString = B_TRANSLATE("Do you want to remember this " 373 "decision for the rest of this installation?\n"); 374 375 BString actionString; 376 if (choice == P_EXISTS_OVERWRITE) { 377 alertString << B_TRANSLATE("All existing files will be replaced?"); 378 actionString = B_TRANSLATE("Replace all"); 379 } else { 380 alertString << B_TRANSLATE("All existing files will be skipped?"); 381 actionString = B_TRANSLATE("Skip all"); 382 } 383 alert = new BAlert("policy_decision", alertString.String(), 384 actionString.String(), B_TRANSLATE("Ask again")); 385 386 int32 decision = alert->Go(); 387 if (decision == 0) 388 policy = choice; 389 else 390 policy = P_EXISTS_ASK; 391 } 392 break; 393 } 394 } 395 396 return choice; 397 } 398 399 400 /* 401 void 402 PackageView::_InitView() 403 { 404 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 405 406 BTextView *description = new BTextView(BRect(0, 0, 20, 20), "description", 407 BRect(4, 4, 16, 16), B_FOLLOW_NONE, B_WILL_DRAW); 408 description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 409 description->SetText(fInfo.GetDescription()); 410 description->MakeEditable(false); 411 description->MakeSelectable(false); 412 413 fInstallTypes = new BPopUpMenu(B_TRANSLATE("none")); 414 415 BMenuField *installType = new BMenuField("install_type", 416 B_TRANSLATE("Installation type:"), fInstallTypes, 0); 417 installType->SetAlignment(B_ALIGN_RIGHT); 418 installType->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_MIDDLE)); 419 420 fInstallDesc = new BTextView(BRect(0, 0, 10, 10), "install_desc", 421 BRect(2, 2, 8, 8), B_FOLLOW_NONE, B_WILL_DRAW); 422 fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 423 fInstallDesc->MakeEditable(false); 424 fInstallDesc->MakeSelectable(false); 425 fInstallDesc->SetText(B_TRANSLATE("No installation type selected")); 426 fInstallDesc->TextHeight(0, fInstallDesc->TextLength()); 427 428 fInstall = new BButton("install_button", B_TRANSLATE("Install"), 429 new BMessage(P_MSG_INSTALL)); 430 431 BView *installField = BGroupLayoutBuilder(B_VERTICAL, 5.0f) 432 .AddGroup(B_HORIZONTAL) 433 .Add(installType) 434 .AddGlue() 435 .End() 436 .Add(fInstallDesc); 437 438 BBox *installBox = new BBox("install_box"); 439 installBox->AddChild(installField); 440 441 BView *root = BGroupLayoutBuilder(B_VERTICAL, 3.0f) 442 .Add(description) 443 .Add(installBox) 444 .AddGroup(B_HORIZONTAL) 445 .AddGlue() 446 .Add(fInstall) 447 .End(); 448 449 AddChild(root); 450 451 fInstall->MakeDefault(true); 452 }*/ 453 454 455 void 456 PackageView::_InitView() 457 { 458 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 459 460 BRect rect = Bounds(); 461 BTextView *description = new BTextView(rect, "description", 462 rect.InsetByCopy(5, 5), B_FOLLOW_NONE, B_WILL_DRAW); 463 description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 464 description->SetText(fInfo.GetDescription()); 465 description->MakeEditable(false); 466 description->MakeSelectable(false); 467 468 float length = description->TextHeight(0, description->TextLength()) + 5; 469 if (length > kMaxDescHeight) { 470 // Set a scroller for the description. 471 description->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, 472 kMaxDescHeight); 473 BScrollView *scroller = new BScrollView("desciption_view", description, 474 B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, 475 B_NO_BORDER); 476 477 AddChild(scroller); 478 rect = scroller->Frame(); 479 } 480 else { 481 description->ResizeTo(rect.Width(), length); 482 AddChild(description); 483 rect = description->Frame(); 484 } 485 486 rect.top = rect.bottom + 2; 487 rect.bottom += 100; 488 BBox *installBox = new BBox(rect.InsetByCopy(2, 2), "install_box"); 489 490 fInstallTypes = new BPopUpMenu(B_TRANSLATE("none")); 491 492 BMenuField *installType = new BMenuField(BRect(2, 2, 100, 50), 493 "install_type", B_TRANSLATE("Installation type:"), 494 fInstallTypes, false); 495 installType->SetDivider(installType->StringWidth(installType->Label()) + 8); 496 installType->SetAlignment(B_ALIGN_RIGHT); 497 installType->ResizeToPreferred(); 498 499 installBox->AddChild(installType); 500 501 rect = installBox->Bounds().InsetBySelf(4, 4); 502 rect.top = installType->Frame().bottom; 503 fInstallDesc = new BTextView(rect, "install_desc", 504 BRect(2, 2, rect.Width() - 2, rect.Height() - 2), B_FOLLOW_NONE, 505 B_WILL_DRAW); 506 fInstallDesc->MakeEditable(false); 507 fInstallDesc->MakeSelectable(false); 508 fInstallDesc->SetText(B_TRANSLATE("No installation type selected")); 509 fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 510 511 fInstallDesc->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, 60); 512 BScrollView *scroller = new BScrollView("desciption_view", fInstallDesc, 513 B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, B_NO_BORDER); 514 515 installBox->ResizeTo(installBox->Bounds().Width(), 516 scroller->Frame().bottom + 10); 517 518 installBox->AddChild(scroller); 519 520 AddChild(installBox); 521 522 fDestination = new BPopUpMenu(B_TRANSLATE("none")); 523 524 rect = installBox->Frame(); 525 rect.top = rect.bottom + 5; 526 rect.bottom += 35; 527 fDestField = new BMenuField(rect, "install_to", B_TRANSLATE("Install to:"), 528 fDestination, false); 529 fDestField->SetDivider(fDestField->StringWidth(fDestField->Label()) + 8); 530 fDestField->SetAlignment(B_ALIGN_RIGHT); 531 fDestField->ResizeToPreferred(); 532 533 AddChild(fDestField); 534 535 fInstall = new BButton(rect, "install_button", B_TRANSLATE("Install"), 536 new BMessage(P_MSG_INSTALL)); 537 fInstall->ResizeToPreferred(); 538 AddChild(fInstall); 539 fInstall->MoveTo(Bounds().Width() - fInstall->Bounds().Width() - 10, 540 rect.top + 2); 541 fInstall->MakeDefault(true); 542 } 543 544 545 void 546 PackageView::_InitProfiles() 547 { 548 // Set all profiles 549 int i = 0, num = fInfo.GetProfileCount(); 550 pkg_profile *prof; 551 BMenuItem *item = 0; 552 char sizeString[48]; 553 BString name = ""; 554 BMessage *message; 555 556 if (num > 0) { // Add the default item 557 prof = fInfo.GetProfile(0); 558 convert_size(prof->space_needed, sizeString, 48); 559 char buffer[512]; 560 snprintf(buffer, sizeof(buffer), "(%s)", sizeString); 561 name << prof->name << buffer; 562 563 message = new BMessage(P_MSG_GROUP_CHANGED); 564 message->AddInt32("index", 0); 565 item = new BMenuItem(name.String(), message); 566 fInstallTypes->AddItem(item); 567 item->SetMarked(true); 568 fCurrentType = 0; 569 } 570 571 for (i = 1; i < num; i++) { 572 prof = fInfo.GetProfile(i); 573 574 if (prof) { 575 convert_size(prof->space_needed, sizeString, 48); 576 name = prof->name; 577 char buffer[512]; 578 snprintf(buffer, sizeof(buffer), "(%s)", sizeString); 579 name << buffer; 580 581 message = new BMessage(P_MSG_GROUP_CHANGED); 582 message->AddInt32("index", i); 583 item = new BMenuItem(name.String(), message); 584 fInstallTypes->AddItem(item); 585 } 586 else 587 fInstallTypes->AddSeparatorItem(); 588 } 589 } 590 591 592 status_t 593 PackageView::_GroupChanged(int32 index) 594 { 595 if (index < 0) 596 return B_ERROR; 597 598 BMenuItem *iter; 599 int32 i, num = fDestination->CountItems(); 600 601 // Clear the choice list 602 for (i = 0;i < num;i++) { 603 iter = fDestination->RemoveItem((int32)0); 604 delete iter; 605 } 606 607 fCurrentType = index; 608 pkg_profile *prof = fInfo.GetProfile(index); 609 BString test; 610 611 if (prof) { 612 fInstallDesc->SetText(prof->description.String()); 613 BMenuItem *item = 0; 614 BPath path; 615 BMessage *temp; 616 BVolume volume; 617 char buffer[512]; 618 const char *tmp = B_TRANSLATE("(%s free)"); 619 620 if (prof->path_type == P_INSTALL_PATH) { 621 dev_t device; 622 BString name; 623 char sizeString[48]; 624 625 if (find_directory(B_BEOS_APPS_DIRECTORY, &path) == B_OK) { 626 device = dev_for_path(path.Path()); 627 if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) { 628 temp = new BMessage(P_MSG_PATH_CHANGED); 629 temp->AddString("path", BString(path.Path())); 630 631 convert_size(volume.FreeBytes(), sizeString, 48); 632 name = path.Path(); 633 snprintf(buffer, sizeof(buffer), tmp, sizeString); 634 name << buffer; 635 item = new BMenuItem(name.String(), temp); 636 item->SetTarget(this); 637 fDestination->AddItem(item); 638 } 639 } 640 if (find_directory(B_APPS_DIRECTORY, &path) == B_OK) { 641 device = dev_for_path(path.Path()); 642 if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) { 643 temp = new BMessage(P_MSG_PATH_CHANGED); 644 temp->AddString("path", BString(path.Path())); 645 646 convert_size(volume.FreeBytes(), sizeString, 48); 647 char buffer[512]; 648 snprintf(buffer, sizeof(buffer), tmp, sizeString); 649 name = path.Path(); 650 name << buffer; 651 item = new BMenuItem(name.String(), temp); 652 item->SetTarget(this); 653 fDestination->AddItem(item); 654 } 655 } 656 657 if (item != NULL) { 658 item->SetMarked(true); 659 fCurrentPath.SetTo(path.Path()); 660 } 661 fDestination->AddSeparatorItem(); 662 663 item = new BMenuItem(B_TRANSLATE("Other" B_UTF8_ELLIPSIS), 664 new BMessage(P_MSG_OPEN_PANEL)); 665 item->SetTarget(this); 666 fDestination->AddItem(item); 667 668 fDestField->SetEnabled(true); 669 } else if (prof->path_type == P_USER_PATH) { 670 BString name; 671 bool defaultPathSet = false; 672 char sizeString[48], volumeName[B_FILE_NAME_LENGTH]; 673 BVolumeRoster roster; 674 BDirectory mountPoint; 675 676 while (roster.GetNextVolume(&volume) != B_BAD_VALUE) { 677 if (volume.IsReadOnly() || !volume.IsPersistent() 678 || volume.GetRootDirectory(&mountPoint) != B_OK) { 679 continue; 680 } 681 682 if (path.SetTo(&mountPoint, NULL) != B_OK) 683 continue; 684 685 temp = new BMessage(P_MSG_PATH_CHANGED); 686 temp->AddString("path", BString(path.Path())); 687 688 convert_size(volume.FreeBytes(), sizeString, 48); 689 volume.GetName(volumeName); 690 name = volumeName; 691 snprintf(buffer, sizeof(buffer), tmp, sizeString); 692 name << buffer; 693 item = new BMenuItem(name.String(), temp); 694 item->SetTarget(this); 695 fDestination->AddItem(item); 696 697 // The first volume becomes the default element 698 if (!defaultPathSet) { 699 item->SetMarked(true); 700 fCurrentPath.SetTo(path.Path()); 701 defaultPathSet = true; 702 } 703 } 704 705 fDestField->SetEnabled(true); 706 } else 707 fDestField->SetEnabled(false); 708 } 709 710 return B_OK; 711 } 712 713