diff --git a/src/apps/webpositive/DownloadProgressView.cpp b/src/apps/webpositive/DownloadProgressView.cpp index bedbc67bad..5284fb2e31 100644 --- a/src/apps/webpositive/DownloadProgressView.cpp +++ b/src/apps/webpositive/DownloadProgressView.cpp @@ -35,15 +35,17 @@ #include #include #include -#include +#include #include #include #include #include #include +#include #include "WebDownload.h" #include "WebPage.h" +#include "StringForSize.h" enum { @@ -53,6 +55,14 @@ enum { REMOVE_DOWNLOAD = 'rmdn', }; +const bigtime_t kMaxUpdateInterval = 100000LL; +const bigtime_t kSpeedReferenceInterval = 500000LL; +const bigtime_t kShowSpeedInterval = 8000000LL; +const bigtime_t kShowEstimatedFinishInterval = 4000000LL; + +bigtime_t DownloadProgressView::sLastEstimatedFinishSpeedToggleTime = -1; +bool DownloadProgressView::sShowSpeed = true; + class IconView : public BView { public: @@ -167,22 +177,20 @@ public: DownloadProgressView::DownloadProgressView(BWebDownload* download) : - BGridView(8, 3), + BGroupView(B_HORIZONTAL, 8), fDownload(download), fURL(download->URL()), - fPath(download->Path()), - fExpectedSize(download->ExpectedSize()) + fPath(download->Path()) { } DownloadProgressView::DownloadProgressView(const BMessage* archive) : - BGridView(8, 3), + BGroupView(B_HORIZONTAL, 8), fDownload(NULL), fURL(), - fPath(), - fExpectedSize(0) + fPath() { const char* string; if (archive->FindString("path", &string) == B_OK) @@ -195,10 +203,22 @@ DownloadProgressView::DownloadProgressView(const BMessage* archive) bool DownloadProgressView::Init(BMessage* archive) { + fCurrentSize = 0; + fExpectedSize = 0; + fLastUpdateTime = 0; + fBytesPerSecond = 0.0; + for (size_t i = 0; i < kBytesPerSecondSlots; i++) + fBytesPerSecondSlot[i] = 0.0; + fCurrentBytesPerSecondSlot = 0; + fLastSpeedReferenceSize = 0; + fEstimatedFinishReferenceSize = 0; + + fProcessStartTime = fLastSpeedReferenceTime = fEstimatedFinishReferenceTime + = system_time(); + SetViewColor(245, 245, 245); SetFlags(Flags() | B_FULL_UPDATE_ON_RESIZE | B_WILL_DRAW); - BGridLayout* layout = GridLayout(); if (archive) { fStatusBar = new BStatusBar("download progress", fPath.Leaf()); float value; @@ -233,11 +253,37 @@ DownloadProgressView::Init(BMessage* archive) fBottomButton->SetEnabled(fDownload == NULL); } + fInfoView = new BStringView("info view", ""); + + BGroupLayout* layout = GroupLayout(); layout->SetInsets(8, 5, 5, 6); - layout->AddView(fIconView, 0, 0, 1, 2); - layout->AddView(fStatusBar, 1, 0, 1, 2); - layout->AddView(fTopButton, 2, 0); - layout->AddView(fBottomButton, 2, 1); + layout->AddView(fIconView); + BView* verticalGroup = BGroupLayoutBuilder(B_VERTICAL, 3) + .Add(fStatusBar) + .Add(fInfoView) + ; + verticalGroup->SetViewColor(ViewColor()); + layout->AddView(verticalGroup); + verticalGroup = BGroupLayoutBuilder(B_VERTICAL, 3) + .Add(fTopButton) + .Add(fBottomButton) + ; + verticalGroup->SetViewColor(ViewColor()); + layout->AddView(verticalGroup); + + BFont font; + fInfoView->GetFont(&font); + float fontSize = font.Size() * 0.8f; + font.SetSize(max_c(8.0f, fontSize)); + fInfoView->SetFont(&font, B_FONT_SIZE); + fInfoView->SetHighColor(tint_color(fInfoView->LowColor(), + B_DARKEN_4_TINT)); +// font_height fontHeight; +// font.GetHeight(&fontHeight); +// float textHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); +// fInfoView->SetExplicitMinSize(BSize(B_SIZE_UNSET, textHeight)); +// fInfoView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, textHeight)); + fInfoView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); return true; } @@ -314,13 +360,23 @@ DownloadProgressView::MessageReceived(BMessage* message) fIconView->SetTo(entry); fStatusBar->Reset(fPath.Leaf()); _StartNodeMonitor(entry); + + // Immediately switch to speed display whenever a new download + // starts. + sShowSpeed = true; + sLastEstimatedFinishSpeedToggleTime + = fProcessStartTime = fLastSpeedReferenceTime + = fEstimatedFinishReferenceTime = system_time(); break; }; case B_DOWNLOAD_PROGRESS: { - float progress; - if (message->FindFloat("progress", &progress) == B_OK) - fStatusBar->SetTo(progress); + int64 currentSize; + int64 expectedSize; + if (message->FindInt64("current size", ¤tSize) == B_OK + && message->FindInt64("expected size", &expectedSize) == B_OK) { + _UpdateStatus(currentSize, expectedSize); + } break; } case OPEN_DOWNLOAD: @@ -427,7 +483,7 @@ DownloadProgressView::MessageReceived(BMessage* message) break; } default: - BGridView::MessageReceived(message); + BGroupView::MessageReceived(message); } } @@ -468,6 +524,7 @@ DownloadProgressView::DownloadFinished() fBottomButton->SetLabel("Remove"); fBottomButton->SetMessage(new BMessage(REMOVE_DOWNLOAD)); fBottomButton->SetEnabled(true); + fInfoView->SetText(""); } @@ -484,9 +541,171 @@ DownloadProgressView::DownloadCanceled() } +/*static*/ void +DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse() +{ + bigtime_t now = system_time(); + if (sShowSpeed + && sLastEstimatedFinishSpeedToggleTime + kShowSpeedInterval + <= now) { + sShowSpeed = false; + sLastEstimatedFinishSpeedToggleTime = now; + } else if (!sShowSpeed + && sLastEstimatedFinishSpeedToggleTime + + kShowEstimatedFinishInterval <= now) { + sShowSpeed = true; + sLastEstimatedFinishSpeedToggleTime = now; + } +} + + // #pragma mark - private +void +DownloadProgressView::_UpdateStatus(off_t currentSize, off_t expectedSize) +{ + fCurrentSize = currentSize; + fExpectedSize = expectedSize; + + fStatusBar->SetTo(100.0 * currentSize / expectedSize); + + bigtime_t currentTime = system_time(); + if ((currentTime - fLastUpdateTime) > kMaxUpdateInterval) { + fLastUpdateTime = currentTime; + + if (currentTime >= fLastSpeedReferenceTime + kSpeedReferenceInterval) { + // update current speed every kSpeedReferenceInterval + fCurrentBytesPerSecondSlot + = (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots; + fBytesPerSecondSlot[fCurrentBytesPerSecondSlot] + = (double)(currentSize - fLastSpeedReferenceSize) + * 1000000LL / (currentTime - fLastSpeedReferenceTime); + fLastSpeedReferenceSize = currentSize; + fLastSpeedReferenceTime = currentTime; + fBytesPerSecond = 0.0; + size_t count = 0; + for (size_t i = 0; i < kBytesPerSecondSlots; i++) { + if (fBytesPerSecondSlot[i] != 0.0) { + fBytesPerSecond += fBytesPerSecondSlot[i]; + count++; + } + } + if (count > 0) + fBytesPerSecond /= count; + } + _UpdateStatusText(); + } +} + + +void +DownloadProgressView::_UpdateStatusText() +{ + fInfoView->SetText(""); + BString buffer; + if (sShowSpeed && fBytesPerSecond != 0.0) { + // Draw speed info + char sizeBuffer[128]; + buffer = "("; + buffer << string_for_size((double)fCurrentSize, sizeBuffer, + sizeof(sizeBuffer)); + buffer << " of "; + buffer << string_for_size((double)fExpectedSize, sizeBuffer, + sizeof(sizeBuffer)); + buffer << ", "; + buffer << string_for_size(fBytesPerSecond, sizeBuffer, + sizeof(sizeBuffer)); + buffer << "/s)"; + float stringWidth = fInfoView->StringWidth(buffer.String()); + if (stringWidth < fInfoView->Bounds().Width()) + fInfoView->SetText(buffer.String()); + else { + // complete string too wide, try with shorter version + buffer << string_for_size(fBytesPerSecond, sizeBuffer, + sizeof(sizeBuffer)); + buffer << "/s"; + stringWidth = fInfoView->StringWidth(buffer.String()); + if (stringWidth < fInfoView->Bounds().Width()) + fInfoView->SetText(buffer.String()); + } + } else if (!sShowSpeed && fCurrentSize < fExpectedSize) { + double totalBytesPerSecond = (double)(fCurrentSize + - fEstimatedFinishReferenceSize) + * 1000000LL / (system_time() - fEstimatedFinishReferenceTime); + double secondsRemaining = (fExpectedSize - fCurrentSize) + / totalBytesPerSecond; + time_t now = (time_t)real_time_clock(); + time_t finishTime = (time_t)(now + secondsRemaining); + + tm _time; + tm* time = localtime_r(&finishTime, &_time); + int32 year = time->tm_year + 1900; + + char timeText[32]; + time_t secondsPerDay = 24 * 60 * 60; + // TODO: Localization of time string... + if (now < finishTime - secondsPerDay) { + // process is going to take more than a day! + sprintf(timeText, "%0*d:%0*d %0*d/%0*d/%ld", + 2, time->tm_hour, 2, time->tm_min, + 2, time->tm_mon + 1, 2, time->tm_mday, year); + } else { + sprintf(timeText, "%0*d:%0*d", + 2, time->tm_hour, 2, time->tm_min); + } + + BString buffer1("Finish: "); + buffer1 << timeText; + finishTime -= now; + time = gmtime(&finishTime); + + BString buffer2; + if (finishTime > secondsPerDay) { + int64 days = finishTime / secondsPerDay; + if (days == 1) + buffer2 << "Over 1 day"; + else + buffer2 << "Over " << days << " days"; + } else if (finishTime > 60 * 60) { + int64 hours = finishTime / (60 * 60); + if (hours == 1) + buffer2 << "Over 1 hour"; + else + buffer2 << "Over " << hours << " hours"; + } else if (finishTime > 60) { + int64 minutes = finishTime / 60; + if (minutes == 1) + buffer2 << "Over 1 minute"; + else + buffer2 << minutes << " minutes"; + } else { + if (finishTime == 1) + buffer2 << "1 second"; + else + buffer2 << finishTime << " seconds"; + } + + buffer2 << " left"; + + buffer = "("; + buffer << buffer1 << " - " << buffer2 << ")"; + + float stringWidth = fInfoView->StringWidth(buffer.String()); + if (stringWidth < fInfoView->Bounds().Width()) + fInfoView->SetText(buffer.String()); + else { + // complete string too wide, try with shorter version + buffer = "("; + buffer << buffer1 << ")"; + stringWidth = fInfoView->StringWidth(buffer.String()); + if (stringWidth < fInfoView->Bounds().Width()) + fInfoView->SetText(buffer.String()); + } + } +} + + void DownloadProgressView::_StartNodeMonitor(const BEntry& entry) { diff --git a/src/apps/webpositive/DownloadProgressView.h b/src/apps/webpositive/DownloadProgressView.h index d39ca93380..4c4ae7e48f 100644 --- a/src/apps/webpositive/DownloadProgressView.h +++ b/src/apps/webpositive/DownloadProgressView.h @@ -28,12 +28,13 @@ #define DOWNLOAD_PROGRESS_VIEW_H -#include +#include #include #include class BEntry; class BStatusBar; +class BStringView; class BWebDownload; class IconView; class SmallButton; @@ -44,7 +45,7 @@ enum { }; -class DownloadProgressView : public BGridView { +class DownloadProgressView : public BGroupView { public: DownloadProgressView(BWebDownload* download); DownloadProgressView(const BMessage* archive); @@ -68,19 +69,41 @@ public: void DownloadFinished(); void DownloadCanceled(); + static void SpeedVersusEstimatedFinishTogglePulse(); + private: + void _UpdateStatus(off_t currentSize, + off_t expectedSize); + void _UpdateStatusText(); void _StartNodeMonitor(const BEntry& entry); void _StopNodeMonitor(); private: IconView* fIconView; BStatusBar* fStatusBar; + BStringView* fInfoView; SmallButton* fTopButton; SmallButton* fBottomButton; BWebDownload* fDownload; BString fURL; BPath fPath; + + off_t fCurrentSize; off_t fExpectedSize; + off_t fLastSpeedReferenceSize; + off_t fEstimatedFinishReferenceSize; + bigtime_t fLastUpdateTime; + bigtime_t fLastSpeedReferenceTime; + bigtime_t fProcessStartTime; + bigtime_t fLastSpeedUpdateTime; + bigtime_t fEstimatedFinishReferenceTime; + static const size_t kBytesPerSecondSlots = 10; + size_t fCurrentBytesPerSecondSlot; + double fBytesPerSecondSlot[kBytesPerSecondSlots]; + double fBytesPerSecond; + + static bigtime_t sLastEstimatedFinishSpeedToggleTime; + static bool sShowSpeed; }; #endif // DOWNLOAD_PROGRESS_VIEW_H diff --git a/src/apps/webpositive/DownloadWindow.cpp b/src/apps/webpositive/DownloadWindow.cpp index a2f3f38aff..5f6dbf1867 100644 --- a/src/apps/webpositive/DownloadWindow.cpp +++ b/src/apps/webpositive/DownloadWindow.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -65,6 +66,7 @@ public: : BGroupView(B_VERTICAL) { + SetFlags(Flags() | B_PULSE_NEEDED); SetViewColor(245, 245, 245); AddChild(BSpaceLayoutItem::CreateGlue()); } @@ -75,6 +77,11 @@ public: return BSize(minSize.width, 80); } + virtual void Pulse() + { + DownloadProgressView::SpeedVersusEstimatedFinishTogglePulse(); + } + protected: virtual void DoLayout() { @@ -129,6 +136,8 @@ DownloadWindow::DownloadWindow(BRect frame, bool visible, B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, B_AUTO_UPDATE_SIZE_LIMITS | B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE) { + SetPulseRate(1000000); + settings->AddListener(BMessenger(this)); BPath downloadPath; if (find_directory(B_DESKTOP_DIRECTORY, &downloadPath) != B_OK) @@ -159,15 +168,17 @@ DownloadWindow::DownloadWindow(BRect frame, bool visible, new BMessage(REMOVE_MISSING_DOWNLOADS)); fRemoveMissingButton->SetEnabled(false); + const float spacing = be_control_look->DefaultItemSpacing(); + AddChild(BGroupLayoutBuilder(B_VERTICAL) .Add(menuBar) .Add(scrollView) .Add(new BSeparatorView(B_HORIZONTAL, B_PLAIN_BORDER)) - .Add(BGroupLayoutBuilder(B_HORIZONTAL) + .Add(BGroupLayoutBuilder(B_HORIZONTAL, spacing) .AddGlue() .Add(fRemoveMissingButton) .Add(fRemoveFinishedButton) - .SetInsets(5, 5, 5, 5) + .SetInsets(12, 5, 12, 5) ) );