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 SetViewColor(245, 245, 245); 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->SetHighColor(0, 0, 0, 255); 350 351 SetViewColor(B_TRANSPARENT_COLOR); 352 SetLowColor(245, 245, 245); 353 SetHighColor(tint_color(LowColor(), B_DARKEN_1_TINT)); 354 } 355 356 357 void 358 DownloadProgressView::Draw(BRect updateRect) 359 { 360 BRect bounds(Bounds()); 361 bounds.bottom--; 362 FillRect(bounds, B_SOLID_LOW); 363 bounds.bottom++; 364 StrokeLine(bounds.LeftBottom(), bounds.RightBottom()); 365 } 366 367 368 void 369 DownloadProgressView::MessageReceived(BMessage* message) 370 { 371 switch (message->what) { 372 case B_DOWNLOAD_STARTED: 373 { 374 BString path; 375 if (message->FindString("path", &path) != B_OK) 376 break; 377 fPath.SetTo(path); 378 BEntry entry(fPath.Path()); 379 fIconView->SetTo(entry); 380 fStatusBar->Reset(fPath.Leaf()); 381 _StartNodeMonitor(entry); 382 383 // Immediately switch to speed display whenever a new download 384 // starts. 385 sShowSpeed = true; 386 sLastEstimatedFinishSpeedToggleTime 387 = fProcessStartTime = fLastSpeedReferenceTime 388 = fEstimatedFinishReferenceTime = system_time(); 389 break; 390 } 391 case B_DOWNLOAD_PROGRESS: 392 { 393 int64 currentSize; 394 int64 expectedSize; 395 if (message->FindInt64("current size", ¤tSize) == B_OK 396 && message->FindInt64("expected size", &expectedSize) == B_OK) { 397 _UpdateStatus(currentSize, expectedSize); 398 } 399 break; 400 } 401 case B_DOWNLOAD_REMOVED: 402 // TODO: This is a bit asymetric. The removed notification 403 // arrives here, but it would be nicer if it arrived 404 // at the window... 405 Window()->PostMessage(message); 406 break; 407 case OPEN_DOWNLOAD: 408 { 409 // TODO: In case of executable files, ask the user first! 410 entry_ref ref; 411 status_t status = get_ref_for_path(fPath.Path(), &ref); 412 if (status == B_OK) 413 status = be_roster->Launch(&ref); 414 if (status != B_OK && status != B_ALREADY_RUNNING) { 415 BAlert* alert = new BAlert(B_TRANSLATE("Open download error"), 416 B_TRANSLATE("The download could not be opened."), 417 B_TRANSLATE("OK")); 418 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 419 alert->Go(NULL); 420 } 421 break; 422 } 423 case RESTART_DOWNLOAD: 424 { 425 // We can't create a download without a full web context (mainly 426 // because it needs to access the cookie jar), and when we get here 427 // the original context is long gone (possibly the browser was 428 // restarted). So we create a new window to restart the download 429 // in a fresh context. 430 // FIXME this has of course the huge downside of leaving the new 431 // window open with a blank page. I can't think of a better 432 // solution right now... 433 BMessage* request = new BMessage(NEW_WINDOW); 434 request->AddString("url", fURL); 435 be_app->PostMessage(request); 436 break; 437 } 438 439 case CANCEL_DOWNLOAD: 440 CancelDownload(); 441 break; 442 443 case REMOVE_DOWNLOAD: 444 { 445 Window()->PostMessage(SAVE_SETTINGS); 446 RemoveSelf(); 447 delete this; 448 // TOAST! 449 return; 450 } 451 case B_NODE_MONITOR: 452 { 453 int32 opCode; 454 if (message->FindInt32("opcode", &opCode) != B_OK) 455 break; 456 switch (opCode) { 457 case B_ENTRY_REMOVED: 458 fIconView->SetIconDimmed(true); 459 CancelDownload(); 460 break; 461 case B_ENTRY_MOVED: 462 { 463 // Follow the entry to the new location 464 dev_t device; 465 ino_t directory; 466 const char* name; 467 if (message->FindInt32("device", 468 reinterpret_cast<int32*>(&device)) != B_OK 469 || message->FindInt64("to directory", 470 reinterpret_cast<int64*>(&directory)) != B_OK 471 || message->FindString("name", &name) != B_OK 472 || strlen(name) == 0) { 473 break; 474 } 475 // Construct the BEntry and update fPath 476 entry_ref ref(device, directory, name); 477 BEntry entry(&ref); 478 if (entry.GetPath(&fPath) != B_OK) 479 break; 480 481 // Find out if the directory is the Trash for this 482 // volume 483 char trashPath[B_PATH_NAME_LENGTH]; 484 if (find_directory(B_TRASH_DIRECTORY, device, false, 485 trashPath, B_PATH_NAME_LENGTH) == B_OK) { 486 BPath trashDirectory(trashPath); 487 BPath parentDirectory; 488 fPath.GetParent(&parentDirectory); 489 if (parentDirectory == trashDirectory) { 490 // The entry was moved into the Trash. 491 // If the download is still in progress, 492 // cancel it. 493 fIconView->SetIconDimmed(true); 494 CancelDownload(); 495 break; 496 } else if (fIconView->IsIconDimmed()) { 497 // Maybe it was moved out of the trash. 498 fIconView->SetIconDimmed(false); 499 } 500 } 501 502 // Inform download of the new path 503 if (fDownload) 504 fDownload->HasMovedTo(fPath); 505 506 float value = fStatusBar->CurrentValue(); 507 fStatusBar->Reset(name); 508 fStatusBar->SetTo(value); 509 Window()->PostMessage(SAVE_SETTINGS); 510 break; 511 } 512 case B_ATTR_CHANGED: 513 { 514 BEntry entry(fPath.Path()); 515 fIconView->SetIconDimmed(false); 516 fIconView->SetTo(entry); 517 break; 518 } 519 } 520 break; 521 } 522 523 // Context menu messages 524 case COPY_URL_TO_CLIPBOARD: 525 if (be_clipboard->Lock()) { 526 BMessage* data = be_clipboard->Data(); 527 if (data != NULL) { 528 be_clipboard->Clear(); 529 data->AddData("text/plain", B_MIME_TYPE, fURL.String(), 530 fURL.Length()); 531 } 532 be_clipboard->Commit(); 533 be_clipboard->Unlock(); 534 } 535 break; 536 case OPEN_CONTAINING_FOLDER: 537 if (fPath.InitCheck() == B_OK) { 538 BEntry selected(fPath.Path()); 539 if (!selected.Exists()) 540 break; 541 542 BPath containingFolder; 543 if (fPath.GetParent(&containingFolder) != B_OK) 544 break; 545 entry_ref ref; 546 if (get_ref_for_path(containingFolder.Path(), &ref) != B_OK) 547 break; 548 549 // Ask Tracker to open the containing folder and select the 550 // file inside it. 551 BMessenger trackerMessenger("application/x-vnd.Be-TRAK"); 552 553 if (trackerMessenger.IsValid()) { 554 BMessage selectionCommand(B_REFS_RECEIVED); 555 selectionCommand.AddRef("refs", &ref); 556 557 node_ref selectedRef; 558 if (selected.GetNodeRef(&selectedRef) == B_OK) { 559 selectionCommand.AddData("nodeRefToSelect", B_RAW_TYPE, 560 (void*)&selectedRef, sizeof(node_ref)); 561 } 562 563 trackerMessenger.SendMessage(&selectionCommand); 564 } 565 } 566 break; 567 568 default: 569 BGroupView::MessageReceived(message); 570 } 571 } 572 573 574 void 575 DownloadProgressView::ShowContextMenu(BPoint screenWhere) 576 { 577 screenWhere += BPoint(2, 2); 578 579 BPopUpMenu* contextMenu = new BPopUpMenu("download context"); 580 BMenuItem* copyURL = new BMenuItem(B_TRANSLATE("Copy URL to clipboard"), 581 new BMessage(COPY_URL_TO_CLIPBOARD)); 582 copyURL->SetEnabled(fURL.Length() > 0); 583 contextMenu->AddItem(copyURL); 584 BMenuItem* openFolder = new BMenuItem(B_TRANSLATE("Open containing folder"), 585 new BMessage(OPEN_CONTAINING_FOLDER)); 586 contextMenu->AddItem(openFolder); 587 588 contextMenu->SetTargetForItems(this); 589 contextMenu->Go(screenWhere, true, true, true); 590 } 591 592 593 BWebDownload* 594 DownloadProgressView::Download() const 595 { 596 return fDownload; 597 } 598 599 600 const BString& 601 DownloadProgressView::URL() const 602 { 603 return fURL; 604 } 605 606 607 bool 608 DownloadProgressView::IsMissing() const 609 { 610 return fIconView->IsIconDimmed(); 611 } 612 613 614 bool 615 DownloadProgressView::IsFinished() const 616 { 617 return !fDownload && fStatusBar->CurrentValue() == 100; 618 } 619 620 621 void 622 DownloadProgressView::DownloadFinished() 623 { 624 fDownload = NULL; 625 if (fExpectedSize == -1) { 626 fStatusBar->SetTo(100.0); 627 fExpectedSize = fCurrentSize; 628 } 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 fStatusBar->SetBarColor(ui_color(B_SUCCESS_COLOR)); 635 636 BNotification success(B_INFORMATION_NOTIFICATION); 637 success.SetGroup(B_TRANSLATE("WebPositive")); 638 success.SetTitle(B_TRANSLATE("Download finished")); 639 success.SetContent(fPath.Leaf()); 640 BEntry entry(fPath.Path()); 641 entry_ref ref; 642 entry.GetRef(&ref); 643 success.SetOnClickFile(&ref); 644 success.SetIcon(fIconView->Bitmap()); 645 success.Send(); 646 647 } 648 649 650 void 651 DownloadProgressView::CancelDownload() 652 { 653 // Show the cancel notification, and set the progress bar red, only if the 654 // download was still running. In cases where the file is deleted after 655 // the download was finished, we don't want these things to happen. 656 if (fDownload) { 657 // Also cancel the download 658 fDownload->Cancel(); 659 BNotification success(B_ERROR_NOTIFICATION); 660 success.SetGroup(B_TRANSLATE("WebPositive")); 661 success.SetTitle(B_TRANSLATE("Download aborted")); 662 success.SetContent(fPath.Leaf()); 663 // Don't make a click on the notification open the file: it is not 664 // complete 665 success.SetIcon(fIconView->Bitmap()); 666 success.Send(); 667 668 fStatusBar->SetBarColor(ui_color(B_FAILURE_COLOR)); 669 } 670 671 fDownload = NULL; 672 fTopButton->SetLabel(B_TRANSLATE("Restart")); 673 fTopButton->SetMessage(new BMessage(RESTART_DOWNLOAD)); 674 fTopButton->SetEnabled(true); 675 fBottomButton->SetLabel(B_TRANSLATE("Remove")); 676 fBottomButton->SetMessage(new BMessage(REMOVE_DOWNLOAD)); 677 fBottomButton->SetEnabled(true); 678 fInfoView->SetText(""); 679 680 fPath.Unset(); 681 } 682 683 684 /*static*/ void 685 DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse() 686 { 687 bigtime_t now = system_time(); 688 if (sShowSpeed 689 && sLastEstimatedFinishSpeedToggleTime + kShowSpeedInterval 690 <= now) { 691 sShowSpeed = false; 692 sLastEstimatedFinishSpeedToggleTime = now; 693 } else if (!sShowSpeed 694 && sLastEstimatedFinishSpeedToggleTime 695 + kShowEstimatedFinishInterval <= now) { 696 sShowSpeed = true; 697 sLastEstimatedFinishSpeedToggleTime = now; 698 } 699 } 700 701 702 // #pragma mark - private 703 704 705 void 706 DownloadProgressView::_UpdateStatus(off_t currentSize, off_t expectedSize) 707 { 708 fCurrentSize = currentSize; 709 fExpectedSize = expectedSize; 710 711 fStatusBar->SetTo(100.0 * currentSize / expectedSize); 712 713 bigtime_t currentTime = system_time(); 714 if ((currentTime - fLastUpdateTime) > kMaxUpdateInterval) { 715 fLastUpdateTime = currentTime; 716 717 if (currentTime >= fLastSpeedReferenceTime + kSpeedReferenceInterval) { 718 // update current speed every kSpeedReferenceInterval 719 fCurrentBytesPerSecondSlot 720 = (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots; 721 fBytesPerSecondSlot[fCurrentBytesPerSecondSlot] 722 = (double)(currentSize - fLastSpeedReferenceSize) 723 * 1000000LL / (currentTime - fLastSpeedReferenceTime); 724 fLastSpeedReferenceSize = currentSize; 725 fLastSpeedReferenceTime = currentTime; 726 fBytesPerSecond = 0.0; 727 size_t count = 0; 728 for (size_t i = 0; i < kBytesPerSecondSlots; i++) { 729 if (fBytesPerSecondSlot[i] != 0.0) { 730 fBytesPerSecond += fBytesPerSecondSlot[i]; 731 count++; 732 } 733 } 734 if (count > 0) 735 fBytesPerSecond /= count; 736 } 737 _UpdateStatusText(); 738 } 739 } 740 741 742 void 743 DownloadProgressView::_UpdateStatusText() 744 { 745 fInfoView->SetText(""); 746 BString buffer; 747 if (sShowSpeed && fBytesPerSecond != 0.0) { 748 // Draw speed info 749 char sizeBuffer[128]; 750 buffer = "("; 751 // Get strings for current and expected size and remove the unit 752 // from the current size string if it's the same as the expected 753 // size unit. 754 BString currentSize = string_for_size((double)fCurrentSize, sizeBuffer, 755 sizeof(sizeBuffer)); 756 BString expectedSize = string_for_size((double)fExpectedSize, sizeBuffer, 757 sizeof(sizeBuffer)); 758 int currentSizeUnitPos = currentSize.FindLast(' '); 759 int expectedSizeUnitPos = expectedSize.FindLast(' '); 760 if (currentSizeUnitPos >= 0 && expectedSizeUnitPos >= 0 761 && strcmp(currentSize.String() + currentSizeUnitPos, 762 expectedSize.String() + expectedSizeUnitPos) == 0) { 763 currentSize.Truncate(currentSizeUnitPos); 764 } 765 buffer << currentSize; 766 buffer << " "; 767 buffer << B_TRANSLATE_COMMENT("of", "...as in '12kB of 256kB'"); 768 buffer << " "; 769 buffer << expectedSize; 770 buffer << ", "; 771 buffer << string_for_size(fBytesPerSecond, sizeBuffer, 772 sizeof(sizeBuffer)); 773 buffer << B_TRANSLATE_COMMENT("/s)", "...as in 'per second'"); 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