1 /* 2 * Copyright 2016-2019 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT license 4 * 5 * Authors: 6 * Alexander von Gluck IV <kallisti5@unixzen.com> 7 * Brian Hill <supernova@tycho.email> 8 * Jacob Secunda 9 */ 10 11 12 #include "SoftwareUpdaterWindow.h" 13 14 #include <Alert.h> 15 #include <AppDefs.h> 16 #include <Application.h> 17 #include <Catalog.h> 18 #include <ControlLook.h> 19 #include <FindDirectory.h> 20 #include <LayoutBuilder.h> 21 #include <LayoutUtils.h> 22 #include <Message.h> 23 #include <Roster.h> 24 #include <RosterPrivate.h> 25 #include <Screen.h> 26 #include <String.h> 27 28 #include "constants.h" 29 30 #undef B_TRANSLATION_CONTEXT 31 #define B_TRANSLATION_CONTEXT "SoftwareUpdaterWindow" 32 33 34 SoftwareUpdaterWindow::SoftwareUpdaterWindow() 35 : 36 BWindow(BRect(0, 0, 300, 10), 37 B_TRANSLATE_SYSTEM_NAME("SoftwareUpdater"), B_TITLED_WINDOW, 38 B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_ZOOMABLE | B_NOT_RESIZABLE), 39 fStripeView(NULL), 40 fHeaderView(NULL), 41 fDetailView(NULL), 42 fUpdateButton(NULL), 43 fCancelButton(NULL), 44 fStatusBar(NULL), 45 fCurrentState(STATE_HEAD), 46 fWaitingSem(-1), 47 fWaitingForButton(false), 48 fUpdateConfirmed(false), 49 fUserCancelRequested(false), 50 fWarningAlertCount(0), 51 fSettingsReadStatus(B_ERROR), 52 fSaveFrameChanges(false), 53 fMessageRunner(NULL), 54 fFrameChangeMessage(kMsgWindowFrameChanged) 55 { 56 // Layout 57 BBitmap icon = GetIcon(32 * icon_layout_scale()); 58 fStripeView = new BStripeView(icon); 59 60 fUpdateButton = new BButton(B_TRANSLATE("Update now"), 61 new BMessage(kMsgUpdateConfirmed)); 62 fUpdateButton->MakeDefault(true); 63 fCancelButton = new BButton(B_TRANSLATE("Cancel"), 64 new BMessage(kMsgCancel)); 65 fRebootButton = new BButton(B_TRANSLATE("Reboot"), 66 new BMessage(kMsgReboot)); 67 68 fHeaderView = new BStringView("header", 69 B_TRANSLATE("Checking for updates"), B_WILL_DRAW); 70 fHeaderView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 71 fHeaderView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP)); 72 fDetailView = new BStringView("detail", B_TRANSLATE("Contacting software " 73 "repositories to check for package updates."), B_WILL_DRAW); 74 fDetailView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 75 fDetailView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP)); 76 fStatusBar = new BStatusBar("progress"); 77 fStatusBar->SetMaxValue(100); 78 79 fListView = new PackageListView(); 80 fScrollView = new BScrollView("scrollview", fListView, B_WILL_DRAW, 81 false, true); 82 83 fDetailsCheckbox = new BCheckBox("detailscheckbox", 84 B_TRANSLATE("Show more details"), 85 new BMessage(kMsgMoreDetailsToggle)); 86 87 BFont font; 88 fHeaderView->GetFont(&font); 89 font.SetFace(B_BOLD_FACE); 90 font.SetSize(font.Size() * 1.5); 91 fHeaderView->SetFont(&font, 92 B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE | B_FONT_FLAGS); 93 94 BLayoutBuilder::Group<>(this, B_HORIZONTAL, B_USE_ITEM_SPACING) 95 .Add(fStripeView) 96 .AddGroup(B_VERTICAL, 0) 97 .SetInsets(0, B_USE_WINDOW_SPACING, 98 B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING) 99 .AddGroup(new BGroupView(B_VERTICAL, B_USE_ITEM_SPACING)) 100 .Add(fHeaderView) 101 .Add(fDetailView) 102 .Add(fStatusBar) 103 .Add(fScrollView) 104 .End() 105 .AddStrut(B_USE_SMALL_SPACING) 106 .AddGroup(new BGroupView(B_HORIZONTAL)) 107 .Add(fDetailsCheckbox) 108 .AddGlue() 109 .Add(fCancelButton) 110 .Add(fUpdateButton) 111 .Add(fRebootButton) 112 .End() 113 .End() 114 .End(); 115 116 fDetailsLayoutItem = layout_item_for(fDetailView); 117 fProgressLayoutItem = layout_item_for(fStatusBar); 118 fPackagesLayoutItem = layout_item_for(fScrollView); 119 fCancelButtonLayoutItem = layout_item_for(fCancelButton); 120 fUpdateButtonLayoutItem = layout_item_for(fUpdateButton); 121 fRebootButtonLayoutItem = layout_item_for(fRebootButton); 122 fDetailsCheckboxLayoutItem = layout_item_for(fDetailsCheckbox); 123 124 _SetState(STATE_DISPLAY_STATUS); 125 CenterOnScreen(); 126 SetFlags(Flags() ^ B_AUTO_UPDATE_SIZE_LIMITS); 127 128 // Prevent resizing for now 129 fDefaultRect = Bounds(); 130 SetSizeLimits(fDefaultRect.Width(), fDefaultRect.Width(), 131 fDefaultRect.Height(), fDefaultRect.Height()); 132 133 // Read settings file 134 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &fSettingsPath); 135 if (status == B_OK) { 136 fSettingsPath.Append(kSettingsFilename); 137 fSettingsReadStatus = _ReadSettings(fInitialSettings); 138 } 139 // Move to saved setting position 140 if (fSettingsReadStatus == B_OK) { 141 BRect windowFrame; 142 status = fInitialSettings.FindRect(kKeyWindowFrame, &windowFrame); 143 if (status == B_OK) { 144 BScreen screen(this); 145 if (screen.Frame().Contains(windowFrame.LeftTop())) 146 MoveTo(windowFrame.LeftTop()); 147 } 148 } 149 Show(); 150 151 BMessage registerMessage(kMsgRegister); 152 registerMessage.AddMessenger(kKeyMessenger, BMessenger(this)); 153 be_app->PostMessage(®isterMessage); 154 155 fCancelAlertResponse.SetMessage(new BMessage(kMsgCancelResponse)); 156 fCancelAlertResponse.SetTarget(this); 157 fWarningAlertDismissed.SetMessage(new BMessage(kMsgWarningDismissed)); 158 fWarningAlertDismissed.SetTarget(this); 159 160 // Common elements used for the zoom height and width calculations 161 fZoomHeightBaseline = 6 162 + be_control_look->ComposeSpacing(B_USE_SMALL_SPACING) 163 + 2 * be_control_look->ComposeSpacing(B_USE_WINDOW_SPACING); 164 fZoomWidthBaseline = fStripeView->PreferredSize().Width() 165 + be_control_look->ComposeSpacing(B_USE_ITEM_SPACING) 166 + fScrollView->ScrollBar(B_VERTICAL)->PreferredSize().Width() 167 + be_control_look->ComposeSpacing(B_USE_WINDOW_SPACING); 168 } 169 170 171 bool 172 SoftwareUpdaterWindow::QuitRequested() 173 { 174 PostMessage(kMsgCancel); 175 return false; 176 } 177 178 179 void 180 SoftwareUpdaterWindow::FrameMoved(BPoint newPosition) 181 { 182 BWindow::FrameMoved(newPosition); 183 184 // Create a message runner to consolidate all function calls from a 185 // move into one message post after moving has ceased for .5 seconds 186 if (fSaveFrameChanges) { 187 if (fMessageRunner == NULL) { 188 fMessageRunner = new BMessageRunner(this, &fFrameChangeMessage, 189 500000, 1); 190 } else 191 fMessageRunner->SetInterval(500000); 192 } 193 } 194 195 196 void 197 SoftwareUpdaterWindow::FrameResized(float newWidth, float newHeight) 198 { 199 BWindow::FrameResized(newWidth, newHeight); 200 201 // Create a message runner to consolidate all function calls from a 202 // resize into one message post after resizing has ceased for .5 seconds 203 if (fSaveFrameChanges) { 204 if (fMessageRunner == NULL) { 205 fMessageRunner = new BMessageRunner(this, &fFrameChangeMessage, 206 500000, 1); 207 } else 208 fMessageRunner->SetInterval(500000); 209 } 210 } 211 212 213 void 214 SoftwareUpdaterWindow::Zoom(BPoint origin, float width, float height) 215 { 216 // Override default zoom behavior and keep window at same position instead 217 // of centering on screen 218 BWindow::Zoom(Frame().LeftTop(), width, height); 219 } 220 221 222 void 223 SoftwareUpdaterWindow::MessageReceived(BMessage* message) 224 { 225 switch (message->what) { 226 227 case kMsgTextUpdate: 228 { 229 if (fCurrentState == STATE_DISPLAY_PROGRESS) 230 _SetState(STATE_DISPLAY_STATUS); 231 else if (fCurrentState != STATE_DISPLAY_STATUS) 232 break; 233 234 BString header; 235 BString detail; 236 Lock(); 237 status_t result = message->FindString(kKeyHeader, &header); 238 if (result == B_OK && header != fHeaderView->Text()) 239 fHeaderView->SetText(header.String()); 240 result = message->FindString(kKeyDetail, &detail); 241 if (result == B_OK) 242 fDetailView->SetText(detail.String()); 243 Unlock(); 244 break; 245 } 246 247 case kMsgProgressUpdate: 248 { 249 if (fCurrentState == STATE_DISPLAY_STATUS) 250 _SetState(STATE_DISPLAY_PROGRESS); 251 else if (fCurrentState != STATE_DISPLAY_PROGRESS) 252 break; 253 254 BString packageName; 255 status_t result = message->FindString(kKeyPackageName, 256 &packageName); 257 if (result != B_OK) 258 break; 259 BString packageCount; 260 result = message->FindString(kKeyPackageCount, &packageCount); 261 if (result != B_OK) 262 break; 263 float percent; 264 result = message->FindFloat(kKeyPercentage, &percent); 265 if (result != B_OK) 266 break; 267 268 BString header; 269 Lock(); 270 result = message->FindString(kKeyHeader, &header); 271 if (result == B_OK && header != fHeaderView->Text()) 272 fHeaderView->SetText(header.String()); 273 fStatusBar->SetTo(percent, packageName.String(), 274 packageCount.String()); 275 Unlock(); 276 277 fListView->UpdatePackageProgress(packageName.String(), percent); 278 break; 279 } 280 281 case kMsgCancel: 282 { 283 if (_GetState() == STATE_FINAL_MESSAGE) { 284 be_app->PostMessage(kMsgFinalQuit); 285 break; 286 } 287 if (!fUpdateConfirmed) { 288 // Downloads have not started yet, we will request to cancel 289 // without confirming 290 Lock(); 291 fHeaderView->SetText(B_TRANSLATE("Cancelling updates")); 292 fDetailView->SetText( 293 B_TRANSLATE("Attempting to cancel the updates" 294 B_UTF8_ELLIPSIS)); 295 Unlock(); 296 fUserCancelRequested = true; 297 298 if (fWaitingForButton) { 299 fButtonResult = message->what; 300 delete_sem(fWaitingSem); 301 fWaitingSem = -1; 302 } 303 break; 304 } 305 306 // Confirm with the user to cancel 307 BAlert* alert = new BAlert("cancel request", B_TRANSLATE("Updates" 308 " have not been completed, are you sure you want to quit?"), 309 B_TRANSLATE("Quit"), B_TRANSLATE("Don't quit"), NULL, 310 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 311 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 312 alert->Go(&fCancelAlertResponse); 313 break; 314 } 315 316 case kMsgShowReboot: 317 { 318 fRebootButtonLayoutItem->SetVisible(true); 319 fRebootButton->SetLabel(B_TRANSLATE_COMMENT("Reboot", 320 "Button label")); 321 fRebootButton->MakeDefault(true); 322 break; 323 } 324 325 case kMsgReboot: 326 { 327 if (_GetState() != STATE_FINAL_MESSAGE) 328 break; 329 330 BRoster roster; 331 BRoster::Private rosterPrivate(roster); 332 status_t error = rosterPrivate.ShutDown(true, false, false); 333 if (error != B_OK) { 334 BAlert* alert = new BAlert("reboot request", B_TRANSLATE( 335 "For some reason, we could not reboot your computer."), 336 B_TRANSLATE("OK"), NULL, NULL, 337 B_WIDTH_AS_USUAL, B_STOP_ALERT); 338 alert->Go(); 339 } 340 break; 341 } 342 343 case kMsgCancelResponse: 344 { 345 // Verify whether the cancel alert was confirmed 346 int32 selection = -1; 347 message->FindInt32("which", &selection); 348 if (selection != 0) 349 break; 350 351 Lock(); 352 fHeaderView->SetText(B_TRANSLATE("Cancelling updates")); 353 fDetailView->SetText( 354 B_TRANSLATE("Attempting to cancel the updates" 355 B_UTF8_ELLIPSIS)); 356 Unlock(); 357 fUserCancelRequested = true; 358 359 if (fWaitingForButton) { 360 fButtonResult = message->what; 361 delete_sem(fWaitingSem); 362 fWaitingSem = -1; 363 } 364 break; 365 } 366 367 case kMsgUpdateConfirmed: 368 { 369 if (fWaitingForButton) { 370 fButtonResult = message->what; 371 delete_sem(fWaitingSem); 372 fWaitingSem = -1; 373 fUpdateConfirmed = true; 374 } 375 break; 376 } 377 378 case kMsgMoreDetailsToggle: 379 fListView->SetMoreDetails(fDetailsCheckbox->Value() != 0); 380 PostMessage(kMsgSetZoomLimits); 381 _WriteSettings(); 382 break; 383 384 case kMsgSetZoomLimits: 385 { 386 int32 count = fListView->CountItems(); 387 if (count < 1) 388 break; 389 // Convert last item's bottom point to its layout group coordinates 390 BPoint zoomPoint = fListView->ZoomPoint(); 391 fScrollView->ConvertToParent(&zoomPoint); 392 // Determine which BControl object height to use 393 float controlHeight; 394 if (fUpdateButtonLayoutItem->IsVisible()) 395 fUpdateButton->GetPreferredSize(NULL, &controlHeight); 396 else 397 fDetailsCheckbox->GetPreferredSize(NULL, &controlHeight); 398 // Calculate height and width values 399 float zoomHeight = fZoomHeightBaseline + zoomPoint.y 400 + controlHeight; 401 float zoomWidth = fZoomWidthBaseline + zoomPoint.x; 402 SetZoomLimits(zoomWidth, zoomHeight); 403 break; 404 } 405 406 case kMsgWarningDismissed: 407 fWarningAlertCount--; 408 break; 409 410 case kMsgWindowFrameChanged: 411 delete fMessageRunner; 412 fMessageRunner = NULL; 413 _WriteSettings(); 414 break; 415 416 case kMsgGetUpdateType: 417 { 418 BString text( 419 B_TRANSLATE("Please choose from these update options:\n\n" 420 "Update:\n" 421 " Updates all installed packages.\n" 422 "Full sync:\n" 423 " Synchronizes the installed packages with the repositories." 424 )); 425 BAlert* alert = new BAlert("update_type", 426 text, 427 B_TRANSLATE_COMMENT("Cancel", "Alert button label"), 428 B_TRANSLATE_COMMENT("Full sync","Alert button label"), 429 B_TRANSLATE_COMMENT("Update","Alert button label"), 430 B_WIDTH_AS_USUAL, B_INFO_ALERT); 431 int32 result = alert->Go(); 432 int32 action = INVALID_SELECTION; 433 switch(result) { 434 case 0: 435 action = CANCEL_UPDATE; 436 break; 437 438 case 1: 439 action = FULLSYNC; 440 break; 441 442 case 2: 443 action = UPDATE; 444 break; 445 } 446 BMessage reply; 447 reply.AddInt32(kKeyAlertResult, action); 448 message->SendReply(&reply); 449 break; 450 } 451 452 case kMsgNoRepositories: 453 { 454 BString text( 455 B_TRANSLATE_COMMENT( 456 "No remote repositories are available. Please verify that some" 457 " repositories are enabled using the Repositories preflet or" 458 " the \'pkgman\' command.", "Error message")); 459 BAlert* alert = new BAlert("repositories", text, 460 B_TRANSLATE_COMMENT("Quit", "Alert button label"), 461 B_TRANSLATE_COMMENT("Open Repositories","Alert button label"), 462 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 463 int32 result = alert->Go(); 464 BMessage reply; 465 reply.AddInt32(kKeyAlertResult, result); 466 message->SendReply(&reply); 467 break; 468 } 469 470 default: 471 BWindow::MessageReceived(message); 472 } 473 } 474 475 476 bool 477 SoftwareUpdaterWindow::ConfirmUpdates() 478 { 479 Lock(); 480 fHeaderView->SetText(B_TRANSLATE("Updates found")); 481 fDetailView->SetText(B_TRANSLATE("The following changes will be made:")); 482 fListView->SortItems(); 483 Unlock(); 484 485 uint32 priorState = _GetState(); 486 _SetState(STATE_GET_CONFIRMATION); 487 488 _WaitForButtonClick(); 489 _SetState(priorState); 490 return fButtonResult == kMsgUpdateConfirmed; 491 } 492 493 494 void 495 SoftwareUpdaterWindow::UpdatesApplying(const char* header, const char* detail) 496 { 497 Lock(); 498 fHeaderView->SetText(header); 499 fDetailView->SetText(detail); 500 Unlock(); 501 _SetState(STATE_APPLY_UPDATES); 502 } 503 504 505 bool 506 SoftwareUpdaterWindow::UserCancelRequested() 507 { 508 if (_GetState() > STATE_GET_CONFIRMATION) 509 return false; 510 511 return fUserCancelRequested; 512 } 513 514 515 void 516 SoftwareUpdaterWindow::AddPackageInfo(uint32 install_type, 517 const char* package_name, const char* cur_ver, const char* new_ver, 518 const char* summary, const char* repository, const char* file_name) 519 { 520 Lock(); 521 fListView->AddPackage(install_type, package_name, cur_ver, new_ver, 522 summary, repository, file_name); 523 Unlock(); 524 } 525 526 527 void 528 SoftwareUpdaterWindow::ShowWarningAlert(const char* text) 529 { 530 BAlert* alert = new BAlert("warning", text, B_TRANSLATE("OK"), NULL, NULL, 531 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 532 alert->Go(&fWarningAlertDismissed); 533 alert->CenterIn(Frame()); 534 // Offset multiple alerts 535 alert->MoveBy(fWarningAlertCount * 15, fWarningAlertCount * 15); 536 fWarningAlertCount++; 537 } 538 539 540 BBitmap 541 SoftwareUpdaterWindow::GetIcon(int32 iconSize) 542 { 543 BBitmap icon(BRect(0, 0, iconSize - 1, iconSize - 1), 0, B_RGBA32); 544 team_info teamInfo; 545 get_team_info(B_CURRENT_TEAM, &teamInfo); 546 app_info appInfo; 547 be_roster->GetRunningAppInfo(teamInfo.team, &appInfo); 548 BNodeInfo::GetTrackerIcon(&appInfo.ref, &icon, icon_size(iconSize)); 549 return icon; 550 } 551 552 553 void 554 SoftwareUpdaterWindow::FinalUpdate(const char* header, const char* detail) 555 { 556 if (_GetState() == STATE_FINAL_MESSAGE) 557 return; 558 559 _SetState(STATE_FINAL_MESSAGE); 560 Lock(); 561 fHeaderView->SetText(header); 562 fDetailView->SetText(detail); 563 Unlock(); 564 } 565 566 567 BLayoutItem* 568 SoftwareUpdaterWindow::layout_item_for(BView* view) 569 { 570 BLayout* layout = view->Parent()->GetLayout(); 571 int32 index = layout->IndexOfView(view); 572 return layout->ItemAt(index); 573 } 574 575 576 uint32 577 SoftwareUpdaterWindow::_WaitForButtonClick() 578 { 579 fButtonResult = 0; 580 fWaitingForButton = true; 581 fWaitingSem = create_sem(0, "WaitingSem"); 582 while (acquire_sem(fWaitingSem) == B_INTERRUPTED) { 583 } 584 fWaitingForButton = false; 585 return fButtonResult; 586 } 587 588 589 void 590 SoftwareUpdaterWindow::_SetState(uint32 state) 591 { 592 if (state <= STATE_HEAD || state >= STATE_MAX) 593 return; 594 595 Lock(); 596 597 // Initial settings 598 if (fCurrentState == STATE_HEAD) { 599 fProgressLayoutItem->SetVisible(false); 600 fPackagesLayoutItem->SetVisible(false); 601 fDetailsCheckboxLayoutItem->SetVisible(false); 602 fCancelButtonLayoutItem->SetVisible(false); 603 fRebootButtonLayoutItem->SetVisible(false); 604 } 605 fCurrentState = state; 606 607 // Update confirmation button 608 // Show only when asking for confirmation to update 609 if (fCurrentState == STATE_GET_CONFIRMATION) 610 fUpdateButtonLayoutItem->SetVisible(true); 611 else 612 fUpdateButtonLayoutItem->SetVisible(false); 613 614 // View package info view and checkbox 615 // Show at confirmation prompt, hide at final update 616 if (fCurrentState == STATE_GET_CONFIRMATION) { 617 fPackagesLayoutItem->SetVisible(true); 618 fDetailsCheckboxLayoutItem->SetVisible(true); 619 if (fSettingsReadStatus == B_OK) { 620 bool showMoreDetails; 621 status_t result = fInitialSettings.FindBool(kKeyShowDetails, 622 &showMoreDetails); 623 if (result == B_OK) { 624 fDetailsCheckbox->SetValue(showMoreDetails ? 1 : 0); 625 fListView->SetMoreDetails(showMoreDetails); 626 } 627 } 628 } else if (fCurrentState == STATE_FINAL_MESSAGE) { 629 fPackagesLayoutItem->SetVisible(false); 630 fDetailsCheckboxLayoutItem->SetVisible(false); 631 } 632 633 // Progress bar and string view 634 // Hide detail text while showing status bar 635 if (fCurrentState == STATE_DISPLAY_PROGRESS) { 636 fDetailsLayoutItem->SetVisible(false); 637 fProgressLayoutItem->SetVisible(true); 638 } else { 639 fProgressLayoutItem->SetVisible(false); 640 fDetailsLayoutItem->SetVisible(true); 641 } 642 643 // Resizing and zooming 644 if (fCurrentState == STATE_GET_CONFIRMATION) { 645 // Enable resizing and zooming 646 float defaultWidth = fDefaultRect.Width(); 647 SetSizeLimits(defaultWidth, B_SIZE_UNLIMITED, 648 fDefaultRect.Height() + 4 * fListView->ItemHeight(), 649 B_SIZE_UNLIMITED); 650 SetFlags(Flags() ^ (B_NOT_RESIZABLE | B_NOT_ZOOMABLE)); 651 PostMessage(kMsgSetZoomLimits); 652 // Recall saved settings 653 BScreen screen(this); 654 BRect screenFrame = screen.Frame(); 655 bool windowResized = false; 656 if (fSettingsReadStatus == B_OK) { 657 BRect windowFrame; 658 status_t result = fInitialSettings.FindRect(kKeyWindowFrame, 659 &windowFrame); 660 if (result == B_OK) { 661 if (screenFrame.Contains(windowFrame)) { 662 ResizeTo(windowFrame.Width(), windowFrame.Height()); 663 windowResized = true; 664 } 665 } 666 } 667 if (!windowResized) 668 ResizeTo(defaultWidth, .75 * defaultWidth); 669 // Check that the bottom of window is on screen 670 float screenBottom = screenFrame.bottom; 671 float windowBottom = DecoratorFrame().bottom; 672 if (windowBottom > screenBottom) 673 MoveBy(0, screenBottom - windowBottom); 674 fSaveFrameChanges = true; 675 } else if (fUpdateConfirmed && (fCurrentState == STATE_DISPLAY_PROGRESS 676 || fCurrentState == STATE_DISPLAY_STATUS)) { 677 PostMessage(kMsgSetZoomLimits); 678 } else if (fCurrentState == STATE_APPLY_UPDATES) 679 fSaveFrameChanges = false; 680 else if (fCurrentState == STATE_FINAL_MESSAGE) { 681 // Disable resizing and zooming 682 fSaveFrameChanges = false; 683 ResizeTo(fDefaultRect.Width(), fDefaultRect.Height()); 684 SetFlags(Flags() | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_RESIZABLE 685 | B_NOT_ZOOMABLE); 686 } 687 688 // Quit button 689 if (fCurrentState == STATE_FINAL_MESSAGE) { 690 fCancelButtonLayoutItem->SetVisible(true); 691 fCancelButton->SetLabel(B_TRANSLATE_COMMENT("Quit", "Button label")); 692 fCancelButton->MakeDefault(true); 693 } 694 695 Unlock(); 696 } 697 698 699 uint32 700 SoftwareUpdaterWindow::_GetState() 701 { 702 return fCurrentState; 703 } 704 705 706 status_t 707 SoftwareUpdaterWindow::_WriteSettings() 708 { 709 BFile file; 710 status_t status = file.SetTo(fSettingsPath.Path(), 711 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 712 if (status == B_OK) { 713 BMessage settings; 714 settings.AddBool(kKeyShowDetails, fDetailsCheckbox->Value() != 0); 715 settings.AddRect(kKeyWindowFrame, Frame()); 716 status = settings.Flatten(&file); 717 } 718 file.Unset(); 719 return status; 720 } 721 722 723 status_t 724 SoftwareUpdaterWindow::_ReadSettings(BMessage& settings) 725 { 726 BFile file; 727 status_t status = file.SetTo(fSettingsPath.Path(), B_READ_ONLY); 728 if (status == B_OK) 729 status = settings.Unflatten(&file); 730 file.Unset(); 731 return status; 732 } 733 734 735 SuperItem::SuperItem(const char* label) 736 : 737 BListItem(), 738 fLabel(label), 739 fRegularFont(be_plain_font), 740 fBoldFont(be_plain_font), 741 fShowMoreDetails(false), 742 fPackageLessIcon(NULL), 743 fPackageMoreIcon(NULL), 744 fItemCount(0) 745 { 746 fBoldFont.SetFace(B_BOLD_FACE); 747 fBoldFont.GetHeight(&fBoldFontHeight); 748 font_height fontHeight; 749 fRegularFont.GetHeight(&fontHeight); 750 fPackageItemLineHeight = fontHeight.ascent + fontHeight.descent 751 + fontHeight.leading; 752 fPackageLessIcon = _GetPackageIcon(GetPackageItemHeight(false)); 753 fPackageMoreIcon = _GetPackageIcon(GetPackageItemHeight(true)); 754 } 755 756 757 SuperItem::~SuperItem() 758 { 759 delete fPackageLessIcon; 760 delete fPackageMoreIcon; 761 } 762 763 764 void 765 SuperItem::DrawItem(BView* owner, BRect item_rect, bool complete) 766 { 767 owner->PushState(); 768 769 float width; 770 owner->GetPreferredSize(&width, NULL); 771 BString text(fItemText); 772 owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); 773 owner->SetFont(&fBoldFont); 774 owner->TruncateString(&text, B_TRUNCATE_END, width); 775 owner->DrawString(text.String(), BPoint(item_rect.left, 776 item_rect.bottom - fBoldFontHeight.descent)); 777 778 owner->PopState(); 779 } 780 781 782 float 783 SuperItem::GetPackageItemHeight() 784 { 785 return GetPackageItemHeight(fShowMoreDetails); 786 } 787 788 789 float 790 SuperItem::GetPackageItemHeight(bool showMoreDetails) 791 { 792 int lineCount = showMoreDetails ? 3 : 2; 793 return lineCount * fPackageItemLineHeight; 794 } 795 796 797 BBitmap* 798 SuperItem::GetIcon(bool showMoreDetails) 799 { 800 if (showMoreDetails) 801 return fPackageMoreIcon; 802 else 803 return fPackageLessIcon; 804 } 805 806 807 float 808 SuperItem::GetIconSize(bool showMoreDetails) 809 { 810 if (showMoreDetails) 811 return fPackageMoreIcon->Bounds().Height(); 812 else 813 return fPackageLessIcon->Bounds().Height(); 814 } 815 816 817 void 818 SuperItem::SetDetailLevel(bool showMoreDetails) 819 { 820 fShowMoreDetails = showMoreDetails; 821 } 822 823 824 void 825 SuperItem::SetItemCount(int32 count) 826 { 827 fItemCount = count; 828 fItemText = fLabel; 829 fItemText.Append(" ("); 830 fItemText << fItemCount; 831 fItemText.Append(")"); 832 } 833 834 835 float 836 SuperItem::ZoomWidth(BView *owner) 837 { 838 owner->PushState(); 839 owner->SetFont(&fBoldFont); 840 float width = owner->StringWidth(fItemText.String()); 841 owner->PopState(); 842 return width; 843 } 844 845 846 BBitmap* 847 SuperItem::_GetPackageIcon(float listItemHeight) 848 { 849 int32 iconSize = int(listItemHeight * .8); 850 status_t result = B_ERROR; 851 BRect iconRect(0, 0, iconSize - 1, iconSize - 1); 852 BBitmap* packageIcon = new BBitmap(iconRect, 0, B_RGBA32); 853 BMimeType nodeType; 854 nodeType.SetTo("application/x-vnd.haiku-package"); 855 result = nodeType.GetIcon(packageIcon, icon_size(iconSize)); 856 // Get super type icon 857 if (result != B_OK) { 858 BMimeType superType; 859 if (nodeType.GetSupertype(&superType) == B_OK) 860 result = superType.GetIcon(packageIcon, icon_size(iconSize)); 861 } 862 if (result != B_OK) { 863 delete packageIcon; 864 return NULL; 865 } 866 return packageIcon; 867 } 868 869 870 PackageItem::PackageItem(const char* name, const char* simple_version, 871 const char* detailed_version, const char* repository, const char* summary, 872 const char* file_name, SuperItem* super) 873 : 874 BListItem(), 875 fName(name), 876 fSimpleVersion(simple_version), 877 fDetailedVersion(detailed_version), 878 fRepository(repository), 879 fSummary(summary), 880 fSmallFont(be_plain_font), 881 fSuperItem(super), 882 fFileName(file_name), 883 fDownloadProgress(0), 884 fDrawBarFlag(false), 885 fMoreDetailsWidth(0), 886 fLessDetailsWidth(0) 887 { 888 fLabelOffset = be_control_look->DefaultLabelSpacing(); 889 fSmallFont.SetSize(be_plain_font->Size() - 2); 890 fSmallFont.GetHeight(&fSmallFontHeight); 891 fSmallTotalHeight = fSmallFontHeight.ascent + fSmallFontHeight.descent 892 + fSmallFontHeight.leading; 893 } 894 895 896 void 897 PackageItem::DrawItem(BView* owner, BRect item_rect, bool complete) 898 { 899 owner->PushState(); 900 901 float width = owner->Frame().Width(); 902 float nameWidth = width / 2.0; 903 float offsetWidth = 0; 904 bool showMoreDetails = fSuperItem->GetDetailLevel(); 905 906 BBitmap* icon = fSuperItem->GetIcon(showMoreDetails); 907 if (icon != NULL && icon->IsValid()) { 908 float iconSize = icon->Bounds().Height(); 909 float offsetMarginHeight = floor((Height() - iconSize) / 2); 910 owner->SetDrawingMode(B_OP_ALPHA); 911 BPoint location = BPoint(item_rect.left, 912 item_rect.top + offsetMarginHeight); 913 owner->DrawBitmap(icon, location); 914 owner->SetDrawingMode(B_OP_COPY); 915 offsetWidth = iconSize + fLabelOffset; 916 917 if (fDrawBarFlag) 918 _DrawBar(location, owner, icon_size(iconSize)); 919 } 920 921 owner->SetFont(be_plain_font); 922 owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); 923 924 // Package name 925 BString name(fName); 926 owner->TruncateString(&name, B_TRUNCATE_END, nameWidth); 927 BPoint cursor(item_rect.left + offsetWidth, 928 item_rect.bottom - fSmallTotalHeight - fSmallFontHeight.descent - 2); 929 if (showMoreDetails) 930 cursor.y -= fSmallTotalHeight + 1; 931 owner->DrawString(name.String(), cursor); 932 cursor.x += owner->StringWidth(name.String()) + fLabelOffset; 933 934 // Change font and color 935 owner->SetFont(&fSmallFont); 936 owner->SetHighColor(tint_color(ui_color(B_LIST_ITEM_TEXT_COLOR), 0.7)); 937 938 // Simple version or repository 939 BString versionOrRepo; 940 if (showMoreDetails) 941 versionOrRepo.SetTo(fRepository); 942 else 943 versionOrRepo.SetTo(fSimpleVersion); 944 owner->TruncateString(&versionOrRepo, B_TRUNCATE_END, width - cursor.x); 945 owner->DrawString(versionOrRepo.String(), cursor); 946 947 // Summary 948 BString summary(fSummary); 949 cursor.x = item_rect.left + offsetWidth; 950 cursor.y += fSmallTotalHeight; 951 owner->TruncateString(&summary, B_TRUNCATE_END, width - cursor.x); 952 owner->DrawString(summary.String(), cursor); 953 954 // Detailed version 955 if (showMoreDetails) { 956 BString version(fDetailedVersion); 957 cursor.y += fSmallTotalHeight; 958 owner->TruncateString(&version, B_TRUNCATE_END, width - cursor.x); 959 owner->DrawString(version.String(), cursor); 960 } 961 962 owner->PopState(); 963 } 964 965 966 // Modified slightly from Tracker's BPose::DrawBar 967 void 968 PackageItem::_DrawBar(BPoint where, BView* view, icon_size which) 969 { 970 int32 yOffset; 971 int32 size = which - 1; 972 int32 barWidth = (int32)(7.0f / 32.0f * (float)which); 973 if (barWidth < 4) { 974 barWidth = 4; 975 yOffset = 0; 976 } else 977 yOffset = 2; 978 int32 barHeight = size - 3 - 2 * yOffset; 979 980 981 // the black shadowed line 982 view->SetHighColor(32, 32, 32, 92); 983 view->MovePenTo(BPoint(where.x + size, where.y + 1 + yOffset)); 984 view->StrokeLine(BPoint(where.x + size, where.y + size - yOffset)); 985 view->StrokeLine(BPoint(where.x + size - barWidth + 1, 986 where.y + size - yOffset)); 987 988 view->SetDrawingMode(B_OP_ALPHA); 989 990 // the gray frame 991 view->SetHighColor(76, 76, 76, 192); 992 BRect rect(where.x + size - barWidth,where.y + yOffset, 993 where.x + size - 1,where.y + size - 1 - yOffset); 994 view->StrokeRect(rect); 995 996 // calculate bar height 997 int32 barPos = barHeight - int32(barHeight * fDownloadProgress / 100.0); 998 if (barPos < 0) 999 barPos = 0; 1000 else if (barPos > barHeight) 1001 barPos = barHeight; 1002 1003 // the free space bar 1004 view->SetHighColor(255, 255, 255, 192); 1005 1006 rect.InsetBy(1,1); 1007 BRect bar(rect); 1008 bar.bottom = bar.top + barPos - 1; 1009 if (barPos > 0) 1010 view->FillRect(bar); 1011 1012 // the used space bar 1013 bar.top = bar.bottom + 1; 1014 bar.bottom = rect.bottom; 1015 view->SetHighColor(0, 203, 0, 192); 1016 view->FillRect(bar); 1017 } 1018 1019 1020 void 1021 PackageItem::Update(BView *owner, const BFont *font) 1022 { 1023 BListItem::Update(owner, font); 1024 SetHeight(fSuperItem->GetPackageItemHeight()); 1025 } 1026 1027 1028 void 1029 PackageItem::CalculateZoomWidths(BView *owner) 1030 { 1031 owner->PushState(); 1032 1033 // More details 1034 float offsetWidth = 2 * be_control_look->DefaultItemSpacing() 1035 + be_plain_font->Size() 1036 + fSuperItem->GetIconSize(true) + fLabelOffset; 1037 // Name and repo 1038 owner->SetFont(be_plain_font); 1039 float stringWidth = owner->StringWidth(fName.String()); 1040 owner->SetFont(&fSmallFont); 1041 stringWidth += fLabelOffset + owner->StringWidth(fRepository.String()); 1042 // Summary 1043 float summaryWidth = owner->StringWidth(fSummary.String()); 1044 if (summaryWidth > stringWidth) 1045 stringWidth = summaryWidth; 1046 // Version 1047 float versionWidth = owner->StringWidth(fDetailedVersion.String()); 1048 if (versionWidth > stringWidth) 1049 stringWidth = versionWidth; 1050 fMoreDetailsWidth = offsetWidth + stringWidth; 1051 1052 // Less details 1053 offsetWidth = 2 * be_control_look->DefaultItemSpacing() 1054 + be_plain_font->Size() 1055 + fSuperItem->GetIconSize(false) + fLabelOffset; 1056 // Name and version 1057 owner->SetFont(be_plain_font); 1058 stringWidth = owner->StringWidth(fName.String()); 1059 owner->SetFont(&fSmallFont); 1060 stringWidth += fLabelOffset + owner->StringWidth(fSimpleVersion.String()); 1061 // Summary 1062 if (summaryWidth > stringWidth) 1063 stringWidth = summaryWidth; 1064 fLessDetailsWidth = offsetWidth + stringWidth; 1065 1066 owner->PopState(); 1067 } 1068 1069 1070 int 1071 PackageItem::NameCompare(PackageItem* item) 1072 { 1073 // sort by package name 1074 return fName.ICompare(item->fName); 1075 } 1076 1077 1078 void 1079 PackageItem::SetDownloadProgress(float percent) 1080 { 1081 fDownloadProgress = percent; 1082 } 1083 1084 1085 int 1086 SortPackageItems(const BListItem* item1, const BListItem* item2) 1087 { 1088 PackageItem* first = (PackageItem*)item1; 1089 PackageItem* second = (PackageItem*)item2; 1090 return first->NameCompare(second); 1091 } 1092 1093 1094 PackageListView::PackageListView() 1095 : 1096 BOutlineListView("Package list"), 1097 fSuperUpdateItem(NULL), 1098 fSuperInstallItem(NULL), 1099 fSuperUninstallItem(NULL), 1100 fShowMoreDetails(false), 1101 fLastProgressItem(NULL), 1102 fLastProgressValue(-1) 1103 { 1104 SetExplicitMinSize(BSize(B_SIZE_UNSET, 40)); 1105 SetExplicitPreferredSize(BSize(B_SIZE_UNSET, 400)); 1106 } 1107 1108 1109 void 1110 PackageListView::FrameResized(float newWidth, float newHeight) 1111 { 1112 BOutlineListView::FrameResized(newWidth, newHeight); 1113 Invalidate(); 1114 } 1115 1116 1117 void 1118 PackageListView::ExpandOrCollapse(BListItem *superItem, bool expand) 1119 { 1120 BOutlineListView::ExpandOrCollapse(superItem, expand); 1121 Window()->PostMessage(kMsgSetZoomLimits); 1122 } 1123 1124 1125 void 1126 PackageListView::AddPackage(uint32 install_type, const char* name, 1127 const char* cur_ver, const char* new_ver, const char* summary, 1128 const char* repository, const char* file_name) 1129 { 1130 SuperItem* super; 1131 BString simpleVersion; 1132 BString detailedVersion(""); 1133 BString repositoryText(B_TRANSLATE_COMMENT("from repository", 1134 "List item text")); 1135 repositoryText.Append(" ").Append(repository); 1136 1137 switch (install_type) { 1138 case PACKAGE_UPDATE: 1139 { 1140 if (fSuperUpdateItem == NULL) { 1141 fSuperUpdateItem = new SuperItem(B_TRANSLATE_COMMENT( 1142 "Packages to be updated", "List super item label")); 1143 AddItem(fSuperUpdateItem); 1144 } 1145 super = fSuperUpdateItem; 1146 1147 simpleVersion.SetTo(new_ver); 1148 detailedVersion.Append(B_TRANSLATE_COMMENT("Updating version", 1149 "List item text")) 1150 .Append(" ").Append(cur_ver) 1151 .Append(" ").Append(B_TRANSLATE_COMMENT("to", 1152 "List item text")) 1153 .Append(" ").Append(new_ver); 1154 break; 1155 } 1156 1157 case PACKAGE_INSTALL: 1158 { 1159 if (fSuperInstallItem == NULL) { 1160 fSuperInstallItem = new SuperItem(B_TRANSLATE_COMMENT( 1161 "New packages to be installed", "List super item label")); 1162 AddItem(fSuperInstallItem); 1163 } 1164 super = fSuperInstallItem; 1165 1166 simpleVersion.SetTo(new_ver); 1167 detailedVersion.Append(B_TRANSLATE_COMMENT("Installing version", 1168 "List item text")) 1169 .Append(" ").Append(new_ver); 1170 break; 1171 } 1172 1173 case PACKAGE_UNINSTALL: 1174 { 1175 if (fSuperUninstallItem == NULL) { 1176 fSuperUninstallItem = new SuperItem(B_TRANSLATE_COMMENT( 1177 "Packages to be uninstalled", "List super item label")); 1178 AddItem(fSuperUninstallItem); 1179 } 1180 super = fSuperUninstallItem; 1181 1182 simpleVersion.SetTo(""); 1183 detailedVersion.Append(B_TRANSLATE_COMMENT("Uninstalling version", 1184 "List item text")) 1185 .Append(" ").Append(cur_ver); 1186 break; 1187 } 1188 1189 default: 1190 return; 1191 1192 } 1193 PackageItem* item = new PackageItem(name, simpleVersion.String(), 1194 detailedVersion.String(), repositoryText.String(), summary, file_name, 1195 super); 1196 AddUnder(item, super); 1197 super->SetItemCount(CountItemsUnder(super, true)); 1198 item->CalculateZoomWidths(this); 1199 } 1200 1201 1202 void 1203 PackageListView::UpdatePackageProgress(const char* packageName, float percent) 1204 { 1205 // Update only every 1 percent change 1206 int16 wholePercent = int16(percent); 1207 if (wholePercent == fLastProgressValue) 1208 return; 1209 fLastProgressValue = wholePercent; 1210 1211 // A new package started downloading, find the PackageItem by name 1212 if (percent == 0) { 1213 fLastProgressItem = NULL; 1214 int32 count = FullListCountItems(); 1215 for (int32 i = 0; i < count; i++) { 1216 PackageItem* item = dynamic_cast<PackageItem*>(FullListItemAt(i)); 1217 if (item != NULL && strcmp(item->FileName(), packageName) == 0) { 1218 fLastProgressItem = item; 1219 fLastProgressItem->ShowProgressBar(); 1220 break; 1221 } 1222 } 1223 } 1224 1225 if (fLastProgressItem != NULL) { 1226 fLastProgressItem->SetDownloadProgress(percent); 1227 Invalidate(); 1228 } 1229 } 1230 1231 1232 void 1233 PackageListView::SortItems() 1234 { 1235 if (fSuperUpdateItem != NULL) 1236 SortItemsUnder(fSuperUpdateItem, true, SortPackageItems); 1237 if (fSuperInstallItem != NULL) 1238 SortItemsUnder(fSuperInstallItem, true, SortPackageItems); 1239 if (fSuperUninstallItem != NULL) 1240 SortItemsUnder(fSuperUninstallItem, true, SortPackageItems); 1241 } 1242 1243 1244 float 1245 PackageListView::ItemHeight() 1246 { 1247 if (fSuperUpdateItem != NULL) 1248 return fSuperUpdateItem->GetPackageItemHeight(); 1249 if (fSuperInstallItem != NULL) 1250 return fSuperInstallItem->GetPackageItemHeight(); 1251 if (fSuperUninstallItem != NULL) 1252 return fSuperUninstallItem->GetPackageItemHeight(); 1253 return 0; 1254 } 1255 1256 1257 void 1258 PackageListView::SetMoreDetails(bool showMore) 1259 { 1260 if (showMore == fShowMoreDetails) 1261 return; 1262 fShowMoreDetails = showMore; 1263 _SetItemHeights(); 1264 InvalidateLayout(); 1265 ResizeToPreferred(); 1266 } 1267 1268 1269 BPoint 1270 PackageListView::ZoomPoint() 1271 { 1272 BPoint zoomPoint(0, 0); 1273 int32 count = CountItems(); 1274 for (int32 i = 0; i < count; i++) 1275 { 1276 BListItem* item = ItemAt(i); 1277 float itemWidth = 0; 1278 if (item->OutlineLevel() == 0) { 1279 SuperItem* sItem = dynamic_cast<SuperItem*>(item); 1280 itemWidth = sItem->ZoomWidth(this); 1281 } else { 1282 PackageItem* pItem = dynamic_cast<PackageItem*>(item); 1283 itemWidth = fShowMoreDetails ? pItem->MoreDetailsWidth() 1284 : pItem->LessDetailsWidth(); 1285 } 1286 if (itemWidth > zoomPoint.x) 1287 zoomPoint.x = itemWidth; 1288 } 1289 if (count > 0) 1290 zoomPoint.y = ItemFrame(count - 1).bottom; 1291 1292 return zoomPoint; 1293 } 1294 1295 1296 void 1297 PackageListView::_SetItemHeights() 1298 { 1299 int32 itemCount = 0; 1300 float itemHeight = 0; 1301 BListItem* item = NULL; 1302 if (fSuperUpdateItem != NULL) { 1303 fSuperUpdateItem->SetDetailLevel(fShowMoreDetails); 1304 itemHeight = fSuperUpdateItem->GetPackageItemHeight(); 1305 itemCount = CountItemsUnder(fSuperUpdateItem, true); 1306 for (int32 i = 0; i < itemCount; i++) { 1307 item = ItemUnderAt(fSuperUpdateItem, true, i); 1308 item->SetHeight(itemHeight); 1309 } 1310 } 1311 if (fSuperInstallItem != NULL) { 1312 fSuperInstallItem->SetDetailLevel(fShowMoreDetails); 1313 itemHeight = fSuperInstallItem->GetPackageItemHeight(); 1314 itemCount = CountItemsUnder(fSuperInstallItem, true); 1315 for (int32 i = 0; i < itemCount; i++) { 1316 item = ItemUnderAt(fSuperInstallItem, true, i); 1317 item->SetHeight(itemHeight); 1318 } 1319 1320 } 1321 if (fSuperUninstallItem != NULL) { 1322 fSuperUninstallItem->SetDetailLevel(fShowMoreDetails); 1323 itemHeight = fSuperUninstallItem->GetPackageItemHeight(); 1324 itemCount = CountItemsUnder(fSuperUninstallItem, true); 1325 for (int32 i = 0; i < itemCount; i++) { 1326 item = ItemUnderAt(fSuperUninstallItem, true, i); 1327 item->SetHeight(itemHeight); 1328 } 1329 1330 } 1331 } 1332