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 Unlock(); 289 fUserCancelRequested = true; 290 291 if (fWaitingForButton) { 292 fButtonResult = message->what; 293 delete_sem(fWaitingSem); 294 fWaitingSem = -1; 295 } 296 break; 297 } 298 299 // Confirm with the user to cancel 300 BAlert* alert = new BAlert("cancel request", B_TRANSLATE("Updates" 301 " have not been completed, are you sure you want to quit?"), 302 B_TRANSLATE("Quit"), B_TRANSLATE("Don't quit"), NULL, 303 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 304 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 305 alert->Go(&fCancelAlertResponse); 306 break; 307 } 308 309 case kMsgCancelResponse: 310 { 311 // Verify whether the cancel alert was confirmed 312 int32 selection = -1; 313 message->FindInt32("which", &selection); 314 if (selection != 0) 315 break; 316 317 Lock(); 318 fHeaderView->SetText(B_TRANSLATE("Cancelling updates")); 319 fDetailView->SetText( 320 B_TRANSLATE("Attempting to cancel the updates...")); 321 Unlock(); 322 fUserCancelRequested = true; 323 324 if (fWaitingForButton) { 325 fButtonResult = message->what; 326 delete_sem(fWaitingSem); 327 fWaitingSem = -1; 328 } 329 break; 330 } 331 332 case kMsgUpdateConfirmed: 333 { 334 if (fWaitingForButton) { 335 fButtonResult = message->what; 336 delete_sem(fWaitingSem); 337 fWaitingSem = -1; 338 fUpdateConfirmed = true; 339 } 340 break; 341 } 342 343 case kMsgMoreDetailsToggle: 344 fListView->SetMoreDetails(fDetailsCheckbox->Value() != 0); 345 PostMessage(kMsgSetZoomLimits); 346 _WriteSettings(); 347 break; 348 349 case kMsgSetZoomLimits: 350 { 351 int32 count = fListView->CountItems(); 352 if (count < 1) 353 break; 354 // Convert last item's bottom point to its layout group coordinates 355 BPoint zoomPoint = fListView->ZoomPoint(); 356 fScrollView->ConvertToParent(&zoomPoint); 357 // Determine which BControl object height to use 358 float controlHeight; 359 if (fUpdateButtonLayoutItem->IsVisible()) 360 fUpdateButton->GetPreferredSize(NULL, &controlHeight); 361 else 362 fDetailsCheckbox->GetPreferredSize(NULL, &controlHeight); 363 // Calculate height and width values 364 float zoomHeight = fZoomHeightBaseline + zoomPoint.y 365 + controlHeight; 366 float zoomWidth = fZoomWidthBaseline + zoomPoint.x; 367 SetZoomLimits(zoomWidth, zoomHeight); 368 break; 369 } 370 371 case kMsgWarningDismissed: 372 fWarningAlertCount--; 373 break; 374 375 case kMsgWindowFrameChanged: 376 delete fMessageRunner; 377 fMessageRunner = NULL; 378 _WriteSettings(); 379 break; 380 381 case kMsgGetUpdateType: 382 { 383 BString text( 384 B_TRANSLATE("Please choose from these update options:\n\n" 385 "Update:\n" 386 " Updates all installed packages.\n" 387 "Full sync:\n" 388 " Synchronizes the installed packages with the repositories." 389 )); 390 BAlert* alert = new BAlert("update_type", 391 text, 392 B_TRANSLATE_COMMENT("Cancel", "Alert button label"), 393 B_TRANSLATE_COMMENT("Full sync","Alert button label"), 394 B_TRANSLATE_COMMENT("Update","Alert button label"), 395 B_WIDTH_AS_USUAL, B_INFO_ALERT); 396 int32 result = alert->Go(); 397 int32 action = INVALID_SELECTION; 398 switch(result) { 399 case 0: 400 action = CANCEL_UPDATE; 401 break; 402 403 case 1: 404 action = FULLSYNC; 405 break; 406 407 case 2: 408 action = UPDATE; 409 break; 410 } 411 BMessage reply; 412 reply.AddInt32(kKeyAlertResult, action); 413 message->SendReply(&reply); 414 break; 415 } 416 417 case kMsgNoRepositories: 418 { 419 BString text( 420 B_TRANSLATE_COMMENT( 421 "No remote repositories are available. Please verify that some" 422 " repositories are enabled using the Repositories preflet or" 423 " the \'pkgman\' command.", "Error message")); 424 BAlert* alert = new BAlert("repositories", text, 425 B_TRANSLATE_COMMENT("Quit", "Alert button label"), 426 B_TRANSLATE_COMMENT("Open Repositories","Alert button label"), 427 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 428 int32 result = alert->Go(); 429 BMessage reply; 430 reply.AddInt32(kKeyAlertResult, result); 431 message->SendReply(&reply); 432 break; 433 } 434 435 default: 436 BWindow::MessageReceived(message); 437 } 438 } 439 440 441 bool 442 SoftwareUpdaterWindow::ConfirmUpdates() 443 { 444 Lock(); 445 fHeaderView->SetText(B_TRANSLATE("Updates found")); 446 fDetailView->SetText(B_TRANSLATE("The following changes will be made:")); 447 fListView->SortItems(); 448 Unlock(); 449 450 uint32 priorState = _GetState(); 451 _SetState(STATE_GET_CONFIRMATION); 452 453 _WaitForButtonClick(); 454 _SetState(priorState); 455 return fButtonResult == kMsgUpdateConfirmed; 456 } 457 458 459 void 460 SoftwareUpdaterWindow::UpdatesApplying(const char* header, const char* detail) 461 { 462 Lock(); 463 fHeaderView->SetText(header); 464 fDetailView->SetText(detail); 465 Unlock(); 466 _SetState(STATE_APPLY_UPDATES); 467 } 468 469 470 bool 471 SoftwareUpdaterWindow::UserCancelRequested() 472 { 473 if (_GetState() > STATE_GET_CONFIRMATION) 474 return false; 475 476 return fUserCancelRequested; 477 } 478 479 480 void 481 SoftwareUpdaterWindow::AddPackageInfo(uint32 install_type, 482 const char* package_name, const char* cur_ver, const char* new_ver, 483 const char* summary, const char* repository, const char* file_name) 484 { 485 Lock(); 486 fListView->AddPackage(install_type, package_name, cur_ver, new_ver, 487 summary, repository, file_name); 488 Unlock(); 489 } 490 491 492 void 493 SoftwareUpdaterWindow::ShowWarningAlert(const char* text) 494 { 495 BAlert* alert = new BAlert("warning", text, B_TRANSLATE("OK"), NULL, NULL, 496 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 497 alert->Go(&fWarningAlertDismissed); 498 alert->CenterIn(Frame()); 499 // Offset multiple alerts 500 alert->MoveBy(fWarningAlertCount * 15, fWarningAlertCount * 15); 501 fWarningAlertCount++; 502 } 503 504 505 BBitmap 506 SoftwareUpdaterWindow::GetIcon(int32 iconSize) 507 { 508 BBitmap icon(BRect(0, 0, iconSize - 1, iconSize - 1), 0, B_RGBA32); 509 team_info teamInfo; 510 get_team_info(B_CURRENT_TEAM, &teamInfo); 511 app_info appInfo; 512 be_roster->GetRunningAppInfo(teamInfo.team, &appInfo); 513 BNodeInfo::GetTrackerIcon(&appInfo.ref, &icon, icon_size(iconSize)); 514 return icon; 515 } 516 517 518 void 519 SoftwareUpdaterWindow::FinalUpdate(const char* header, const char* detail) 520 { 521 if (_GetState() == STATE_FINAL_MESSAGE) 522 return; 523 524 _SetState(STATE_FINAL_MESSAGE); 525 Lock(); 526 fHeaderView->SetText(header); 527 fDetailView->SetText(detail); 528 Unlock(); 529 } 530 531 532 BLayoutItem* 533 SoftwareUpdaterWindow::layout_item_for(BView* view) 534 { 535 BLayout* layout = view->Parent()->GetLayout(); 536 int32 index = layout->IndexOfView(view); 537 return layout->ItemAt(index); 538 } 539 540 541 uint32 542 SoftwareUpdaterWindow::_WaitForButtonClick() 543 { 544 fButtonResult = 0; 545 fWaitingForButton = true; 546 fWaitingSem = create_sem(0, "WaitingSem"); 547 while (acquire_sem(fWaitingSem) == B_INTERRUPTED) { 548 } 549 fWaitingForButton = false; 550 return fButtonResult; 551 } 552 553 554 void 555 SoftwareUpdaterWindow::_SetState(uint32 state) 556 { 557 if (state <= STATE_HEAD || state >= STATE_MAX) 558 return; 559 560 Lock(); 561 562 // Initial settings 563 if (fCurrentState == STATE_HEAD) { 564 fProgressLayoutItem->SetVisible(false); 565 fPackagesLayoutItem->SetVisible(false); 566 fDetailsCheckboxLayoutItem->SetVisible(false); 567 fCancelButtonLayoutItem->SetVisible(false); 568 } 569 fCurrentState = state; 570 571 // Update confirmation button 572 // Show only when asking for confirmation to update 573 if (fCurrentState == STATE_GET_CONFIRMATION) 574 fUpdateButtonLayoutItem->SetVisible(true); 575 else 576 fUpdateButtonLayoutItem->SetVisible(false); 577 578 // View package info view and checkbox 579 // Show at confirmation prompt, hide at final update 580 if (fCurrentState == STATE_GET_CONFIRMATION) { 581 fPackagesLayoutItem->SetVisible(true); 582 fDetailsCheckboxLayoutItem->SetVisible(true); 583 if (fSettingsReadStatus == B_OK) { 584 bool showMoreDetails; 585 status_t result = fInitialSettings.FindBool(kKeyShowDetails, 586 &showMoreDetails); 587 if (result == B_OK) { 588 fDetailsCheckbox->SetValue(showMoreDetails ? 1 : 0); 589 fListView->SetMoreDetails(showMoreDetails); 590 } 591 } 592 } else if (fCurrentState == STATE_FINAL_MESSAGE) { 593 fPackagesLayoutItem->SetVisible(false); 594 fDetailsCheckboxLayoutItem->SetVisible(false); 595 } 596 597 // Progress bar and string view 598 // Hide detail text while showing status bar 599 if (fCurrentState == STATE_DISPLAY_PROGRESS) { 600 fDetailsLayoutItem->SetVisible(false); 601 fProgressLayoutItem->SetVisible(true); 602 } else { 603 fProgressLayoutItem->SetVisible(false); 604 fDetailsLayoutItem->SetVisible(true); 605 } 606 607 // Resizing and zooming 608 if (fCurrentState == STATE_GET_CONFIRMATION) { 609 // Enable resizing and zooming 610 float defaultWidth = fDefaultRect.Width(); 611 SetSizeLimits(defaultWidth, B_SIZE_UNLIMITED, 612 fDefaultRect.Height() + 4 * fListView->ItemHeight(), 613 B_SIZE_UNLIMITED); 614 SetFlags(Flags() ^ (B_NOT_RESIZABLE | B_NOT_ZOOMABLE)); 615 PostMessage(kMsgSetZoomLimits); 616 // Recall saved settings 617 BScreen screen(this); 618 BRect screenFrame = screen.Frame(); 619 bool windowResized = false; 620 if (fSettingsReadStatus == B_OK) { 621 BRect windowFrame; 622 status_t result = fInitialSettings.FindRect(kKeyWindowFrame, 623 &windowFrame); 624 if (result == B_OK) { 625 if (screenFrame.Contains(windowFrame)) { 626 ResizeTo(windowFrame.Width(), windowFrame.Height()); 627 windowResized = true; 628 } 629 } 630 } 631 if (!windowResized) 632 ResizeTo(defaultWidth, .75 * defaultWidth); 633 // Check that the bottom of window is on screen 634 float screenBottom = screenFrame.bottom; 635 float windowBottom = DecoratorFrame().bottom; 636 if (windowBottom > screenBottom) 637 MoveBy(0, screenBottom - windowBottom); 638 fSaveFrameChanges = true; 639 } else if (fUpdateConfirmed && (fCurrentState == STATE_DISPLAY_PROGRESS 640 || fCurrentState == STATE_DISPLAY_STATUS)) { 641 PostMessage(kMsgSetZoomLimits); 642 } else if (fCurrentState == STATE_APPLY_UPDATES) 643 fSaveFrameChanges = false; 644 else if (fCurrentState == STATE_FINAL_MESSAGE) { 645 // Disable resizing and zooming 646 fSaveFrameChanges = false; 647 ResizeTo(fDefaultRect.Width(), fDefaultRect.Height()); 648 SetFlags(Flags() | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_RESIZABLE 649 | B_NOT_ZOOMABLE); 650 } 651 652 // Quit button 653 if (fCurrentState == STATE_FINAL_MESSAGE) { 654 fCancelButtonLayoutItem->SetVisible(true); 655 fCancelButton->SetLabel(B_TRANSLATE_COMMENT("Quit", "Button label")); 656 fCancelButton->MakeDefault(true); 657 } 658 659 Unlock(); 660 } 661 662 663 uint32 664 SoftwareUpdaterWindow::_GetState() 665 { 666 return fCurrentState; 667 } 668 669 670 status_t 671 SoftwareUpdaterWindow::_WriteSettings() 672 { 673 BFile file; 674 status_t status = file.SetTo(fSettingsPath.Path(), 675 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 676 if (status == B_OK) { 677 BMessage settings; 678 settings.AddBool(kKeyShowDetails, fDetailsCheckbox->Value() != 0); 679 settings.AddRect(kKeyWindowFrame, Frame()); 680 status = settings.Flatten(&file); 681 } 682 file.Unset(); 683 return status; 684 } 685 686 687 status_t 688 SoftwareUpdaterWindow::_ReadSettings(BMessage& settings) 689 { 690 BFile file; 691 status_t status = file.SetTo(fSettingsPath.Path(), B_READ_ONLY); 692 if (status == B_OK) 693 status = settings.Unflatten(&file); 694 file.Unset(); 695 return status; 696 } 697 698 699 SuperItem::SuperItem(const char* label) 700 : 701 BListItem(), 702 fLabel(label), 703 fRegularFont(be_plain_font), 704 fBoldFont(be_plain_font), 705 fShowMoreDetails(false), 706 fPackageLessIcon(NULL), 707 fPackageMoreIcon(NULL), 708 fItemCount(0) 709 { 710 fBoldFont.SetFace(B_BOLD_FACE); 711 fBoldFont.GetHeight(&fBoldFontHeight); 712 font_height fontHeight; 713 fRegularFont.GetHeight(&fontHeight); 714 fPackageItemLineHeight = fontHeight.ascent + fontHeight.descent 715 + fontHeight.leading; 716 fPackageLessIcon = _GetPackageIcon(GetPackageItemHeight(false)); 717 fPackageMoreIcon = _GetPackageIcon(GetPackageItemHeight(true)); 718 } 719 720 721 SuperItem::~SuperItem() 722 { 723 delete fPackageLessIcon; 724 delete fPackageMoreIcon; 725 } 726 727 728 void 729 SuperItem::DrawItem(BView* owner, BRect item_rect, bool complete) 730 { 731 owner->PushState(); 732 733 float width; 734 owner->GetPreferredSize(&width, NULL); 735 BString text(fItemText); 736 owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); 737 owner->SetFont(&fBoldFont); 738 owner->TruncateString(&text, B_TRUNCATE_END, width); 739 owner->DrawString(text.String(), BPoint(item_rect.left, 740 item_rect.bottom - fBoldFontHeight.descent)); 741 742 owner->PopState(); 743 } 744 745 746 float 747 SuperItem::GetPackageItemHeight() 748 { 749 return GetPackageItemHeight(fShowMoreDetails); 750 } 751 752 753 float 754 SuperItem::GetPackageItemHeight(bool showMoreDetails) 755 { 756 int lineCount = showMoreDetails ? 3 : 2; 757 return lineCount * fPackageItemLineHeight; 758 } 759 760 761 BBitmap* 762 SuperItem::GetIcon(bool showMoreDetails) 763 { 764 if (showMoreDetails) 765 return fPackageMoreIcon; 766 else 767 return fPackageLessIcon; 768 } 769 770 771 float 772 SuperItem::GetIconSize(bool showMoreDetails) 773 { 774 if (showMoreDetails) 775 return fPackageMoreIcon->Bounds().Height(); 776 else 777 return fPackageLessIcon->Bounds().Height(); 778 } 779 780 781 void 782 SuperItem::SetDetailLevel(bool showMoreDetails) 783 { 784 fShowMoreDetails = showMoreDetails; 785 } 786 787 788 void 789 SuperItem::SetItemCount(int32 count) 790 { 791 fItemCount = count; 792 fItemText = fLabel; 793 fItemText.Append(" ("); 794 fItemText << fItemCount; 795 fItemText.Append(")"); 796 } 797 798 799 float 800 SuperItem::ZoomWidth(BView *owner) 801 { 802 owner->PushState(); 803 owner->SetFont(&fBoldFont); 804 float width = owner->StringWidth(fItemText.String()); 805 owner->PopState(); 806 return width; 807 } 808 809 810 BBitmap* 811 SuperItem::_GetPackageIcon(float listItemHeight) 812 { 813 int32 iconSize = int(listItemHeight * .8); 814 status_t result = B_ERROR; 815 BRect iconRect(0, 0, iconSize - 1, iconSize - 1); 816 BBitmap* packageIcon = new BBitmap(iconRect, 0, B_RGBA32); 817 BMimeType nodeType; 818 nodeType.SetTo("application/x-vnd.haiku-package"); 819 result = nodeType.GetIcon(packageIcon, icon_size(iconSize)); 820 // Get super type icon 821 if (result != B_OK) { 822 BMimeType superType; 823 if (nodeType.GetSupertype(&superType) == B_OK) 824 result = superType.GetIcon(packageIcon, icon_size(iconSize)); 825 } 826 if (result != B_OK) { 827 delete packageIcon; 828 return NULL; 829 } 830 return packageIcon; 831 } 832 833 834 PackageItem::PackageItem(const char* name, const char* simple_version, 835 const char* detailed_version, const char* repository, const char* summary, 836 const char* file_name, SuperItem* super) 837 : 838 BListItem(), 839 fName(name), 840 fSimpleVersion(simple_version), 841 fDetailedVersion(detailed_version), 842 fRepository(repository), 843 fSummary(summary), 844 fSmallFont(be_plain_font), 845 fSuperItem(super), 846 fFileName(file_name), 847 fDownloadProgress(0), 848 fDrawBarFlag(false), 849 fMoreDetailsWidth(0), 850 fLessDetailsWidth(0) 851 { 852 fLabelOffset = be_control_look->DefaultLabelSpacing(); 853 fSmallFont.SetSize(be_plain_font->Size() - 2); 854 fSmallFont.GetHeight(&fSmallFontHeight); 855 fSmallTotalHeight = fSmallFontHeight.ascent + fSmallFontHeight.descent 856 + fSmallFontHeight.leading; 857 } 858 859 860 void 861 PackageItem::DrawItem(BView* owner, BRect item_rect, bool complete) 862 { 863 owner->PushState(); 864 865 float width = owner->Frame().Width(); 866 float nameWidth = width / 2.0; 867 float offsetWidth = 0; 868 bool showMoreDetails = fSuperItem->GetDetailLevel(); 869 870 BBitmap* icon = fSuperItem->GetIcon(showMoreDetails); 871 if (icon != NULL && icon->IsValid()) { 872 float iconSize = icon->Bounds().Height(); 873 float offsetMarginHeight = floor((Height() - iconSize) / 2); 874 owner->SetDrawingMode(B_OP_ALPHA); 875 BPoint location = BPoint(item_rect.left, 876 item_rect.top + offsetMarginHeight); 877 owner->DrawBitmap(icon, location); 878 owner->SetDrawingMode(B_OP_COPY); 879 offsetWidth = iconSize + fLabelOffset; 880 881 if (fDrawBarFlag) 882 _DrawBar(location, owner, icon_size(iconSize)); 883 } 884 885 owner->SetFont(be_plain_font); 886 owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); 887 888 // Package name 889 BString name(fName); 890 owner->TruncateString(&name, B_TRUNCATE_END, nameWidth); 891 BPoint cursor(item_rect.left + offsetWidth, 892 item_rect.bottom - fSmallTotalHeight - fSmallFontHeight.descent - 2); 893 if (showMoreDetails) 894 cursor.y -= fSmallTotalHeight + 1; 895 owner->DrawString(name.String(), cursor); 896 cursor.x += owner->StringWidth(name.String()) + fLabelOffset; 897 898 // Change font and color 899 owner->SetFont(&fSmallFont); 900 owner->SetHighColor(tint_color(ui_color(B_LIST_ITEM_TEXT_COLOR), 0.7)); 901 902 // Simple version or repository 903 BString versionOrRepo; 904 if (showMoreDetails) 905 versionOrRepo.SetTo(fRepository); 906 else 907 versionOrRepo.SetTo(fSimpleVersion); 908 owner->TruncateString(&versionOrRepo, B_TRUNCATE_END, width - cursor.x); 909 owner->DrawString(versionOrRepo.String(), cursor); 910 911 // Summary 912 BString summary(fSummary); 913 cursor.x = item_rect.left + offsetWidth; 914 cursor.y += fSmallTotalHeight; 915 owner->TruncateString(&summary, B_TRUNCATE_END, width - cursor.x); 916 owner->DrawString(summary.String(), cursor); 917 918 // Detailed version 919 if (showMoreDetails) { 920 BString version(fDetailedVersion); 921 cursor.y += fSmallTotalHeight; 922 owner->TruncateString(&version, B_TRUNCATE_END, width - cursor.x); 923 owner->DrawString(version.String(), cursor); 924 } 925 926 owner->PopState(); 927 } 928 929 930 // Modified slightly from Tracker's BPose::DrawBar 931 void 932 PackageItem::_DrawBar(BPoint where, BView* view, icon_size which) 933 { 934 int32 yOffset; 935 int32 size = which - 1; 936 int32 barWidth = (int32)(7.0f / 32.0f * (float)which); 937 if (barWidth < 4) { 938 barWidth = 4; 939 yOffset = 0; 940 } else 941 yOffset = 2; 942 int32 barHeight = size - 3 - 2 * yOffset; 943 944 945 // the black shadowed line 946 view->SetHighColor(32, 32, 32, 92); 947 view->MovePenTo(BPoint(where.x + size, where.y + 1 + yOffset)); 948 view->StrokeLine(BPoint(where.x + size, where.y + size - yOffset)); 949 view->StrokeLine(BPoint(where.x + size - barWidth + 1, 950 where.y + size - yOffset)); 951 952 view->SetDrawingMode(B_OP_ALPHA); 953 954 // the gray frame 955 view->SetHighColor(76, 76, 76, 192); 956 BRect rect(where.x + size - barWidth,where.y + yOffset, 957 where.x + size - 1,where.y + size - 1 - yOffset); 958 view->StrokeRect(rect); 959 960 // calculate bar height 961 int32 barPos = barHeight - int32(barHeight * fDownloadProgress / 100.0); 962 if (barPos < 0) 963 barPos = 0; 964 else if (barPos > barHeight) 965 barPos = barHeight; 966 967 // the free space bar 968 view->SetHighColor(255, 255, 255, 192); 969 970 rect.InsetBy(1,1); 971 BRect bar(rect); 972 bar.bottom = bar.top + barPos - 1; 973 if (barPos > 0) 974 view->FillRect(bar); 975 976 // the used space bar 977 bar.top = bar.bottom + 1; 978 bar.bottom = rect.bottom; 979 view->SetHighColor(0, 203, 0, 192); 980 view->FillRect(bar); 981 } 982 983 984 void 985 PackageItem::Update(BView *owner, const BFont *font) 986 { 987 BListItem::Update(owner, font); 988 SetHeight(fSuperItem->GetPackageItemHeight()); 989 } 990 991 992 void 993 PackageItem::CalculateZoomWidths(BView *owner) 994 { 995 owner->PushState(); 996 997 // More details 998 float offsetWidth = 2 * be_control_look->DefaultItemSpacing() 999 + be_plain_font->Size() 1000 + fSuperItem->GetIconSize(true) + fLabelOffset; 1001 // Name and repo 1002 owner->SetFont(be_plain_font); 1003 float stringWidth = owner->StringWidth(fName.String()); 1004 owner->SetFont(&fSmallFont); 1005 stringWidth += fLabelOffset + owner->StringWidth(fRepository.String()); 1006 // Summary 1007 float summaryWidth = owner->StringWidth(fSummary.String()); 1008 if (summaryWidth > stringWidth) 1009 stringWidth = summaryWidth; 1010 // Version 1011 float versionWidth = owner->StringWidth(fDetailedVersion.String()); 1012 if (versionWidth > stringWidth) 1013 stringWidth = versionWidth; 1014 fMoreDetailsWidth = offsetWidth + stringWidth; 1015 1016 // Less details 1017 offsetWidth = 2 * be_control_look->DefaultItemSpacing() 1018 + be_plain_font->Size() 1019 + fSuperItem->GetIconSize(false) + fLabelOffset; 1020 // Name and version 1021 owner->SetFont(be_plain_font); 1022 stringWidth = owner->StringWidth(fName.String()); 1023 owner->SetFont(&fSmallFont); 1024 stringWidth += fLabelOffset + owner->StringWidth(fSimpleVersion.String()); 1025 // Summary 1026 if (summaryWidth > stringWidth) 1027 stringWidth = summaryWidth; 1028 fLessDetailsWidth = offsetWidth + stringWidth; 1029 1030 owner->PopState(); 1031 } 1032 1033 1034 int 1035 PackageItem::NameCompare(PackageItem* item) 1036 { 1037 // sort by package name 1038 return fName.ICompare(item->fName); 1039 } 1040 1041 1042 void 1043 PackageItem::SetDownloadProgress(float percent) 1044 { 1045 fDownloadProgress = percent; 1046 } 1047 1048 1049 int 1050 SortPackageItems(const BListItem* item1, const BListItem* item2) 1051 { 1052 PackageItem* first = (PackageItem*)item1; 1053 PackageItem* second = (PackageItem*)item2; 1054 return first->NameCompare(second); 1055 } 1056 1057 1058 PackageListView::PackageListView() 1059 : 1060 BOutlineListView("Package list"), 1061 fSuperUpdateItem(NULL), 1062 fSuperInstallItem(NULL), 1063 fSuperUninstallItem(NULL), 1064 fShowMoreDetails(false), 1065 fLastProgressItem(NULL), 1066 fLastProgressValue(-1) 1067 { 1068 SetExplicitMinSize(BSize(B_SIZE_UNSET, 40)); 1069 SetExplicitPreferredSize(BSize(B_SIZE_UNSET, 400)); 1070 } 1071 1072 1073 void 1074 PackageListView::FrameResized(float newWidth, float newHeight) 1075 { 1076 BOutlineListView::FrameResized(newWidth, newHeight); 1077 Invalidate(); 1078 } 1079 1080 1081 void 1082 PackageListView::ExpandOrCollapse(BListItem *superItem, bool expand) 1083 { 1084 BOutlineListView::ExpandOrCollapse(superItem, expand); 1085 Window()->PostMessage(kMsgSetZoomLimits); 1086 } 1087 1088 1089 void 1090 PackageListView::AddPackage(uint32 install_type, const char* name, 1091 const char* cur_ver, const char* new_ver, const char* summary, 1092 const char* repository, const char* file_name) 1093 { 1094 SuperItem* super; 1095 BString simpleVersion; 1096 BString detailedVersion(""); 1097 BString repositoryText(B_TRANSLATE_COMMENT("from repository", 1098 "List item text")); 1099 repositoryText.Append(" ").Append(repository); 1100 1101 switch (install_type) { 1102 case PACKAGE_UPDATE: 1103 { 1104 if (fSuperUpdateItem == NULL) { 1105 fSuperUpdateItem = new SuperItem(B_TRANSLATE_COMMENT( 1106 "Packages to be updated", "List super item label")); 1107 AddItem(fSuperUpdateItem); 1108 } 1109 super = fSuperUpdateItem; 1110 1111 simpleVersion.SetTo(new_ver); 1112 detailedVersion.Append(B_TRANSLATE_COMMENT("Updating version", 1113 "List item text")) 1114 .Append(" ").Append(cur_ver) 1115 .Append(" ").Append(B_TRANSLATE_COMMENT("to", 1116 "List item text")) 1117 .Append(" ").Append(new_ver); 1118 break; 1119 } 1120 1121 case PACKAGE_INSTALL: 1122 { 1123 if (fSuperInstallItem == NULL) { 1124 fSuperInstallItem = new SuperItem(B_TRANSLATE_COMMENT( 1125 "New packages to be installed", "List super item label")); 1126 AddItem(fSuperInstallItem); 1127 } 1128 super = fSuperInstallItem; 1129 1130 simpleVersion.SetTo(new_ver); 1131 detailedVersion.Append(B_TRANSLATE_COMMENT("Installing version", 1132 "List item text")) 1133 .Append(" ").Append(new_ver); 1134 break; 1135 } 1136 1137 case PACKAGE_UNINSTALL: 1138 { 1139 if (fSuperUninstallItem == NULL) { 1140 fSuperUninstallItem = new SuperItem(B_TRANSLATE_COMMENT( 1141 "Packages to be uninstalled", "List super item label")); 1142 AddItem(fSuperUninstallItem); 1143 } 1144 super = fSuperUninstallItem; 1145 1146 simpleVersion.SetTo(""); 1147 detailedVersion.Append(B_TRANSLATE_COMMENT("Uninstalling version", 1148 "List item text")) 1149 .Append(" ").Append(cur_ver); 1150 break; 1151 } 1152 1153 default: 1154 return; 1155 1156 } 1157 PackageItem* item = new PackageItem(name, simpleVersion.String(), 1158 detailedVersion.String(), repositoryText.String(), summary, file_name, 1159 super); 1160 AddUnder(item, super); 1161 super->SetItemCount(CountItemsUnder(super, true)); 1162 item->CalculateZoomWidths(this); 1163 } 1164 1165 1166 void 1167 PackageListView::UpdatePackageProgress(const char* packageName, float percent) 1168 { 1169 // Update only every 1 percent change 1170 int16 wholePercent = int16(percent); 1171 if (wholePercent == fLastProgressValue) 1172 return; 1173 fLastProgressValue = wholePercent; 1174 1175 // A new package started downloading, find the PackageItem by name 1176 if (percent == 0) { 1177 fLastProgressItem = NULL; 1178 int32 count = FullListCountItems(); 1179 for (int32 i = 0; i < count; i++) { 1180 PackageItem* item = dynamic_cast<PackageItem*>(FullListItemAt(i)); 1181 if (item != NULL && strcmp(item->FileName(), packageName) == 0) { 1182 fLastProgressItem = item; 1183 fLastProgressItem->ShowProgressBar(); 1184 break; 1185 } 1186 } 1187 } 1188 1189 if (fLastProgressItem != NULL) { 1190 fLastProgressItem->SetDownloadProgress(percent); 1191 Invalidate(); 1192 } 1193 } 1194 1195 1196 void 1197 PackageListView::SortItems() 1198 { 1199 if (fSuperUpdateItem != NULL) 1200 SortItemsUnder(fSuperUpdateItem, true, SortPackageItems); 1201 if (fSuperInstallItem != NULL) 1202 SortItemsUnder(fSuperInstallItem, true, SortPackageItems); 1203 if (fSuperUninstallItem != NULL) 1204 SortItemsUnder(fSuperUninstallItem, true, SortPackageItems); 1205 } 1206 1207 1208 float 1209 PackageListView::ItemHeight() 1210 { 1211 if (fSuperUpdateItem != NULL) 1212 return fSuperUpdateItem->GetPackageItemHeight(); 1213 if (fSuperInstallItem != NULL) 1214 return fSuperInstallItem->GetPackageItemHeight(); 1215 if (fSuperUninstallItem != NULL) 1216 return fSuperUninstallItem->GetPackageItemHeight(); 1217 return 0; 1218 } 1219 1220 1221 void 1222 PackageListView::SetMoreDetails(bool showMore) 1223 { 1224 if (showMore == fShowMoreDetails) 1225 return; 1226 fShowMoreDetails = showMore; 1227 _SetItemHeights(); 1228 InvalidateLayout(); 1229 ResizeToPreferred(); 1230 } 1231 1232 1233 BPoint 1234 PackageListView::ZoomPoint() 1235 { 1236 BPoint zoomPoint(0, 0); 1237 int32 count = CountItems(); 1238 for (int32 i = 0; i < count; i++) 1239 { 1240 BListItem* item = ItemAt(i); 1241 float itemWidth = 0; 1242 if (item->OutlineLevel() == 0) { 1243 SuperItem* sItem = dynamic_cast<SuperItem*>(item); 1244 itemWidth = sItem->ZoomWidth(this); 1245 } else { 1246 PackageItem* pItem = dynamic_cast<PackageItem*>(item); 1247 itemWidth = fShowMoreDetails ? pItem->MoreDetailsWidth() 1248 : pItem->LessDetailsWidth(); 1249 } 1250 if (itemWidth > zoomPoint.x) 1251 zoomPoint.x = itemWidth; 1252 } 1253 if (count > 0) 1254 zoomPoint.y = ItemFrame(count - 1).bottom; 1255 1256 return zoomPoint; 1257 } 1258 1259 1260 void 1261 PackageListView::_SetItemHeights() 1262 { 1263 int32 itemCount = 0; 1264 float itemHeight = 0; 1265 BListItem* item = NULL; 1266 if (fSuperUpdateItem != NULL) { 1267 fSuperUpdateItem->SetDetailLevel(fShowMoreDetails); 1268 itemHeight = fSuperUpdateItem->GetPackageItemHeight(); 1269 itemCount = CountItemsUnder(fSuperUpdateItem, true); 1270 for (int32 i = 0; i < itemCount; i++) { 1271 item = ItemUnderAt(fSuperUpdateItem, true, i); 1272 item->SetHeight(itemHeight); 1273 } 1274 } 1275 if (fSuperInstallItem != NULL) { 1276 fSuperInstallItem->SetDetailLevel(fShowMoreDetails); 1277 itemHeight = fSuperInstallItem->GetPackageItemHeight(); 1278 itemCount = CountItemsUnder(fSuperInstallItem, true); 1279 for (int32 i = 0; i < itemCount; i++) { 1280 item = ItemUnderAt(fSuperInstallItem, true, i); 1281 item->SetHeight(itemHeight); 1282 } 1283 1284 } 1285 if (fSuperUninstallItem != NULL) { 1286 fSuperUninstallItem->SetDetailLevel(fShowMoreDetails); 1287 itemHeight = fSuperUninstallItem->GetPackageItemHeight(); 1288 itemCount = CountItemsUnder(fSuperUninstallItem, true); 1289 for (int32 i = 0; i < itemCount; i++) { 1290 item = ItemUnderAt(fSuperUninstallItem, true, i); 1291 item->SetHeight(itemHeight); 1292 } 1293 1294 } 1295 } 1296