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