#include <Application.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Debug.h>
#include <DurationFormat.h>
#include <Locale.h>
#include <MessageFilter.h>
#include <StringView.h>
#include <String.h>
#include <TimeFormat.h>
#include <string.h>
#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;
}
BStatusMouseFilter::BStatusMouseFilter()
:
BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_MOUSE_DOWN)
{
}
filter_result
BStatusMouseFilter::Filter(BMessage* message, BHandler** target)
{
if ((*target)->Name() != NULL
&& strcmp((*target)->Name(), "StatusBar") == 0) {
BView* view = dynamic_cast<BView*>(*target);
if (view != NULL)
view = view->Parent();
if (view != NULL)
*target = view;
}
return B_DISPATCH_MESSAGE;
}
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);
}
}
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;
}
}
};
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<BWindow> lock(this);
BRect rect(Bounds());
if (BStatusView* lastView = fViewList.LastItem())
rect.top = lastView->Frame().bottom + 1;
else {
sShowSpeed = true;
sLastEstimatedFinishSpeedToggleTime = system_time();
}
rect.bottom = rect.top + kDefaultStatusViewHeight - 1;
BStatusView* view = new BStatusView(rect, thread, type);
ChildAt(0)->AddChild(view);
fViewList.AddItem(view);
ResizeTo(Bounds().Width(), view->Frame().bottom);
bool desktopActive = false;
{
AutoLock<BLooper> lock(be_app);
int32 count = be_app->CountWindows();
for (int32 index = 0; index < count; index++) {
BWindow* window = be_app->WindowAt(index);
if (dynamic_cast<BDeskWindow*>(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<BWindow> 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<BWindow> 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<BWindow> 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) {
float height = winner->Bounds().Height() + 1;
fViewList.RemoveItem(winner);
winner->RemoveSelf();
delete winner;
if (--numItems == 0 && !IsHidden()) {
BDeskWindow* desktop = NULL;
if (fRetainDesktopFocus) {
AutoLock<BLooper> lock(be_app);
int32 count = be_app->CountWindows();
for (int32 index = 0; index < count; index++) {
desktop = dynamic_cast<BDeskWindow*>(
be_app->WindowAt(index));
if (desktop != NULL)
break;
}
}
Hide();
if (desktop != NULL) {
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<BWindow> lock(this);
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) {
view->Invalidate();
thread_id thread = view->Thread();
lock.Unlock();
ASSERT(find_thread(NULL) == thread);
suspend_thread(thread);
}
return wasCanceled;
}
bool
BStatusWindow::AttemptToQuit()
{
int32 count = fViewList.CountItems();
if (count == 0)
return true;
for (int32 index = 0; index < count; index++)
fViewList.ItemAt(index)->SetWasCanceled();
return false;
}
void
BStatusWindow::WindowActivated(bool state)
{
if (!state)
fRetainDesktopFocus = false;
return _inherited::WindowActivated(state);
}
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);
font_height fh;
GetFontHeight(&fh);
BRect f = fStatusBar->Frame();
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);
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());
SetHighUIColor(B_PANEL_TEXT_COLOR);
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();
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);
}
if (LowColor().IsLight())
SetHighColor(tint_color(LowColor(), B_DARKEN_4_TINT));
else
SetHighColor(tint_color(LowColor(), B_LIGHTEN_2_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;
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;
Invalidate();
resume_thread(Thread());
}
break;
case kStopButton:
fWasCanceled = true;
if (fIsPaused) {
fIsPaused = false;
Invalidate();
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]) {
BString buffer;
buffer << fCurItem << " ";
const char* statusItem = curItem != NULL
? curItem : fPendingStatusString;
fStatusBar->Update((float)fItemSize / fTotalSize, statusItem,
buffer.String());
fPendingStatusString[0] = '\0';
fLastUpdateTime = currentTime;
} else {
fStatusBar->Update((float)fItemSize / fTotalSize);
}
if (currentTime
>= fLastSpeedReferenceTime + 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) {
strncpy(fPendingStatusString, curItem, 127);
fPendingStatusString[127] = '0';
} else
SetToolTip((const char*)NULL);
}
void
BStatusView::SetWasCanceled()
{
fWasCanceled = true;
}