/* * Copyright 2016-2019 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT license * * Authors: * Alexander von Gluck IV * Brian Hill * Jacob Secunda */ #include "SoftwareUpdaterWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "constants.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "SoftwareUpdaterWindow" SoftwareUpdaterWindow::SoftwareUpdaterWindow() : BWindow(BRect(0, 0, 300, 10), B_TRANSLATE_SYSTEM_NAME("SoftwareUpdater"), B_TITLED_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_ZOOMABLE | B_NOT_RESIZABLE), fStripeView(NULL), fHeaderView(NULL), fDetailView(NULL), fUpdateButton(NULL), fCancelButton(NULL), fStatusBar(NULL), fCurrentState(STATE_HEAD), fWaitingSem(-1), fWaitingForButton(false), fUpdateConfirmed(false), fUserCancelRequested(false), fWarningAlertCount(0), fSettingsReadStatus(B_ERROR), fSaveFrameChanges(false), fMessageRunner(NULL), fFrameChangeMessage(kMsgWindowFrameChanged) { // Layout BBitmap icon = GetIcon(32 * icon_layout_scale()); fStripeView = new BStripeView(icon); fUpdateButton = new BButton(B_TRANSLATE("Update now"), new BMessage(kMsgUpdateConfirmed)); fUpdateButton->MakeDefault(true); fCancelButton = new BButton(B_TRANSLATE("Cancel"), new BMessage(kMsgCancel)); fRebootButton = new BButton(B_TRANSLATE("Reboot"), new BMessage(kMsgReboot)); fHeaderView = new BStringView("header", B_TRANSLATE("Checking for updates"), B_WILL_DRAW); fHeaderView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); fHeaderView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP)); fDetailView = new BStringView("detail", B_TRANSLATE("Contacting software " "repositories to check for package updates."), B_WILL_DRAW); fDetailView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); fDetailView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP)); fStatusBar = new BStatusBar("progress"); fStatusBar->SetMaxValue(100); fListView = new PackageListView(); fScrollView = new BScrollView("scrollview", fListView, B_WILL_DRAW, false, true); fDetailsCheckbox = new BCheckBox("detailscheckbox", B_TRANSLATE("Show more details"), new BMessage(kMsgMoreDetailsToggle)); BFont font; fHeaderView->GetFont(&font); font.SetFace(B_BOLD_FACE); font.SetSize(font.Size() * 1.5); fHeaderView->SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE | B_FONT_FLAGS); BLayoutBuilder::Group<>(this, B_HORIZONTAL, B_USE_ITEM_SPACING) .Add(fStripeView) .AddGroup(B_VERTICAL, 0) .SetInsets(0, B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING) .AddGroup(new BGroupView(B_VERTICAL, B_USE_ITEM_SPACING)) .Add(fHeaderView) .Add(fDetailView) .Add(fStatusBar) .Add(fScrollView) .End() .AddStrut(B_USE_SMALL_SPACING) .AddGroup(new BGroupView(B_HORIZONTAL)) .Add(fDetailsCheckbox) .AddGlue() .Add(fCancelButton) .Add(fUpdateButton) .Add(fRebootButton) .End() .End() .End(); fDetailsLayoutItem = layout_item_for(fDetailView); fProgressLayoutItem = layout_item_for(fStatusBar); fPackagesLayoutItem = layout_item_for(fScrollView); fCancelButtonLayoutItem = layout_item_for(fCancelButton); fUpdateButtonLayoutItem = layout_item_for(fUpdateButton); fRebootButtonLayoutItem = layout_item_for(fRebootButton); fDetailsCheckboxLayoutItem = layout_item_for(fDetailsCheckbox); _SetState(STATE_DISPLAY_STATUS); CenterOnScreen(); SetFlags(Flags() ^ B_AUTO_UPDATE_SIZE_LIMITS); // Prevent resizing for now fDefaultRect = Bounds(); SetSizeLimits(fDefaultRect.Width(), fDefaultRect.Width(), fDefaultRect.Height(), fDefaultRect.Height()); // Read settings file status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &fSettingsPath); if (status == B_OK) { fSettingsPath.Append(kSettingsFilename); fSettingsReadStatus = _ReadSettings(fInitialSettings); } // Move to saved setting position if (fSettingsReadStatus == B_OK) { BRect windowFrame; status = fInitialSettings.FindRect(kKeyWindowFrame, &windowFrame); if (status == B_OK) { BScreen screen(this); if (screen.Frame().Contains(windowFrame.LeftTop())) MoveTo(windowFrame.LeftTop()); } } Show(); BMessage registerMessage(kMsgRegister); registerMessage.AddMessenger(kKeyMessenger, BMessenger(this)); be_app->PostMessage(®isterMessage); fCancelAlertResponse.SetMessage(new BMessage(kMsgCancelResponse)); fCancelAlertResponse.SetTarget(this); fWarningAlertDismissed.SetMessage(new BMessage(kMsgWarningDismissed)); fWarningAlertDismissed.SetTarget(this); // Common elements used for the zoom height and width calculations fZoomHeightBaseline = 6 + be_control_look->ComposeSpacing(B_USE_SMALL_SPACING) + 2 * be_control_look->ComposeSpacing(B_USE_WINDOW_SPACING); fZoomWidthBaseline = fStripeView->PreferredSize().Width() + be_control_look->ComposeSpacing(B_USE_ITEM_SPACING) + fScrollView->ScrollBar(B_VERTICAL)->PreferredSize().Width() + be_control_look->ComposeSpacing(B_USE_WINDOW_SPACING); } bool SoftwareUpdaterWindow::QuitRequested() { PostMessage(kMsgCancel); return false; } void SoftwareUpdaterWindow::FrameMoved(BPoint newPosition) { BWindow::FrameMoved(newPosition); // Create a message runner to consolidate all function calls from a // move into one message post after moving has ceased for .5 seconds if (fSaveFrameChanges) { if (fMessageRunner == NULL) { fMessageRunner = new BMessageRunner(this, &fFrameChangeMessage, 500000, 1); } else fMessageRunner->SetInterval(500000); } } void SoftwareUpdaterWindow::FrameResized(float newWidth, float newHeight) { BWindow::FrameResized(newWidth, newHeight); // Create a message runner to consolidate all function calls from a // resize into one message post after resizing has ceased for .5 seconds if (fSaveFrameChanges) { if (fMessageRunner == NULL) { fMessageRunner = new BMessageRunner(this, &fFrameChangeMessage, 500000, 1); } else fMessageRunner->SetInterval(500000); } } void SoftwareUpdaterWindow::Zoom(BPoint origin, float width, float height) { // Override default zoom behavior and keep window at same position instead // of centering on screen BWindow::Zoom(Frame().LeftTop(), width, height); } void SoftwareUpdaterWindow::MessageReceived(BMessage* message) { switch (message->what) { case kMsgTextUpdate: { if (fCurrentState == STATE_DISPLAY_PROGRESS) _SetState(STATE_DISPLAY_STATUS); else if (fCurrentState != STATE_DISPLAY_STATUS) break; BString header; BString detail; Lock(); status_t result = message->FindString(kKeyHeader, &header); if (result == B_OK && header != fHeaderView->Text()) fHeaderView->SetText(header.String()); result = message->FindString(kKeyDetail, &detail); if (result == B_OK) fDetailView->SetText(detail.String()); Unlock(); break; } case kMsgProgressUpdate: { if (fCurrentState == STATE_DISPLAY_STATUS) _SetState(STATE_DISPLAY_PROGRESS); else if (fCurrentState != STATE_DISPLAY_PROGRESS) break; BString packageName; status_t result = message->FindString(kKeyPackageName, &packageName); if (result != B_OK) break; BString packageCount; result = message->FindString(kKeyPackageCount, &packageCount); if (result != B_OK) break; float percent; result = message->FindFloat(kKeyPercentage, &percent); if (result != B_OK) break; BString header; Lock(); result = message->FindString(kKeyHeader, &header); if (result == B_OK && header != fHeaderView->Text()) fHeaderView->SetText(header.String()); fStatusBar->SetTo(percent, packageName.String(), packageCount.String()); Unlock(); fListView->UpdatePackageProgress(packageName.String(), percent); break; } case kMsgCancel: { if (_GetState() == STATE_FINAL_MESSAGE) { be_app->PostMessage(kMsgFinalQuit); break; } if (!fUpdateConfirmed) { // Downloads have not started yet, we will request to cancel // without confirming Lock(); fHeaderView->SetText(B_TRANSLATE("Cancelling updates")); fDetailView->SetText( B_TRANSLATE("Attempting to cancel the updates" B_UTF8_ELLIPSIS)); Unlock(); fUserCancelRequested = true; if (fWaitingForButton) { fButtonResult = message->what; delete_sem(fWaitingSem); fWaitingSem = -1; } break; } // Confirm with the user to cancel BAlert* alert = new BAlert("cancel request", B_TRANSLATE("Updates" " have not been completed, are you sure you want to quit?"), B_TRANSLATE("Quit"), B_TRANSLATE("Don't quit"), NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); alert->Go(&fCancelAlertResponse); break; } case kMsgShowReboot: { fRebootButtonLayoutItem->SetVisible(true); fRebootButton->SetLabel(B_TRANSLATE_COMMENT("Reboot", "Button label")); fRebootButton->MakeDefault(true); break; } case kMsgReboot: { if (_GetState() != STATE_FINAL_MESSAGE) break; BRoster roster; BRoster::Private rosterPrivate(roster); status_t error = rosterPrivate.ShutDown(true, false, false); if (error != B_OK) { BAlert* alert = new BAlert("reboot request", B_TRANSLATE( "For some reason, we could not reboot your computer."), B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); alert->Go(); } break; } case kMsgCancelResponse: { // Verify whether the cancel alert was confirmed int32 selection = -1; message->FindInt32("which", &selection); if (selection != 0) break; Lock(); fHeaderView->SetText(B_TRANSLATE("Cancelling updates")); fDetailView->SetText( B_TRANSLATE("Attempting to cancel the updates" B_UTF8_ELLIPSIS)); Unlock(); fUserCancelRequested = true; if (fWaitingForButton) { fButtonResult = message->what; delete_sem(fWaitingSem); fWaitingSem = -1; } break; } case kMsgUpdateConfirmed: { if (fWaitingForButton) { fButtonResult = message->what; delete_sem(fWaitingSem); fWaitingSem = -1; fUpdateConfirmed = true; } break; } case kMsgMoreDetailsToggle: fListView->SetMoreDetails(fDetailsCheckbox->Value() != 0); PostMessage(kMsgSetZoomLimits); _WriteSettings(); break; case kMsgSetZoomLimits: { int32 count = fListView->CountItems(); if (count < 1) break; // Convert last item's bottom point to its layout group coordinates BPoint zoomPoint = fListView->ZoomPoint(); fScrollView->ConvertToParent(&zoomPoint); // Determine which BControl object height to use float controlHeight; if (fUpdateButtonLayoutItem->IsVisible()) fUpdateButton->GetPreferredSize(NULL, &controlHeight); else fDetailsCheckbox->GetPreferredSize(NULL, &controlHeight); // Calculate height and width values float zoomHeight = fZoomHeightBaseline + zoomPoint.y + controlHeight; float zoomWidth = fZoomWidthBaseline + zoomPoint.x; SetZoomLimits(zoomWidth, zoomHeight); break; } case kMsgWarningDismissed: fWarningAlertCount--; break; case kMsgWindowFrameChanged: delete fMessageRunner; fMessageRunner = NULL; _WriteSettings(); break; case kMsgGetUpdateType: { BString text( B_TRANSLATE("Please choose from these update options:\n\n" "Update:\n" " Updates all installed packages.\n" "Full sync:\n" " Synchronizes the installed packages with the repositories." )); BAlert* alert = new BAlert("update_type", text, B_TRANSLATE_COMMENT("Cancel", "Alert button label"), B_TRANSLATE_COMMENT("Full sync","Alert button label"), B_TRANSLATE_COMMENT("Update","Alert button label"), B_WIDTH_AS_USUAL, B_INFO_ALERT); int32 result = alert->Go(); int32 action = INVALID_SELECTION; switch(result) { case 0: action = CANCEL_UPDATE; break; case 1: action = FULLSYNC; break; case 2: action = UPDATE; break; } BMessage reply; reply.AddInt32(kKeyAlertResult, action); message->SendReply(&reply); break; } case kMsgNoRepositories: { BString text( B_TRANSLATE_COMMENT( "No remote repositories are available. Please verify that some" " repositories are enabled using the Repositories preflet or" " the \'pkgman\' command.", "Error message")); BAlert* alert = new BAlert("repositories", text, B_TRANSLATE_COMMENT("Quit", "Alert button label"), B_TRANSLATE_COMMENT("Open Repositories","Alert button label"), NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); int32 result = alert->Go(); BMessage reply; reply.AddInt32(kKeyAlertResult, result); message->SendReply(&reply); break; } default: BWindow::MessageReceived(message); } } bool SoftwareUpdaterWindow::ConfirmUpdates() { Lock(); fHeaderView->SetText(B_TRANSLATE("Updates found")); fDetailView->SetText(B_TRANSLATE("The following changes will be made:")); fListView->SortItems(); Unlock(); uint32 priorState = _GetState(); _SetState(STATE_GET_CONFIRMATION); _WaitForButtonClick(); _SetState(priorState); return fButtonResult == kMsgUpdateConfirmed; } void SoftwareUpdaterWindow::UpdatesApplying(const char* header, const char* detail) { Lock(); fHeaderView->SetText(header); fDetailView->SetText(detail); Unlock(); _SetState(STATE_APPLY_UPDATES); } bool SoftwareUpdaterWindow::UserCancelRequested() { if (_GetState() > STATE_GET_CONFIRMATION) return false; return fUserCancelRequested; } void SoftwareUpdaterWindow::AddPackageInfo(uint32 install_type, const char* package_name, const char* cur_ver, const char* new_ver, const char* summary, const char* repository, const char* file_name) { Lock(); fListView->AddPackage(install_type, package_name, cur_ver, new_ver, summary, repository, file_name); Unlock(); } void SoftwareUpdaterWindow::ShowWarningAlert(const char* text) { BAlert* alert = new BAlert("warning", text, B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); alert->Go(&fWarningAlertDismissed); alert->CenterIn(Frame()); // Offset multiple alerts alert->MoveBy(fWarningAlertCount * 15, fWarningAlertCount * 15); fWarningAlertCount++; } BBitmap SoftwareUpdaterWindow::GetIcon(int32 iconSize) { BBitmap icon(BRect(0, 0, iconSize - 1, iconSize - 1), 0, B_RGBA32); team_info teamInfo; get_team_info(B_CURRENT_TEAM, &teamInfo); app_info appInfo; be_roster->GetRunningAppInfo(teamInfo.team, &appInfo); BNodeInfo::GetTrackerIcon(&appInfo.ref, &icon, icon_size(iconSize)); return icon; } void SoftwareUpdaterWindow::FinalUpdate(const char* header, const char* detail) { if (_GetState() == STATE_FINAL_MESSAGE) return; _SetState(STATE_FINAL_MESSAGE); Lock(); fHeaderView->SetText(header); fDetailView->SetText(detail); Unlock(); } BLayoutItem* SoftwareUpdaterWindow::layout_item_for(BView* view) { BLayout* layout = view->Parent()->GetLayout(); int32 index = layout->IndexOfView(view); return layout->ItemAt(index); } uint32 SoftwareUpdaterWindow::_WaitForButtonClick() { fButtonResult = 0; fWaitingForButton = true; fWaitingSem = create_sem(0, "WaitingSem"); while (acquire_sem(fWaitingSem) == B_INTERRUPTED) { } fWaitingForButton = false; return fButtonResult; } void SoftwareUpdaterWindow::_SetState(uint32 state) { if (state <= STATE_HEAD || state >= STATE_MAX) return; Lock(); // Initial settings if (fCurrentState == STATE_HEAD) { fProgressLayoutItem->SetVisible(false); fPackagesLayoutItem->SetVisible(false); fDetailsCheckboxLayoutItem->SetVisible(false); fCancelButtonLayoutItem->SetVisible(false); fRebootButtonLayoutItem->SetVisible(false); } fCurrentState = state; // Update confirmation button // Show only when asking for confirmation to update if (fCurrentState == STATE_GET_CONFIRMATION) fUpdateButtonLayoutItem->SetVisible(true); else fUpdateButtonLayoutItem->SetVisible(false); // View package info view and checkbox // Show at confirmation prompt, hide at final update if (fCurrentState == STATE_GET_CONFIRMATION) { fPackagesLayoutItem->SetVisible(true); fDetailsCheckboxLayoutItem->SetVisible(true); if (fSettingsReadStatus == B_OK) { bool showMoreDetails; status_t result = fInitialSettings.FindBool(kKeyShowDetails, &showMoreDetails); if (result == B_OK) { fDetailsCheckbox->SetValue(showMoreDetails ? 1 : 0); fListView->SetMoreDetails(showMoreDetails); } } } else if (fCurrentState == STATE_FINAL_MESSAGE) { fPackagesLayoutItem->SetVisible(false); fDetailsCheckboxLayoutItem->SetVisible(false); } // Progress bar and string view // Hide detail text while showing status bar if (fCurrentState == STATE_DISPLAY_PROGRESS) { fDetailsLayoutItem->SetVisible(false); fProgressLayoutItem->SetVisible(true); } else { fProgressLayoutItem->SetVisible(false); fDetailsLayoutItem->SetVisible(true); } // Resizing and zooming if (fCurrentState == STATE_GET_CONFIRMATION) { // Enable resizing and zooming float defaultWidth = fDefaultRect.Width(); SetSizeLimits(defaultWidth, B_SIZE_UNLIMITED, fDefaultRect.Height() + 4 * fListView->ItemHeight(), B_SIZE_UNLIMITED); SetFlags(Flags() ^ (B_NOT_RESIZABLE | B_NOT_ZOOMABLE)); PostMessage(kMsgSetZoomLimits); // Recall saved settings BScreen screen(this); BRect screenFrame = screen.Frame(); bool windowResized = false; if (fSettingsReadStatus == B_OK) { BRect windowFrame; status_t result = fInitialSettings.FindRect(kKeyWindowFrame, &windowFrame); if (result == B_OK) { if (screenFrame.Contains(windowFrame)) { ResizeTo(windowFrame.Width(), windowFrame.Height()); windowResized = true; } } } if (!windowResized) ResizeTo(defaultWidth, .75 * defaultWidth); // Check that the bottom of window is on screen float screenBottom = screenFrame.bottom; float windowBottom = DecoratorFrame().bottom; if (windowBottom > screenBottom) MoveBy(0, screenBottom - windowBottom); fSaveFrameChanges = true; } else if (fUpdateConfirmed && (fCurrentState == STATE_DISPLAY_PROGRESS || fCurrentState == STATE_DISPLAY_STATUS)) { PostMessage(kMsgSetZoomLimits); } else if (fCurrentState == STATE_APPLY_UPDATES) fSaveFrameChanges = false; else if (fCurrentState == STATE_FINAL_MESSAGE) { // Disable resizing and zooming fSaveFrameChanges = false; ResizeTo(fDefaultRect.Width(), fDefaultRect.Height()); SetFlags(Flags() | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_RESIZABLE | B_NOT_ZOOMABLE); } // Quit button if (fCurrentState == STATE_FINAL_MESSAGE) { fCancelButtonLayoutItem->SetVisible(true); fCancelButton->SetLabel(B_TRANSLATE_COMMENT("Quit", "Button label")); fCancelButton->MakeDefault(true); } Unlock(); } uint32 SoftwareUpdaterWindow::_GetState() { return fCurrentState; } status_t SoftwareUpdaterWindow::_WriteSettings() { BFile file; status_t status = file.SetTo(fSettingsPath.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); if (status == B_OK) { BMessage settings; settings.AddBool(kKeyShowDetails, fDetailsCheckbox->Value() != 0); settings.AddRect(kKeyWindowFrame, Frame()); status = settings.Flatten(&file); } file.Unset(); return status; } status_t SoftwareUpdaterWindow::_ReadSettings(BMessage& settings) { BFile file; status_t status = file.SetTo(fSettingsPath.Path(), B_READ_ONLY); if (status == B_OK) status = settings.Unflatten(&file); file.Unset(); return status; } SuperItem::SuperItem(const char* label) : BListItem(), fLabel(label), fRegularFont(be_plain_font), fBoldFont(be_plain_font), fShowMoreDetails(false), fPackageLessIcon(NULL), fPackageMoreIcon(NULL), fItemCount(0) { fBoldFont.SetFace(B_BOLD_FACE); fBoldFont.GetHeight(&fBoldFontHeight); font_height fontHeight; fRegularFont.GetHeight(&fontHeight); fPackageItemLineHeight = fontHeight.ascent + fontHeight.descent + fontHeight.leading; fPackageLessIcon = _GetPackageIcon(GetPackageItemHeight(false)); fPackageMoreIcon = _GetPackageIcon(GetPackageItemHeight(true)); } SuperItem::~SuperItem() { delete fPackageLessIcon; delete fPackageMoreIcon; } void SuperItem::DrawItem(BView* owner, BRect item_rect, bool complete) { owner->PushState(); float width; owner->GetPreferredSize(&width, NULL); BString text(fItemText); owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); owner->SetFont(&fBoldFont); owner->TruncateString(&text, B_TRUNCATE_END, width); owner->DrawString(text.String(), BPoint(item_rect.left, item_rect.bottom - fBoldFontHeight.descent)); owner->PopState(); } float SuperItem::GetPackageItemHeight() { return GetPackageItemHeight(fShowMoreDetails); } float SuperItem::GetPackageItemHeight(bool showMoreDetails) { int lineCount = showMoreDetails ? 3 : 2; return lineCount * fPackageItemLineHeight; } BBitmap* SuperItem::GetIcon(bool showMoreDetails) { if (showMoreDetails) return fPackageMoreIcon; else return fPackageLessIcon; } float SuperItem::GetIconSize(bool showMoreDetails) { if (showMoreDetails) return fPackageMoreIcon->Bounds().Height(); else return fPackageLessIcon->Bounds().Height(); } void SuperItem::SetDetailLevel(bool showMoreDetails) { fShowMoreDetails = showMoreDetails; } void SuperItem::SetItemCount(int32 count) { fItemCount = count; fItemText = fLabel; fItemText.Append(" ("); fItemText << fItemCount; fItemText.Append(")"); } float SuperItem::ZoomWidth(BView *owner) { owner->PushState(); owner->SetFont(&fBoldFont); float width = owner->StringWidth(fItemText.String()); owner->PopState(); return width; } BBitmap* SuperItem::_GetPackageIcon(float listItemHeight) { int32 iconSize = int(listItemHeight * .8); status_t result = B_ERROR; BRect iconRect(0, 0, iconSize - 1, iconSize - 1); BBitmap* packageIcon = new BBitmap(iconRect, 0, B_RGBA32); BMimeType nodeType; nodeType.SetTo("application/x-vnd.haiku-package"); result = nodeType.GetIcon(packageIcon, icon_size(iconSize)); // Get super type icon if (result != B_OK) { BMimeType superType; if (nodeType.GetSupertype(&superType) == B_OK) result = superType.GetIcon(packageIcon, icon_size(iconSize)); } if (result != B_OK) { delete packageIcon; return NULL; } return packageIcon; } PackageItem::PackageItem(const char* name, const char* simple_version, const char* detailed_version, const char* repository, const char* summary, const char* file_name, SuperItem* super) : BListItem(), fName(name), fSimpleVersion(simple_version), fDetailedVersion(detailed_version), fRepository(repository), fSummary(summary), fSmallFont(be_plain_font), fSuperItem(super), fFileName(file_name), fDownloadProgress(0), fDrawBarFlag(false), fMoreDetailsWidth(0), fLessDetailsWidth(0) { fLabelOffset = be_control_look->DefaultLabelSpacing(); fSmallFont.SetSize(be_plain_font->Size() - 2); fSmallFont.GetHeight(&fSmallFontHeight); fSmallTotalHeight = fSmallFontHeight.ascent + fSmallFontHeight.descent + fSmallFontHeight.leading; } void PackageItem::DrawItem(BView* owner, BRect item_rect, bool complete) { owner->PushState(); float width = owner->Frame().Width(); float nameWidth = width / 2.0; float offsetWidth = 0; bool showMoreDetails = fSuperItem->GetDetailLevel(); BBitmap* icon = fSuperItem->GetIcon(showMoreDetails); if (icon != NULL && icon->IsValid()) { float iconSize = icon->Bounds().Height(); float offsetMarginHeight = floor((Height() - iconSize) / 2); owner->SetDrawingMode(B_OP_ALPHA); BPoint location = BPoint(item_rect.left, item_rect.top + offsetMarginHeight); owner->DrawBitmap(icon, location); owner->SetDrawingMode(B_OP_COPY); offsetWidth = iconSize + fLabelOffset; if (fDrawBarFlag) _DrawBar(location, owner, icon_size(iconSize)); } owner->SetFont(be_plain_font); owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); // Package name BString name(fName); owner->TruncateString(&name, B_TRUNCATE_END, nameWidth); BPoint cursor(item_rect.left + offsetWidth, item_rect.bottom - fSmallTotalHeight - fSmallFontHeight.descent - 2); if (showMoreDetails) cursor.y -= fSmallTotalHeight + 1; owner->DrawString(name.String(), cursor); cursor.x += owner->StringWidth(name.String()) + fLabelOffset; // Change font and color owner->SetFont(&fSmallFont); owner->SetHighColor(tint_color(ui_color(B_LIST_ITEM_TEXT_COLOR), 0.7)); // Simple version or repository BString versionOrRepo; if (showMoreDetails) versionOrRepo.SetTo(fRepository); else versionOrRepo.SetTo(fSimpleVersion); owner->TruncateString(&versionOrRepo, B_TRUNCATE_END, width - cursor.x); owner->DrawString(versionOrRepo.String(), cursor); // Summary BString summary(fSummary); cursor.x = item_rect.left + offsetWidth; cursor.y += fSmallTotalHeight; owner->TruncateString(&summary, B_TRUNCATE_END, width - cursor.x); owner->DrawString(summary.String(), cursor); // Detailed version if (showMoreDetails) { BString version(fDetailedVersion); cursor.y += fSmallTotalHeight; owner->TruncateString(&version, B_TRUNCATE_END, width - cursor.x); owner->DrawString(version.String(), cursor); } owner->PopState(); } // Modified slightly from Tracker's BPose::DrawBar void PackageItem::_DrawBar(BPoint where, BView* view, icon_size which) { int32 yOffset; int32 size = which - 1; int32 barWidth = (int32)(7.0f / 32.0f * (float)which); if (barWidth < 4) { barWidth = 4; yOffset = 0; } else yOffset = 2; int32 barHeight = size - 3 - 2 * yOffset; // the black shadowed line view->SetHighColor(32, 32, 32, 92); view->MovePenTo(BPoint(where.x + size, where.y + 1 + yOffset)); view->StrokeLine(BPoint(where.x + size, where.y + size - yOffset)); view->StrokeLine(BPoint(where.x + size - barWidth + 1, where.y + size - yOffset)); view->SetDrawingMode(B_OP_ALPHA); // the gray frame view->SetHighColor(76, 76, 76, 192); BRect rect(where.x + size - barWidth,where.y + yOffset, where.x + size - 1,where.y + size - 1 - yOffset); view->StrokeRect(rect); // calculate bar height int32 barPos = barHeight - int32(barHeight * fDownloadProgress / 100.0); if (barPos < 0) barPos = 0; else if (barPos > barHeight) barPos = barHeight; // the free space bar view->SetHighColor(255, 255, 255, 192); rect.InsetBy(1,1); BRect bar(rect); bar.bottom = bar.top + barPos - 1; if (barPos > 0) view->FillRect(bar); // the used space bar bar.top = bar.bottom + 1; bar.bottom = rect.bottom; view->SetHighColor(0, 203, 0, 192); view->FillRect(bar); } void PackageItem::Update(BView *owner, const BFont *font) { BListItem::Update(owner, font); SetHeight(fSuperItem->GetPackageItemHeight()); } void PackageItem::CalculateZoomWidths(BView *owner) { owner->PushState(); // More details float offsetWidth = 2 * be_control_look->DefaultItemSpacing() + be_plain_font->Size() + fSuperItem->GetIconSize(true) + fLabelOffset; // Name and repo owner->SetFont(be_plain_font); float stringWidth = owner->StringWidth(fName.String()); owner->SetFont(&fSmallFont); stringWidth += fLabelOffset + owner->StringWidth(fRepository.String()); // Summary float summaryWidth = owner->StringWidth(fSummary.String()); if (summaryWidth > stringWidth) stringWidth = summaryWidth; // Version float versionWidth = owner->StringWidth(fDetailedVersion.String()); if (versionWidth > stringWidth) stringWidth = versionWidth; fMoreDetailsWidth = offsetWidth + stringWidth; // Less details offsetWidth = 2 * be_control_look->DefaultItemSpacing() + be_plain_font->Size() + fSuperItem->GetIconSize(false) + fLabelOffset; // Name and version owner->SetFont(be_plain_font); stringWidth = owner->StringWidth(fName.String()); owner->SetFont(&fSmallFont); stringWidth += fLabelOffset + owner->StringWidth(fSimpleVersion.String()); // Summary if (summaryWidth > stringWidth) stringWidth = summaryWidth; fLessDetailsWidth = offsetWidth + stringWidth; owner->PopState(); } int PackageItem::NameCompare(PackageItem* item) { // sort by package name return fName.ICompare(item->fName); } void PackageItem::SetDownloadProgress(float percent) { fDownloadProgress = percent; } int SortPackageItems(const BListItem* item1, const BListItem* item2) { PackageItem* first = (PackageItem*)item1; PackageItem* second = (PackageItem*)item2; return first->NameCompare(second); } PackageListView::PackageListView() : BOutlineListView("Package list"), fSuperUpdateItem(NULL), fSuperInstallItem(NULL), fSuperUninstallItem(NULL), fShowMoreDetails(false), fLastProgressItem(NULL), fLastProgressValue(-1) { SetExplicitMinSize(BSize(B_SIZE_UNSET, 40)); SetExplicitPreferredSize(BSize(B_SIZE_UNSET, 400)); } void PackageListView::FrameResized(float newWidth, float newHeight) { BOutlineListView::FrameResized(newWidth, newHeight); Invalidate(); } void PackageListView::ExpandOrCollapse(BListItem *superItem, bool expand) { BOutlineListView::ExpandOrCollapse(superItem, expand); Window()->PostMessage(kMsgSetZoomLimits); } void PackageListView::AddPackage(uint32 install_type, const char* name, const char* cur_ver, const char* new_ver, const char* summary, const char* repository, const char* file_name) { SuperItem* super; BString simpleVersion; BString detailedVersion(""); BString repositoryText(B_TRANSLATE_COMMENT("from repository", "List item text")); repositoryText.Append(" ").Append(repository); switch (install_type) { case PACKAGE_UPDATE: { if (fSuperUpdateItem == NULL) { fSuperUpdateItem = new SuperItem(B_TRANSLATE_COMMENT( "Packages to be updated", "List super item label")); AddItem(fSuperUpdateItem); } super = fSuperUpdateItem; simpleVersion.SetTo(new_ver); detailedVersion.Append(B_TRANSLATE_COMMENT("Updating version", "List item text")) .Append(" ").Append(cur_ver) .Append(" ").Append(B_TRANSLATE_COMMENT("to", "List item text")) .Append(" ").Append(new_ver); break; } case PACKAGE_INSTALL: { if (fSuperInstallItem == NULL) { fSuperInstallItem = new SuperItem(B_TRANSLATE_COMMENT( "New packages to be installed", "List super item label")); AddItem(fSuperInstallItem); } super = fSuperInstallItem; simpleVersion.SetTo(new_ver); detailedVersion.Append(B_TRANSLATE_COMMENT("Installing version", "List item text")) .Append(" ").Append(new_ver); break; } case PACKAGE_UNINSTALL: { if (fSuperUninstallItem == NULL) { fSuperUninstallItem = new SuperItem(B_TRANSLATE_COMMENT( "Packages to be uninstalled", "List super item label")); AddItem(fSuperUninstallItem); } super = fSuperUninstallItem; simpleVersion.SetTo(""); detailedVersion.Append(B_TRANSLATE_COMMENT("Uninstalling version", "List item text")) .Append(" ").Append(cur_ver); break; } default: return; } PackageItem* item = new PackageItem(name, simpleVersion.String(), detailedVersion.String(), repositoryText.String(), summary, file_name, super); AddUnder(item, super); super->SetItemCount(CountItemsUnder(super, true)); item->CalculateZoomWidths(this); } void PackageListView::UpdatePackageProgress(const char* packageName, float percent) { // Update only every 1 percent change int16 wholePercent = int16(percent); if (wholePercent == fLastProgressValue) return; fLastProgressValue = wholePercent; // A new package started downloading, find the PackageItem by name if (percent == 0) { fLastProgressItem = NULL; int32 count = FullListCountItems(); for (int32 i = 0; i < count; i++) { PackageItem* item = dynamic_cast(FullListItemAt(i)); if (item != NULL && strcmp(item->FileName(), packageName) == 0) { fLastProgressItem = item; fLastProgressItem->ShowProgressBar(); break; } } } if (fLastProgressItem != NULL) { fLastProgressItem->SetDownloadProgress(percent); Invalidate(); } } void PackageListView::SortItems() { if (fSuperUpdateItem != NULL) SortItemsUnder(fSuperUpdateItem, true, SortPackageItems); if (fSuperInstallItem != NULL) SortItemsUnder(fSuperInstallItem, true, SortPackageItems); if (fSuperUninstallItem != NULL) SortItemsUnder(fSuperUninstallItem, true, SortPackageItems); } float PackageListView::ItemHeight() { if (fSuperUpdateItem != NULL) return fSuperUpdateItem->GetPackageItemHeight(); if (fSuperInstallItem != NULL) return fSuperInstallItem->GetPackageItemHeight(); if (fSuperUninstallItem != NULL) return fSuperUninstallItem->GetPackageItemHeight(); return 0; } void PackageListView::SetMoreDetails(bool showMore) { if (showMore == fShowMoreDetails) return; fShowMoreDetails = showMore; _SetItemHeights(); InvalidateLayout(); ResizeToPreferred(); } BPoint PackageListView::ZoomPoint() { BPoint zoomPoint(0, 0); int32 count = CountItems(); for (int32 i = 0; i < count; i++) { BListItem* item = ItemAt(i); float itemWidth = 0; if (item->OutlineLevel() == 0) { SuperItem* sItem = dynamic_cast(item); itemWidth = sItem->ZoomWidth(this); } else { PackageItem* pItem = dynamic_cast(item); itemWidth = fShowMoreDetails ? pItem->MoreDetailsWidth() : pItem->LessDetailsWidth(); } if (itemWidth > zoomPoint.x) zoomPoint.x = itemWidth; } if (count > 0) zoomPoint.y = ItemFrame(count - 1).bottom; return zoomPoint; } void PackageListView::_SetItemHeights() { int32 itemCount = 0; float itemHeight = 0; BListItem* item = NULL; if (fSuperUpdateItem != NULL) { fSuperUpdateItem->SetDetailLevel(fShowMoreDetails); itemHeight = fSuperUpdateItem->GetPackageItemHeight(); itemCount = CountItemsUnder(fSuperUpdateItem, true); for (int32 i = 0; i < itemCount; i++) { item = ItemUnderAt(fSuperUpdateItem, true, i); item->SetHeight(itemHeight); } } if (fSuperInstallItem != NULL) { fSuperInstallItem->SetDetailLevel(fShowMoreDetails); itemHeight = fSuperInstallItem->GetPackageItemHeight(); itemCount = CountItemsUnder(fSuperInstallItem, true); for (int32 i = 0; i < itemCount; i++) { item = ItemUnderAt(fSuperInstallItem, true, i); item->SetHeight(itemHeight); } } if (fSuperUninstallItem != NULL) { fSuperUninstallItem->SetDetailLevel(fShowMoreDetails); itemHeight = fSuperUninstallItem->GetPackageItemHeight(); itemCount = CountItemsUnder(fSuperUninstallItem, true); for (int32 i = 0; i < itemCount; i++) { item = ItemUnderAt(fSuperUninstallItem, true, i); item->SetHeight(itemHeight); } } }