1 /* 2 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de> 3 * 4 * All rights reserved. Distributed under the terms of the MIT License. 5 */ 6 7 #include "DownloadProgressView.h" 8 9 #include <stdio.h> 10 11 #include <Alert.h> 12 #include <Application.h> 13 #include <Bitmap.h> 14 #include <Button.h> 15 #include <Catalog.h> 16 #include <Clipboard.h> 17 #include <Directory.h> 18 #include <DateTimeFormat.h> 19 #include <DurationFormat.h> 20 #include <Entry.h> 21 #include <FindDirectory.h> 22 #include <GroupLayoutBuilder.h> 23 #include <Locale.h> 24 #include <MenuItem.h> 25 #include <NodeInfo.h> 26 #include <NodeMonitor.h> 27 #include <Notification.h> 28 #include <PopUpMenu.h> 29 #include <Roster.h> 30 #include <SpaceLayoutItem.h> 31 #include <StatusBar.h> 32 #include <StringView.h> 33 #include <TimeFormat.h> 34 35 #include "BrowserWindow.h" 36 #include "WebDownload.h" 37 #include "WebPage.h" 38 #include "StringForSize.h" 39 40 41 #undef B_TRANSLATION_CONTEXT 42 #define B_TRANSLATION_CONTEXT "Download Window" 43 44 enum { 45 OPEN_DOWNLOAD = 'opdn', 46 RESTART_DOWNLOAD = 'rsdn', 47 CANCEL_DOWNLOAD = 'cndn', 48 REMOVE_DOWNLOAD = 'rmdn', 49 COPY_URL_TO_CLIPBOARD = 'curl', 50 OPEN_CONTAINING_FOLDER = 'opfd', 51 }; 52 53 const bigtime_t kMaxUpdateInterval = 100000LL; 54 const bigtime_t kSpeedReferenceInterval = 500000LL; 55 const bigtime_t kShowSpeedInterval = 8000000LL; 56 const bigtime_t kShowEstimatedFinishInterval = 4000000LL; 57 58 bigtime_t DownloadProgressView::sLastEstimatedFinishSpeedToggleTime = -1; 59 bool DownloadProgressView::sShowSpeed = true; 60 static const time_t kSecondsPerDay = 24 * 60 * 60; 61 static const time_t kSecondsPerHour = 60 * 60; 62 63 64 class IconView : public BView { 65 public: 66 IconView(const BEntry& entry) 67 : 68 BView("Download icon", B_WILL_DRAW), 69 fIconBitmap(BRect(0, 0, 31, 31), 0, B_RGBA32), 70 fDimmedIcon(false) 71 { 72 SetDrawingMode(B_OP_OVER); 73 SetTo(entry); 74 } 75 76 IconView() 77 : 78 BView("Download icon", B_WILL_DRAW), 79 fIconBitmap(BRect(0, 0, 31, 31), 0, B_RGBA32), 80 fDimmedIcon(false) 81 { 82 SetDrawingMode(B_OP_OVER); 83 memset(fIconBitmap.Bits(), 0, fIconBitmap.BitsLength()); 84 } 85 86 IconView(BMessage* archive) 87 : 88 BView("Download icon", B_WILL_DRAW), 89 fIconBitmap(archive), 90 fDimmedIcon(true) 91 { 92 SetDrawingMode(B_OP_OVER); 93 } 94 95 void SetTo(const BEntry& entry) 96 { 97 BNode node(&entry); 98 BNodeInfo info(&node); 99 info.GetTrackerIcon(&fIconBitmap, B_LARGE_ICON); 100 Invalidate(); 101 } 102 103 void SetIconDimmed(bool iconDimmed) 104 { 105 if (fDimmedIcon != iconDimmed) { 106 fDimmedIcon = iconDimmed; 107 Invalidate(); 108 } 109 } 110 111 bool IsIconDimmed() const 112 { 113 return fDimmedIcon; 114 } 115 116 status_t SaveSettings(BMessage* archive) 117 { 118 return fIconBitmap.Archive(archive); 119 } 120 121 virtual void AttachedToWindow() 122 { 123 AdoptParentColors(); 124 } 125 126 virtual void Draw(BRect updateRect) 127 { 128 if (fDimmedIcon) { 129 SetDrawingMode(B_OP_ALPHA); 130 SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 131 SetHighColor(0, 0, 0, 100); 132 } 133 DrawBitmapAsync(&fIconBitmap); 134 } 135 136 virtual BSize MinSize() 137 { 138 return BSize(fIconBitmap.Bounds().Width(), 139 fIconBitmap.Bounds().Height()); 140 } 141 142 virtual BSize PreferredSize() 143 { 144 return MinSize(); 145 } 146 147 virtual BSize MaxSize() 148 { 149 return MinSize(); 150 } 151 152 BBitmap* Bitmap() 153 { 154 return &fIconBitmap; 155 } 156 157 private: 158 BBitmap fIconBitmap; 159 bool fDimmedIcon; 160 }; 161 162 163 class SmallButton : public BButton { 164 public: 165 SmallButton(const char* label, BMessage* message = NULL) 166 : 167 BButton(label, message) 168 { 169 BFont font; 170 GetFont(&font); 171 float size = ceilf(font.Size() * 0.8); 172 font.SetSize(max_c(8, size)); 173 SetFont(&font, B_FONT_SIZE); 174 } 175 }; 176 177 178 // #pragma mark - DownloadProgressView 179 180 181 DownloadProgressView::DownloadProgressView(BWebDownload* download) 182 : 183 BGroupView(B_HORIZONTAL, 8), 184 fDownload(download), 185 fURL(download->URL()), 186 fPath(download->Path()) 187 { 188 } 189 190 191 DownloadProgressView::DownloadProgressView(const BMessage* archive) 192 : 193 BGroupView(B_HORIZONTAL, 8), 194 fDownload(NULL), 195 fURL(), 196 fPath() 197 { 198 const char* string; 199 if (archive->FindString("path", &string) == B_OK) 200 fPath.SetTo(string); 201 if (archive->FindString("url", &string) == B_OK) 202 fURL = string; 203 } 204 205 206 bool 207 DownloadProgressView::Init(BMessage* archive) 208 { 209 fCurrentSize = 0; 210 fExpectedSize = 0; 211 fLastUpdateTime = 0; 212 fBytesPerSecond = 0.0; 213 for (size_t i = 0; i < kBytesPerSecondSlots; i++) 214 fBytesPerSecondSlot[i] = 0.0; 215 fCurrentBytesPerSecondSlot = 0; 216 fLastSpeedReferenceSize = 0; 217 fEstimatedFinishReferenceSize = 0; 218 219 fProcessStartTime = fLastSpeedReferenceTime 220 = fEstimatedFinishReferenceTime = system_time(); 221 222 SetViewUIColor(B_LIST_BACKGROUND_COLOR); 223 SetFlags(Flags() | B_FULL_UPDATE_ON_RESIZE | B_WILL_DRAW); 224 225 if (archive) { 226 fStatusBar = new BStatusBar("download progress", fPath.Leaf()); 227 float value; 228 if (archive->FindFloat("value", &value) == B_OK) 229 fStatusBar->SetTo(value); 230 } else 231 fStatusBar = new BStatusBar("download progress", "Download"); 232 fStatusBar->SetMaxValue(100); 233 fStatusBar->SetBarHeight(12); 234 235 // fPath is only valid when constructed from archive (fDownload == NULL) 236 BEntry entry(fPath.Path()); 237 238 if (archive) { 239 if (!entry.Exists()) 240 fIconView = new IconView(archive); 241 else 242 fIconView = new IconView(entry); 243 } else 244 fIconView = new IconView(); 245 246 if (!fDownload && (fStatusBar->CurrentValue() < 100 || !entry.Exists())) { 247 fTopButton = new SmallButton(B_TRANSLATE("Restart"), 248 new BMessage(RESTART_DOWNLOAD)); 249 } else { 250 fTopButton = new SmallButton(B_TRANSLATE("Open"), 251 new BMessage(OPEN_DOWNLOAD)); 252 fTopButton->SetEnabled(fDownload == NULL); 253 } 254 if (fDownload) { 255 fBottomButton = new SmallButton(B_TRANSLATE("Cancel"), 256 new BMessage(CANCEL_DOWNLOAD)); 257 } else { 258 fBottomButton = new SmallButton(B_TRANSLATE("Remove"), 259 new BMessage(REMOVE_DOWNLOAD)); 260 fBottomButton->SetEnabled(fDownload == NULL); 261 } 262 263 fInfoView = new BStringView("info view", ""); 264 fInfoView->SetViewColor(ViewColor()); 265 266 BSize topButtonSize = fTopButton->PreferredSize(); 267 BSize bottomButtonSize = fBottomButton->PreferredSize(); 268 if (bottomButtonSize.width < topButtonSize.width) 269 fBottomButton->SetExplicitMaxSize(topButtonSize); 270 else 271 fTopButton->SetExplicitMaxSize(bottomButtonSize); 272 273 BGroupLayout* layout = GroupLayout(); 274 layout->SetInsets(8, 5, 5, 6); 275 layout->AddView(fIconView); 276 BView* verticalGroup = BGroupLayoutBuilder(B_VERTICAL, 3) 277 .Add(fStatusBar) 278 .Add(fInfoView) 279 .TopView() 280 ; 281 verticalGroup->SetViewColor(ViewColor()); 282 layout->AddView(verticalGroup); 283 284 verticalGroup = BGroupLayoutBuilder(B_VERTICAL, 3) 285 .Add(fTopButton) 286 .Add(fBottomButton) 287 .TopView() 288 ; 289 verticalGroup->SetViewColor(ViewColor()); 290 layout->AddView(verticalGroup); 291 292 BFont font; 293 fInfoView->GetFont(&font); 294 float fontSize = font.Size() * 0.8f; 295 font.SetSize(max_c(8.0f, fontSize)); 296 fInfoView->SetFont(&font, B_FONT_SIZE); 297 fInfoView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 298 299 return true; 300 } 301 302 303 status_t 304 DownloadProgressView::SaveSettings(BMessage* archive) 305 { 306 if (!archive) 307 return B_BAD_VALUE; 308 status_t ret = archive->AddString("path", fPath.Path()); 309 if (ret == B_OK) 310 ret = archive->AddString("url", fURL.String()); 311 if (ret == B_OK) 312 ret = archive->AddFloat("value", fStatusBar->CurrentValue()); 313 if (ret == B_OK) 314 ret = fIconView->SaveSettings(archive); 315 return ret; 316 } 317 318 319 void 320 DownloadProgressView::AttachedToWindow() 321 { 322 if (fDownload) { 323 fDownload->SetProgressListener(BMessenger(this)); 324 // Will start node monitor upon receiving the B_DOWNLOAD_STARTED 325 // message. 326 } else { 327 BEntry entry(fPath.Path()); 328 if (entry.Exists()) 329 _StartNodeMonitor(entry); 330 } 331 332 fTopButton->SetTarget(this); 333 fBottomButton->SetTarget(this); 334 } 335 336 337 void 338 DownloadProgressView::DetachedFromWindow() 339 { 340 _StopNodeMonitor(); 341 } 342 343 344 void 345 DownloadProgressView::AllAttached() 346 { 347 fStatusBar->SetLowColor(ViewColor()); 348 fInfoView->SetLowColor(ViewColor()); 349 fInfoView->SetHighUIColor(B_LIST_ITEM_TEXT_COLOR); 350 351 SetViewColor(B_TRANSPARENT_COLOR); 352 SetLowUIColor(B_LIST_BACKGROUND_COLOR); 353 if (LowColor().IsLight()) 354 SetHighColor(tint_color(LowColor(), B_DARKEN_1_TINT)); 355 else 356 SetHighColor(tint_color(LowColor(), B_LIGHTEN_1_TINT)); 357 } 358 359 360 void 361 DownloadProgressView::Draw(BRect updateRect) 362 { 363 BRect bounds(Bounds()); 364 bounds.bottom--; 365 FillRect(bounds, B_SOLID_LOW); 366 bounds.bottom++; 367 StrokeLine(bounds.LeftBottom(), bounds.RightBottom()); 368 } 369 370 371 void 372 DownloadProgressView::MessageReceived(BMessage* message) 373 { 374 switch (message->what) { 375 case B_DOWNLOAD_STARTED: 376 { 377 BString path; 378 if (message->FindString("path", &path) != B_OK) 379 break; 380 fPath.SetTo(path); 381 BEntry entry(fPath.Path()); 382 fIconView->SetTo(entry); 383 fStatusBar->Reset(fPath.Leaf()); 384 _StartNodeMonitor(entry); 385 386 // Immediately switch to speed display whenever a new download 387 // starts. 388 sShowSpeed = true; 389 sLastEstimatedFinishSpeedToggleTime 390 = fProcessStartTime = fLastSpeedReferenceTime 391 = fEstimatedFinishReferenceTime = system_time(); 392 break; 393 } 394 case B_DOWNLOAD_PROGRESS: 395 { 396 int64 currentSize; 397 int64 expectedSize; 398 if (message->FindInt64("current size", ¤tSize) == B_OK 399 && message->FindInt64("expected size", &expectedSize) == B_OK) { 400 _UpdateStatus(currentSize, expectedSize); 401 } 402 break; 403 } 404 case B_DOWNLOAD_REMOVED: 405 // TODO: This is a bit asymetric. The removed notification 406 // arrives here, but it would be nicer if it arrived 407 // at the window... 408 Window()->PostMessage(message); 409 break; 410 case OPEN_DOWNLOAD: 411 { 412 // TODO: In case of executable files, ask the user first! 413 entry_ref ref; 414 status_t status = get_ref_for_path(fPath.Path(), &ref); 415 if (status == B_OK) 416 status = be_roster->Launch(&ref); 417 if (status != B_OK && status != B_ALREADY_RUNNING) { 418 BAlert* alert = new BAlert(B_TRANSLATE("Open download error"), 419 B_TRANSLATE("The download could not be opened."), 420 B_TRANSLATE("OK")); 421 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 422 alert->Go(NULL); 423 } 424 break; 425 } 426 case RESTART_DOWNLOAD: 427 { 428 // We can't create a download without a full web context (mainly 429 // because it needs to access the cookie jar), and when we get here 430 // the original context is long gone (possibly the browser was 431 // restarted). So we create a new window to restart the download 432 // in a fresh context. 433 // FIXME this has of course the huge downside of leaving the new 434 // window open with a blank page. I can't think of a better 435 // solution right now... 436 BMessage* request = new BMessage(NEW_WINDOW); 437 request->AddString("url", fURL); 438 be_app->PostMessage(request); 439 break; 440 } 441 442 case CANCEL_DOWNLOAD: 443 CancelDownload(); 444 break; 445 446 case REMOVE_DOWNLOAD: 447 { 448 Window()->PostMessage(SAVE_SETTINGS); 449 RemoveSelf(); 450 delete this; 451 // TOAST! 452 return; 453 } 454 case B_NODE_MONITOR: 455 { 456 int32 opCode; 457 if (message->FindInt32("opcode", &opCode) != B_OK) 458 break; 459 switch (opCode) { 460 case B_ENTRY_REMOVED: 461 fIconView->SetIconDimmed(true); 462 CancelDownload(); 463 break; 464 case B_ENTRY_MOVED: 465 { 466 // Follow the entry to the new location 467 dev_t device; 468 ino_t directory; 469 const char* name; 470 if (message->FindInt32("device", 471 reinterpret_cast<int32*>(&device)) != B_OK 472 || message->FindInt64("to directory", 473 reinterpret_cast<int64*>(&directory)) != B_OK 474 || message->FindString("name", &name) != B_OK 475 || strlen(name) == 0) { 476 break; 477 } 478 // Construct the BEntry and update fPath 479 entry_ref ref(device, directory, name); 480 BEntry entry(&ref); 481 if (entry.GetPath(&fPath) != B_OK) 482 break; 483 484 // Find out if the directory is the Trash for this 485 // volume 486 char trashPath[B_PATH_NAME_LENGTH]; 487 if (find_directory(B_TRASH_DIRECTORY, device, false, 488 trashPath, B_PATH_NAME_LENGTH) == B_OK) { 489 BPath trashDirectory(trashPath); 490 BPath parentDirectory; 491 fPath.GetParent(&parentDirectory); 492 if (parentDirectory == trashDirectory) { 493 // The entry was moved into the Trash. 494 // If the download is still in progress, 495 // cancel it. 496 fIconView->SetIconDimmed(true); 497 CancelDownload(); 498 break; 499 } else if (fIconView->IsIconDimmed()) { 500 // Maybe it was moved out of the trash. 501 fIconView->SetIconDimmed(false); 502 } 503 } 504 505 // Inform download of the new path 506 if (fDownload) 507 fDownload->HasMovedTo(fPath); 508 509 float value = fStatusBar->CurrentValue(); 510 fStatusBar->Reset(name); 511 fStatusBar->SetTo(value); 512 Window()->PostMessage(SAVE_SETTINGS); 513 break; 514 } 515 case B_ATTR_CHANGED: 516 { 517 BEntry entry(fPath.Path()); 518 fIconView->SetIconDimmed(false); 519 fIconView->SetTo(entry); 520 break; 521 } 522 } 523 break; 524 } 525 526 // Context menu messages 527 case COPY_URL_TO_CLIPBOARD: 528 if (be_clipboard->Lock()) { 529 BMessage* data = be_clipboard->Data(); 530 if (data != NULL) { 531 be_clipboard->Clear(); 532 data->AddData("text/plain", B_MIME_TYPE, fURL.String(), 533 fURL.Length()); 534 } 535 be_clipboard->Commit(); 536 be_clipboard->Unlock(); 537 } 538 break; 539 case OPEN_CONTAINING_FOLDER: 540 if (fPath.InitCheck() == B_OK) { 541 BEntry selected(fPath.Path()); 542 if (!selected.Exists()) 543 break; 544 545 BPath containingFolder; 546 if (fPath.GetParent(&containingFolder) != B_OK) 547 break; 548 entry_ref ref; 549 if (get_ref_for_path(containingFolder.Path(), &ref) != B_OK) 550 break; 551 552 // Ask Tracker to open the containing folder and select the 553 // file inside it. 554 BMessenger trackerMessenger("application/x-vnd.Be-TRAK"); 555 556 if (trackerMessenger.IsValid()) { 557 BMessage selectionCommand(B_REFS_RECEIVED); 558 selectionCommand.AddRef("refs", &ref); 559 560 node_ref selectedRef; 561 if (selected.GetNodeRef(&selectedRef) == B_OK) { 562 selectionCommand.AddData("nodeRefToSelect", B_RAW_TYPE, 563 (void*)&selectedRef, sizeof(node_ref)); 564 } 565 566 trackerMessenger.SendMessage(&selectionCommand); 567 } 568 } 569 break; 570 571 default: 572 BGroupView::MessageReceived(message); 573 } 574 } 575 576 577 void 578 DownloadProgressView::ShowContextMenu(BPoint screenWhere) 579 { 580 screenWhere += BPoint(2, 2); 581 582 BPopUpMenu* contextMenu = new BPopUpMenu("download context"); 583 BMenuItem* copyURL = new BMenuItem(B_TRANSLATE("Copy URL to clipboard"), 584 new BMessage(COPY_URL_TO_CLIPBOARD)); 585 copyURL->SetEnabled(fURL.Length() > 0); 586 contextMenu->AddItem(copyURL); 587 BMenuItem* openFolder = new BMenuItem(B_TRANSLATE("Open containing folder"), 588 new BMessage(OPEN_CONTAINING_FOLDER)); 589 contextMenu->AddItem(openFolder); 590 591 contextMenu->SetTargetForItems(this); 592 contextMenu->Go(screenWhere, true, true, true); 593 } 594 595 596 BWebDownload* 597 DownloadProgressView::Download() const 598 { 599 return fDownload; 600 } 601 602 603 const BString& 604 DownloadProgressView::URL() const 605 { 606 return fURL; 607 } 608 609 610 bool 611 DownloadProgressView::IsMissing() const 612 { 613 return fIconView->IsIconDimmed(); 614 } 615 616 617 bool 618 DownloadProgressView::IsFinished() const 619 { 620 return !fDownload && fStatusBar->CurrentValue() == 100; 621 } 622 623 624 void 625 DownloadProgressView::DownloadFinished() 626 { 627 fDownload = NULL; 628 if (fExpectedSize == -1) { 629 fStatusBar->SetTo(100.0); 630 fExpectedSize = fCurrentSize; 631 } 632 fTopButton->SetEnabled(true); 633 fBottomButton->SetLabel(B_TRANSLATE("Remove")); 634 fBottomButton->SetMessage(new BMessage(REMOVE_DOWNLOAD)); 635 fBottomButton->SetEnabled(true); 636 fInfoView->SetText(""); 637 fStatusBar->SetBarColor(ui_color(B_SUCCESS_COLOR)); 638 639 BNotification success(B_INFORMATION_NOTIFICATION); 640 success.SetGroup(B_TRANSLATE("WebPositive")); 641 success.SetTitle(B_TRANSLATE("Download finished")); 642 success.SetContent(fPath.Leaf()); 643 BEntry entry(fPath.Path()); 644 entry_ref ref; 645 entry.GetRef(&ref); 646 success.SetOnClickFile(&ref); 647 success.SetIcon(fIconView->Bitmap()); 648 success.Send(); 649 650 } 651 652 653 void 654 DownloadProgressView::CancelDownload() 655 { 656 // Show the cancel notification, and set the progress bar red, only if the 657 // download was still running. In cases where the file is deleted after 658 // the download was finished, we don't want these things to happen. 659 if (fDownload) { 660 // Also cancel the download 661 fDownload->Cancel(); 662 BNotification success(B_ERROR_NOTIFICATION); 663 success.SetGroup(B_TRANSLATE("WebPositive")); 664 success.SetTitle(B_TRANSLATE("Download aborted")); 665 success.SetContent(fPath.Leaf()); 666 // Don't make a click on the notification open the file: it is not 667 // complete 668 success.SetIcon(fIconView->Bitmap()); 669 success.Send(); 670 671 fStatusBar->SetBarColor(ui_color(B_FAILURE_COLOR)); 672 } 673 674 fDownload = NULL; 675 fTopButton->SetLabel(B_TRANSLATE("Restart")); 676 fTopButton->SetMessage(new BMessage(RESTART_DOWNLOAD)); 677 fTopButton->SetEnabled(true); 678 fBottomButton->SetLabel(B_TRANSLATE("Remove")); 679 fBottomButton->SetMessage(new BMessage(REMOVE_DOWNLOAD)); 680 fBottomButton->SetEnabled(true); 681 fInfoView->SetText(""); 682 683 fPath.Unset(); 684 } 685 686 687 /*static*/ void 688 DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse() 689 { 690 bigtime_t now = system_time(); 691 if (sShowSpeed 692 && sLastEstimatedFinishSpeedToggleTime + kShowSpeedInterval 693 <= now) { 694 sShowSpeed = false; 695 sLastEstimatedFinishSpeedToggleTime = now; 696 } else if (!sShowSpeed 697 && sLastEstimatedFinishSpeedToggleTime 698 + kShowEstimatedFinishInterval <= now) { 699 sShowSpeed = true; 700 sLastEstimatedFinishSpeedToggleTime = now; 701 } 702 } 703 704 705 // #pragma mark - private 706 707 708 void 709 DownloadProgressView::_UpdateStatus(off_t currentSize, off_t expectedSize) 710 { 711 fCurrentSize = currentSize; 712 fExpectedSize = expectedSize; 713 714 fStatusBar->SetTo(100.0 * currentSize / expectedSize); 715 716 bigtime_t currentTime = system_time(); 717 if ((currentTime - fLastUpdateTime) > kMaxUpdateInterval) { 718 fLastUpdateTime = currentTime; 719 720 if (currentTime >= fLastSpeedReferenceTime + kSpeedReferenceInterval) { 721 // update current speed every kSpeedReferenceInterval 722 fCurrentBytesPerSecondSlot 723 = (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots; 724 fBytesPerSecondSlot[fCurrentBytesPerSecondSlot] 725 = (double)(currentSize - fLastSpeedReferenceSize) 726 * 1000000LL / (currentTime - fLastSpeedReferenceTime); 727 fLastSpeedReferenceSize = currentSize; 728 fLastSpeedReferenceTime = currentTime; 729 fBytesPerSecond = 0.0; 730 size_t count = 0; 731 for (size_t i = 0; i < kBytesPerSecondSlots; i++) { 732 if (fBytesPerSecondSlot[i] != 0.0) { 733 fBytesPerSecond += fBytesPerSecondSlot[i]; 734 count++; 735 } 736 } 737 if (count > 0) 738 fBytesPerSecond /= count; 739 } 740 _UpdateStatusText(); 741 } 742 } 743 744 745 void 746 DownloadProgressView::_UpdateStatusText() 747 { 748 fInfoView->SetText(""); 749 BString buffer; 750 if (sShowSpeed && fBytesPerSecond != 0.0) { 751 // Draw speed info 752 char sizeBuffer[128]; 753 // Get strings for current and expected size and remove the unit 754 // from the current size string if it's the same as the expected 755 // size unit. 756 BString currentSize = string_for_size((double)fCurrentSize, sizeBuffer, 757 sizeof(sizeBuffer)); 758 BString expectedSize = string_for_size((double)fExpectedSize, sizeBuffer, 759 sizeof(sizeBuffer)); 760 int currentSizeUnitPos = currentSize.FindLast(' '); 761 int expectedSizeUnitPos = expectedSize.FindLast(' '); 762 if (currentSizeUnitPos >= 0 && expectedSizeUnitPos >= 0 763 && strcmp(currentSize.String() + currentSizeUnitPos, 764 expectedSize.String() + expectedSizeUnitPos) == 0) { 765 currentSize.Truncate(currentSizeUnitPos); 766 } 767 768 buffer = B_TRANSLATE("(%currentSize% of %expectedSize%, %rate%/s)"); 769 buffer.ReplaceFirst("%currentSize%", currentSize); 770 buffer.ReplaceFirst("%expectedSize%", expectedSize); 771 buffer.ReplaceFirst("%rate%", string_for_size(fBytesPerSecond, 772 sizeBuffer, sizeof(sizeBuffer))); 773 774 float stringWidth = fInfoView->StringWidth(buffer.String()); 775 if (stringWidth < fInfoView->Bounds().Width()) 776 fInfoView->SetText(buffer.String()); 777 else { 778 // complete string too wide, try with shorter version 779 buffer = string_for_size(fBytesPerSecond, sizeBuffer, 780 sizeof(sizeBuffer)); 781 buffer << B_TRANSLATE_COMMENT("/s)", "...as in 'per second'"); 782 stringWidth = fInfoView->StringWidth(buffer.String()); 783 if (stringWidth < fInfoView->Bounds().Width()) 784 fInfoView->SetText(buffer.String()); 785 } 786 } else if (!sShowSpeed && fCurrentSize < fExpectedSize) { 787 double totalBytesPerSecond = (double)(fCurrentSize 788 - fEstimatedFinishReferenceSize) 789 * 1000000LL / (system_time() - fEstimatedFinishReferenceTime); 790 double secondsRemaining = (fExpectedSize - fCurrentSize) 791 / totalBytesPerSecond; 792 time_t now = (time_t)real_time_clock(); 793 time_t finishTime = (time_t)(now + secondsRemaining); 794 795 BString timeText; 796 if (finishTime - now > kSecondsPerDay) { 797 BDateTimeFormat().Format(timeText, finishTime, 798 B_MEDIUM_DATE_FORMAT, B_MEDIUM_TIME_FORMAT); 799 } else { 800 BTimeFormat().Format(timeText, finishTime, 801 B_MEDIUM_TIME_FORMAT); 802 } 803 804 BString statusString; 805 BDurationFormat formatter; 806 BString finishString; 807 if (finishTime - now > kSecondsPerHour) { 808 statusString.SetTo(B_TRANSLATE("(Finish: %date - Over %duration left)")); 809 formatter.Format(finishString, now * 1000000LL, finishTime * 1000000LL); 810 } else { 811 statusString.SetTo(B_TRANSLATE("(Finish: %date - %duration left)")); 812 formatter.Format(finishString, now * 1000000LL, finishTime * 1000000LL); 813 } 814 815 statusString.ReplaceFirst("%date", timeText); 816 statusString.ReplaceFirst("%duration", finishString); 817 818 float stringWidth = fInfoView->StringWidth(statusString.String()); 819 if (stringWidth < fInfoView->Bounds().Width()) 820 fInfoView->SetText(statusString.String()); 821 else { 822 // complete string too wide, try with shorter version 823 statusString.SetTo(B_TRANSLATE("(Finish: %date)")); 824 statusString.ReplaceFirst("%date", timeText); 825 stringWidth = fInfoView->StringWidth(statusString.String()); 826 if (stringWidth < fInfoView->Bounds().Width()) 827 fInfoView->SetText(statusString.String()); 828 } 829 } 830 } 831 832 833 void 834 DownloadProgressView::_StartNodeMonitor(const BEntry& entry) 835 { 836 node_ref nref; 837 if (entry.GetNodeRef(&nref) == B_OK) 838 watch_node(&nref, B_WATCH_ALL, this); 839 } 840 841 842 void 843 DownloadProgressView::_StopNodeMonitor() 844 { 845 stop_watching(this); 846 } 847 848