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