/* Open Tracker License Terms and Conditions Copyright (c) 1991-2000, Be Incorporated. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice applies to all licensees and shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of Be Incorporated shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Be Incorporated. Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks of Be Incorporated in the United States and other countries. Other brand product names are registered trademarks or trademarks of their respective holders. All rights reserved. */ /*! A subclass of BWindow that is used to display the status of the Tracker operations (copying, deleting, etc.). */ #include #include #include #include #include #include #include #include #include #include #include #include #include "AutoLock.h" #include "Bitmaps.h" #include "Commands.h" #include "StatusWindow.h" #include "StringForSize.h" #include "DeskWindow.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "StatusWindow" const float kDefaultStatusViewHeight = 50; const bigtime_t kMaxUpdateInterval = 100000LL; const bigtime_t kSpeedReferenceInterval = 2000000LL; const bigtime_t kShowSpeedInterval = 8000000LL; const bigtime_t kShowEstimatedFinishInterval = 4000000LL; const BRect kStatusRect(200, 200, 550, 200); static bigtime_t sLastEstimatedFinishSpeedToggleTime = -1; static bool sShowSpeed = true; static const time_t kSecondsPerDay = 24 * 60 * 60; class TCustomButton : public BButton { public: TCustomButton(BRect frame, uint32 command); virtual void Draw(BRect updateRect); private: typedef BButton _inherited; }; class BStatusMouseFilter : public BMessageFilter { public: BStatusMouseFilter(); virtual filter_result Filter(BMessage* message, BHandler** target); }; namespace BPrivate { BStatusWindow* gStatusWindow = NULL; } // #pragma mark - BStatusMouseFilter BStatusMouseFilter::BStatusMouseFilter() : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_MOUSE_DOWN) { } filter_result BStatusMouseFilter::Filter(BMessage* message, BHandler** target) { // If the target is the status bar, make sure the message goes to the // parent view instead. if ((*target)->Name() != NULL && strcmp((*target)->Name(), "StatusBar") == 0) { BView* view = dynamic_cast(*target); if (view != NULL) view = view->Parent(); if (view != NULL) *target = view; } return B_DISPATCH_MESSAGE; } // #pragma mark - TCustomButton TCustomButton::TCustomButton(BRect frame, uint32 what) : BButton(frame, "", "", new BMessage(what), B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW) { } void TCustomButton::Draw(BRect updateRect) { _inherited::Draw(updateRect); if (Message()->what == kStopButton) { updateRect = Bounds(); updateRect.InsetBy(9, 8); SetHighColor(0, 0, 0); if (Value() == B_CONTROL_ON) updateRect.OffsetBy(1, 1); FillRect(updateRect); } else { updateRect = Bounds(); updateRect.InsetBy(9, 7); BRect rect(updateRect); rect.right -= 3; updateRect.left += 3; updateRect.OffsetBy(1, 0); SetHighColor(0, 0, 0); if (Value() == B_CONTROL_ON) { updateRect.OffsetBy(1, 1); rect.OffsetBy(1, 1); } FillRect(updateRect); FillRect(rect); } } // #pragma mark - StatusBackgroundView class StatusBackgroundView : public BView { public: StatusBackgroundView(BRect frame) : BView(frame, "BackView", B_FOLLOW_ALL, B_WILL_DRAW | B_PULSE_NEEDED) { SetViewUIColor(B_PANEL_BACKGROUND_COLOR); } virtual void Pulse() { 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 - BStatusWindow BStatusWindow::BStatusWindow() : BWindow(kStatusRect, B_TRANSLATE("Tracker status"), B_TITLED_WINDOW, B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_NOT_ZOOMABLE, B_ALL_WORKSPACES), fRetainDesktopFocus(false) { SetSizeLimits(0, 100000, 0, 100000); fMouseDownFilter = new BStatusMouseFilter(); AddCommonFilter(fMouseDownFilter); BView* view = new StatusBackgroundView(Bounds()); AddChild(view); SetPulseRate(1000000); Hide(); Show(); } BStatusWindow::~BStatusWindow() { } void BStatusWindow::CreateStatusItem(thread_id thread, StatusWindowState type) { AutoLock lock(this); BRect rect(Bounds()); if (BStatusView* lastView = fViewList.LastItem()) rect.top = lastView->Frame().bottom + 1; else { // This is the first status item, reset speed/estimated finish toggle. sShowSpeed = true; sLastEstimatedFinishSpeedToggleTime = system_time(); } rect.bottom = rect.top + kDefaultStatusViewHeight - 1; BStatusView* view = new BStatusView(rect, thread, type); // the BStatusView will resize itself if needed in its constructor ChildAt(0)->AddChild(view); fViewList.AddItem(view); ResizeTo(Bounds().Width(), view->Frame().bottom); // find out if the desktop is the active window // if the status window is the only thing to take over active state and // desktop was active to begin with, return focus back to desktop // when we are done bool desktopActive = false; { AutoLock lock(be_app); int32 count = be_app->CountWindows(); for (int32 index = 0; index < count; index++) { BWindow* window = be_app->WindowAt(index); if (dynamic_cast(window) != NULL && window->IsActive()) { desktopActive = true; break; } } } if (IsHidden()) { fRetainDesktopFocus = desktopActive; Minimize(false); Show(); } else fRetainDesktopFocus &= desktopActive; } void BStatusWindow::InitStatusItem(thread_id thread, int32 totalItems, off_t totalSize, const entry_ref* destDir, bool showCount) { AutoLock lock(this); int32 numItems = fViewList.CountItems(); for (int32 index = 0; index < numItems; index++) { BStatusView* view = fViewList.ItemAt(index); if (view->Thread() == thread) { view->InitStatus(totalItems, totalSize, destDir, showCount); break; } } } void BStatusWindow::UpdateStatus(thread_id thread, const char* curItem, off_t itemSize, bool optional) { AutoLock lock(this); int32 numItems = fViewList.CountItems(); for (int32 index = 0; index < numItems; index++) { BStatusView* view = fViewList.ItemAt(index); if (view->Thread() == thread) { view->UpdateStatus(curItem, itemSize, optional); break; } } } void BStatusWindow::RemoveStatusItem(thread_id thread) { AutoLock lock(this); BStatusView* winner = NULL; int32 numItems = fViewList.CountItems(); int32 index; for (index = 0; index < numItems; index++) { BStatusView* view = fViewList.ItemAt(index); if (view->Thread() == thread) { winner = view; break; } } if (winner != NULL) { // The height by which the other views will have to be moved // (in pixel count). float height = winner->Bounds().Height() + 1; fViewList.RemoveItem(winner); winner->RemoveSelf(); delete winner; if (--numItems == 0 && !IsHidden()) { BDeskWindow* desktop = NULL; if (fRetainDesktopFocus) { AutoLock lock(be_app); int32 count = be_app->CountWindows(); for (int32 index = 0; index < count; index++) { desktop = dynamic_cast( be_app->WindowAt(index)); if (desktop != NULL) break; } } Hide(); if (desktop != NULL) { // desktop was active when we first started, // make it active again desktop->Activate(); } } for (; index < numItems; index++) fViewList.ItemAt(index)->MoveBy(0, -height); ResizeTo(Bounds().Width(), Bounds().Height() - height); } } bool BStatusWindow::CheckCanceledOrPaused(thread_id thread) { bool wasCanceled = false; bool isPaused = false; BStatusView* view = NULL; AutoLock lock(this); // check if cancel or pause hit for (int32 index = fViewList.CountItems() - 1; index >= 0; index--) { view = fViewList.ItemAt(index); if (view && view->Thread() == thread) { isPaused = view->IsPaused(); wasCanceled = view->WasCanceled(); break; } } if (wasCanceled || !isPaused) return wasCanceled; if (isPaused && view != NULL) { // say we are paused view->Invalidate(); thread_id thread = view->Thread(); lock.Unlock(); // and suspend ourselves // we will get resumed from BStatusView::MessageReceived ASSERT(find_thread(NULL) == thread); suspend_thread(thread); } return wasCanceled; } bool BStatusWindow::AttemptToQuit() { // called when tracker is quitting // try to cancel all the move/copy/empty trash threads in a nice way // by issuing cancels int32 count = fViewList.CountItems(); if (count == 0) return true; for (int32 index = 0; index < count; index++) fViewList.ItemAt(index)->SetWasCanceled(); // maybe next time everything will have been canceled return false; } void BStatusWindow::WindowActivated(bool state) { if (!state) fRetainDesktopFocus = false; return _inherited::WindowActivated(state); } // #pragma mark - BStatusView BStatusView::BStatusView(BRect bounds, thread_id thread, StatusWindowState type) : BView(bounds, "StatusView", B_FOLLOW_NONE, B_WILL_DRAW), fStatusBar(NULL), fType(type), fBitmap(NULL), fStopButton(NULL), fPauseButton(NULL), fThread(thread) { Init(); SetViewUIColor(B_PANEL_BACKGROUND_COLOR); SetLowUIColor(ViewUIColor()); SetHighColor(20, 20, 20); SetDrawingMode(B_OP_ALPHA); const float buttonWidth = 22; const float buttonHeight = 20; BRect rect(bounds); rect.OffsetTo(B_ORIGIN); rect.left += 40; rect.right -= buttonWidth * 2 + 12; rect.top += 6; rect.bottom = rect.top + 15; BString caption; int32 id = 0; switch (type) { case kCopyState: caption = B_TRANSLATE("Preparing to copy items" B_UTF8_ELLIPSIS); id = R_CopyStatusIcon; break; case kMoveState: caption = B_TRANSLATE("Preparing to move items" B_UTF8_ELLIPSIS); id = R_MoveStatusIcon; break; case kCreateLinkState: caption = B_TRANSLATE("Preparing to create links" B_UTF8_ELLIPSIS); id = R_MoveStatusIcon; break; case kTrashState: caption = B_TRANSLATE("Preparing to empty Trash" B_UTF8_ELLIPSIS); id = R_TrashIcon; break; case kVolumeState: caption = B_TRANSLATE("Searching for disks to mount" B_UTF8_ELLIPSIS); break; case kDeleteState: caption = B_TRANSLATE("Preparing to delete items" B_UTF8_ELLIPSIS); id = R_TrashIcon; break; case kRestoreFromTrashState: caption = B_TRANSLATE("Preparing to restore items" B_UTF8_ELLIPSIS); break; default: TRESPASS(); break; } if (caption.Length() != 0) { fStatusBar = new BStatusBar(rect, "StatusBar", caption.String()); fStatusBar->SetBarHeight(12); float width, height; fStatusBar->GetPreferredSize(&width, &height); fStatusBar->ResizeTo(fStatusBar->Frame().Width(), height); AddChild(fStatusBar); // Figure out how much room we need to display the additional status // message below the bar font_height fh; GetFontHeight(&fh); BRect f = fStatusBar->Frame(); // Height is 3 x the "room from the top" + bar height + room for // string. ResizeTo(Bounds().Width(), f.top + f.Height() + fh.leading + fh.ascent + fh.descent + f.top); } if (id != 0) { fBitmap = new BBitmap(BRect(0, 0, 16, 16), B_RGBA32); GetTrackerResources()->GetIconResource(id, B_MINI_ICON, fBitmap); } rect = Bounds(); rect.left = rect.right - buttonWidth * 2 - 7; rect.right = rect.left + buttonWidth; rect.top = floorf((rect.top + rect.bottom) / 2 + 0.5) - buttonHeight / 2; rect.bottom = rect.top + buttonHeight; fPauseButton = new TCustomButton(rect, kPauseButton); fPauseButton->ResizeTo(buttonWidth, buttonHeight); AddChild(fPauseButton); rect.OffsetBy(buttonWidth + 2, 0); fStopButton = new TCustomButton(rect, kStopButton); fStopButton->ResizeTo(buttonWidth, buttonHeight); AddChild(fStopButton); } BStatusView::~BStatusView() { delete fBitmap; } void BStatusView::Init() { fTotalSize = fItemSize = fSizeProcessed = fLastSpeedReferenceSize = fEstimatedFinishReferenceSize = 0; fCurItem = 0; fLastUpdateTime = fLastSpeedReferenceTime = fProcessStartTime = fLastSpeedUpdateTime = fEstimatedFinishReferenceTime = system_time(); fCurrentBytesPerSecondSlot = 0; for (size_t i = 0; i < kBytesPerSecondSlots; i++) fBytesPerSecondSlot[i] = 0.0; fBytesPerSecond = 0.0; fShowCount = fWasCanceled = fIsPaused = false; fDestDir.SetTo(""); fPendingStatusString[0] = '\0'; } void BStatusView::InitStatus(int32 totalItems, off_t totalSize, const entry_ref* destDir, bool showCount) { Init(); fTotalSize = totalSize; fShowCount = showCount; BEntry entry; char name[B_FILE_NAME_LENGTH]; if (destDir != NULL && entry.SetTo(destDir) == B_OK) { entry.GetName(name); fDestDir.SetTo(name); } BString buffer; if (totalItems > 0) { char totalStr[32]; buffer.SetTo(B_TRANSLATE("of %items")); snprintf(totalStr, sizeof(totalStr), "%" B_PRId32, totalItems); buffer.ReplaceFirst("%items", totalStr); } switch (fType) { case kCopyState: fStatusBar->Reset(B_TRANSLATE("Copying: "), buffer.String()); break; case kCreateLinkState: fStatusBar->Reset(B_TRANSLATE("Creating links: "), buffer.String()); break; case kMoveState: fStatusBar->Reset(B_TRANSLATE("Moving: "), buffer.String()); break; case kTrashState: fStatusBar->Reset( B_TRANSLATE("Emptying Trash" B_UTF8_ELLIPSIS " "), buffer.String()); break; case kDeleteState: fStatusBar->Reset(B_TRANSLATE("Deleting: "), buffer.String()); break; case kRestoreFromTrashState: fStatusBar->Reset(B_TRANSLATE("Restoring: "), buffer.String()); break; } fStatusBar->SetMaxValue(1); // SetMaxValue has to be here because Reset changes it to 100 Invalidate(); } void BStatusView::Draw(BRect updateRect) { if (fBitmap != NULL) { BPoint location; location.x = (fStatusBar->Frame().left - fBitmap->Bounds().Width()) / 2; location.y = (Bounds().Height()- fBitmap->Bounds().Height()) / 2; DrawBitmap(fBitmap, location); } BRect bounds(Bounds()); be_control_look->DrawRaisedBorder(this, bounds, updateRect, ViewColor()); SetHighColor(0, 0, 0); BPoint tp = fStatusBar->Frame().LeftBottom(); font_height fh; GetFontHeight(&fh); tp.y += ceilf(fh.leading) + ceilf(fh.ascent); if (IsPaused()) { DrawString(B_TRANSLATE("Paused: click to resume or stop"), tp); return; } BFont font; GetFont(&font); float normalFontSize = font.Size(); float smallFontSize = max_c(normalFontSize * 0.8f, 8.0f); float availableSpace = fStatusBar->Frame().Width(); availableSpace -= be_control_look->DefaultLabelSpacing(); // subtract to provide some room between our two strings float destinationStringWidth = 0.f; BString destinationString(_DestinationString(&destinationStringWidth)); availableSpace -= destinationStringWidth; float statusStringWidth = 0.f; BString statusString(_StatusString(availableSpace, smallFontSize, &statusStringWidth)); if (statusStringWidth > availableSpace) { TruncateString(&destinationString, B_TRUNCATE_MIDDLE, availableSpace + destinationStringWidth - statusStringWidth); } BPoint textPoint = fStatusBar->Frame().LeftBottom(); textPoint.y += ceilf(fh.leading) + ceilf(fh.ascent); if (destinationStringWidth > 0) { DrawString(destinationString.String(), textPoint); } SetHighColor(tint_color(LowColor(), B_DARKEN_4_TINT)); font.SetSize(smallFontSize); SetFont(&font, B_FONT_SIZE); textPoint.x = fStatusBar->Frame().right - statusStringWidth; DrawString(statusString.String(), textPoint); font.SetSize(normalFontSize); SetFont(&font, B_FONT_SIZE); } BString BStatusView::_DestinationString(float* _width) { if (fDestDir.Length() > 0) { BString buffer(B_TRANSLATE("To: %dir")); buffer.ReplaceFirst("%dir", fDestDir); *_width = ceilf(StringWidth(buffer.String())); return buffer; } else { *_width = 0; return BString(); } } BString BStatusView::_StatusString(float availableSpace, float fontSize, float* _width) { BFont font; GetFont(&font); float oldSize = font.Size(); font.SetSize(fontSize); SetFont(&font, B_FONT_SIZE); BString status; if (sShowSpeed) { status = _SpeedStatusString(availableSpace, _width); } else status = _TimeStatusString(availableSpace, _width); font.SetSize(oldSize); SetFont(&font, B_FONT_SIZE); return status; } BString BStatusView::_SpeedStatusString(float availableSpace, float* _width) { BString string(_FullSpeedString()); *_width = StringWidth(string.String()); if (*_width > availableSpace) { string.SetTo(_ShortSpeedString()); *_width = StringWidth(string.String()); } *_width = ceilf(*_width); return string; } BString BStatusView::_FullSpeedString() { BString buffer; if (fBytesPerSecond != 0.0) { char sizeBuffer[128]; buffer.SetTo(B_TRANSLATE( "%SizeProcessed of %TotalSize, %BytesPerSecond/s")); buffer.ReplaceFirst("%SizeProcessed", string_for_size((double)fSizeProcessed, sizeBuffer, sizeof(sizeBuffer))); buffer.ReplaceFirst("%TotalSize", string_for_size((double)fTotalSize, sizeBuffer, sizeof(sizeBuffer))); buffer.ReplaceFirst("%BytesPerSecond", string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer))); } return buffer; } BString BStatusView::_ShortSpeedString() { BString buffer; if (fBytesPerSecond != 0.0) { char sizeBuffer[128]; buffer << B_TRANSLATE("%BytesPerSecond/s"); buffer.ReplaceFirst("%BytesPerSecond", string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer))); } return buffer; } BString BStatusView::_TimeStatusString(float availableSpace, float* _width) { double totalBytesPerSecond = (double)(fSizeProcessed - fEstimatedFinishReferenceSize) * 1000000LL / (system_time() - fEstimatedFinishReferenceTime); double secondsRemaining = (fTotalSize - fSizeProcessed) / totalBytesPerSecond; time_t now = (time_t)real_time_clock(); BString string; if (secondsRemaining < 0 || (sizeof(time_t) == 4 && now + secondsRemaining > INT32_MAX)) { string = B_TRANSLATE("Finish: after several years"); } else { char timeText[32]; time_t finishTime = (time_t)(now + secondsRemaining); if (finishTime - now > kSecondsPerDay) { BDateTimeFormat().Format(timeText, sizeof(timeText), finishTime, B_MEDIUM_DATE_FORMAT, B_MEDIUM_TIME_FORMAT); } else { BTimeFormat().Format(timeText, sizeof(timeText), finishTime, B_MEDIUM_TIME_FORMAT); } string = _FullTimeRemainingString(now, finishTime, timeText); float width = StringWidth(string.String()); if (width > availableSpace) { string.SetTo(_ShortTimeRemainingString(timeText)); } } if (_width != NULL) *_width = StringWidth(string.String()); return string; } BString BStatusView::_ShortTimeRemainingString(const char* timeText) { BString buffer; // complete string too wide, try with shorter version buffer.SetTo(B_TRANSLATE("Finish: %time")); buffer.ReplaceFirst("%time", timeText); return buffer; } BString BStatusView::_FullTimeRemainingString(time_t now, time_t finishTime, const char* timeText) { BDurationFormat formatter; BString buffer; BString finishStr; if (finishTime - now > 60 * 60) { buffer.SetTo(B_TRANSLATE("Finish: %time - Over %finishtime left")); formatter.Format(finishStr, now * 1000000LL, finishTime * 1000000LL); } else { buffer.SetTo(B_TRANSLATE("Finish: %time - %finishtime left")); formatter.Format(finishStr, now * 1000000LL, finishTime * 1000000LL); } buffer.ReplaceFirst("%time", timeText); buffer.ReplaceFirst("%finishtime", finishStr); return buffer; } void BStatusView::AttachedToWindow() { fPauseButton->SetTarget(this); fStopButton->SetTarget(this); } void BStatusView::MessageReceived(BMessage* message) { switch (message->what) { case kPauseButton: fIsPaused = !fIsPaused; fPauseButton->SetValue(fIsPaused ? B_CONTROL_ON : B_CONTROL_OFF); if (fBytesPerSecond != 0.0) { fBytesPerSecond = 0.0; for (size_t i = 0; i < kBytesPerSecondSlots; i++) fBytesPerSecondSlot[i] = 0.0; Invalidate(); } if (!fIsPaused) { fEstimatedFinishReferenceTime = system_time(); fEstimatedFinishReferenceSize = fSizeProcessed; // force window update Invalidate(); // let 'er rip resume_thread(Thread()); } break; case kStopButton: fWasCanceled = true; if (fIsPaused) { // resume so that the copy loop gets a chance to finish up fIsPaused = false; // force window update Invalidate(); // let 'er rip resume_thread(Thread()); } break; default: _inherited::MessageReceived(message); break; } } void BStatusView::UpdateStatus(const char* curItem, off_t itemSize, bool optional) { if (!fShowCount) { fStatusBar->Update((float)fItemSize / fTotalSize); fItemSize = 0; return; } if (curItem != NULL) fCurItem++; fItemSize += itemSize; fSizeProcessed += itemSize; bigtime_t currentTime = system_time(); if (!optional || ((currentTime - fLastUpdateTime) > kMaxUpdateInterval)) { if (curItem != NULL || fPendingStatusString[0]) { // forced update or past update time BString buffer; buffer << fCurItem << " "; // if we don't have curItem, take the one from the stash const char* statusItem = curItem != NULL ? curItem : fPendingStatusString; fStatusBar->Update((float)fItemSize / fTotalSize, statusItem, buffer.String()); // we already displayed this item, clear the stash fPendingStatusString[0] = '\0'; fLastUpdateTime = currentTime; } else { // don't have a file to show, just update the bar fStatusBar->Update((float)fItemSize / fTotalSize); } if (currentTime >= fLastSpeedReferenceTime + kSpeedReferenceInterval) { // update current speed every kSpeedReferenceInterval fCurrentBytesPerSecondSlot = (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots; fBytesPerSecondSlot[fCurrentBytesPerSecondSlot] = (double)(fSizeProcessed - fLastSpeedReferenceSize) * 1000000LL / (currentTime - fLastSpeedReferenceTime); fLastSpeedReferenceSize = fSizeProcessed; 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; BString toolTip = _TimeStatusString(1024.f, NULL); toolTip << "\n" << _FullSpeedString(); SetToolTip(toolTip.String()); Invalidate(); } fItemSize = 0; } else if (curItem != NULL) { // stash away the name of the item we are currently processing // so we can show it when the time comes strncpy(fPendingStatusString, curItem, 127); fPendingStatusString[127] = '0'; } else SetToolTip((const char*)NULL); } void BStatusView::SetWasCanceled() { fWasCanceled = true; }