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