xref: /haiku/src/kits/tracker/StatusWindow.cpp (revision f2b4344867e97c3f4e742a1b4a15e6879644601a)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 /*!	A subclass of BWindow that is used to display the status of the Tracker
36 	operations (copying, deleting, etc.).
37 */
38 
39 
40 #include <Application.h>
41 #include <Button.h>
42 #include <Catalog.h>
43 #include <ControlLook.h>
44 #include <Debug.h>
45 #include <DurationFormat.h>
46 #include <Locale.h>
47 #include <MessageFilter.h>
48 #include <StringView.h>
49 #include <String.h>
50 
51 #include <string.h>
52 
53 #include "AutoLock.h"
54 #include "Bitmaps.h"
55 #include "Commands.h"
56 #include "StatusWindow.h"
57 #include "StringForSize.h"
58 #include "DeskWindow.h"
59 
60 
61 const float	kDefaultStatusViewHeight = 50;
62 const bigtime_t kMaxUpdateInterval = 100000LL;
63 const bigtime_t kSpeedReferenceInterval = 2000000LL;
64 const bigtime_t kShowSpeedInterval = 8000000LL;
65 const bigtime_t kShowEstimatedFinishInterval = 4000000LL;
66 const BRect kStatusRect(200, 200, 550, 200);
67 
68 static bigtime_t sLastEstimatedFinishSpeedToggleTime = -1;
69 static bool sShowSpeed = true;
70 static const time_t kSecondsPerDay = 24 * 60 * 60;
71 
72 class TCustomButton : public BButton {
73 public:
74 								TCustomButton(BRect frame, uint32 command);
75 	virtual	void				Draw(BRect updateRect);
76 private:
77 			typedef BButton _inherited;
78 };
79 
80 
81 class BStatusMouseFilter : public BMessageFilter {
82 public:
83 								BStatusMouseFilter();
84 	virtual	filter_result		Filter(BMessage* message, BHandler** target);
85 };
86 
87 
88 namespace BPrivate {
89 BStatusWindow *gStatusWindow = NULL;
90 }
91 
92 
93 BStatusMouseFilter::BStatusMouseFilter()
94 	:
95 	BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_MOUSE_DOWN)
96 {
97 }
98 
99 
100 filter_result
101 BStatusMouseFilter::Filter(BMessage* message, BHandler** target)
102 {
103 	// If the target is the status bar, make sure the message goes to the
104 	// parent view instead.
105 	if ((*target)->Name() != NULL
106 		&& strcmp((*target)->Name(), "StatusBar") == 0) {
107 		BView* view = dynamic_cast<BView*>(*target);
108 		if (view != NULL)
109 			view = view->Parent();
110 		if (view != NULL)
111 			*target = view;
112 	}
113 
114 	return B_DISPATCH_MESSAGE;
115 }
116 
117 
118 TCustomButton::TCustomButton(BRect frame, uint32 what)
119 	:
120 	BButton(frame, "", "", new BMessage(what), B_FOLLOW_LEFT | B_FOLLOW_TOP,
121 		B_WILL_DRAW)
122 {
123 }
124 
125 
126 void
127 TCustomButton::Draw(BRect updateRect)
128 {
129 	_inherited::Draw(updateRect);
130 
131 	if (Message()->what == kStopButton) {
132 		updateRect = Bounds();
133 		updateRect.InsetBy(9, 8);
134 		SetHighColor(0, 0, 0);
135 		if (Value() == B_CONTROL_ON)
136 			updateRect.OffsetBy(1, 1);
137 		FillRect(updateRect);
138 	} else {
139 		updateRect = Bounds();
140 		updateRect.InsetBy(9, 7);
141 		BRect rect(updateRect);
142 		rect.right -= 3;
143 
144 		updateRect.left += 3;
145 		updateRect.OffsetBy(1, 0);
146 		SetHighColor(0, 0, 0);
147 		if (Value() == B_CONTROL_ON) {
148 			updateRect.OffsetBy(1, 1);
149 			rect.OffsetBy(1, 1);
150 		}
151 		FillRect(updateRect);
152 		FillRect(rect);
153 	}
154 }
155 
156 
157 // #pragma mark -
158 
159 
160 class StatusBackgroundView : public BView {
161 public:
162 	StatusBackgroundView(BRect frame)
163 		: BView(frame, "BackView", B_FOLLOW_ALL, B_WILL_DRAW | B_PULSE_NEEDED)
164 	{
165 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
166 	}
167 
168 	virtual void Pulse()
169 	{
170 		bigtime_t now = system_time();
171 		if (sShowSpeed
172 			&& sLastEstimatedFinishSpeedToggleTime + kShowSpeedInterval
173 				<= now) {
174 			sShowSpeed = false;
175 			sLastEstimatedFinishSpeedToggleTime = now;
176 		} else if (!sShowSpeed
177 			&& sLastEstimatedFinishSpeedToggleTime
178 				+ kShowEstimatedFinishInterval <= now) {
179 			sShowSpeed = true;
180 			sLastEstimatedFinishSpeedToggleTime = now;
181 		}
182 	}
183 };
184 
185 
186 // #pragma mark - BStatusWindow
187 
188 
189 #undef B_TRANSLATE_CONTEXT
190 #define B_TRANSLATE_CONTEXT "StatusWindow"
191 
192 
193 BStatusWindow::BStatusWindow()
194 	:
195 	BWindow(kStatusRect, B_TRANSLATE("Tracker status"),	B_TITLED_WINDOW,
196 		B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_NOT_ZOOMABLE, B_ALL_WORKSPACES),
197 	fRetainDesktopFocus(false)
198 {
199 	SetSizeLimits(0, 100000, 0, 100000);
200 	fMouseDownFilter = new BStatusMouseFilter();
201 	AddCommonFilter(fMouseDownFilter);
202 
203 	BView* view = new StatusBackgroundView(Bounds());
204 	AddChild(view);
205 
206 	SetPulseRate(1000000);
207 
208 	Hide();
209 	Show();
210 }
211 
212 
213 BStatusWindow::~BStatusWindow()
214 {
215 }
216 
217 
218 void
219 BStatusWindow::CreateStatusItem(thread_id thread, StatusWindowState type)
220 {
221 	AutoLock<BWindow> lock(this);
222 
223 	BRect rect(Bounds());
224 	if (BStatusView* lastView = fViewList.LastItem())
225 		rect.top = lastView->Frame().bottom + 1;
226 	else {
227 		// This is the first status item, reset speed/estimated finish toggle.
228 		sShowSpeed = true;
229 		sLastEstimatedFinishSpeedToggleTime = system_time();
230 	}
231 	rect.bottom = rect.top + kDefaultStatusViewHeight - 1;
232 
233 	BStatusView* view = new BStatusView(rect, thread, type);
234 	// the BStatusView will resize itself if needed in its constructor
235 	ChildAt(0)->AddChild(view);
236 	fViewList.AddItem(view);
237 
238 	ResizeTo(Bounds().Width(), view->Frame().bottom);
239 
240 	// find out if the desktop is the active window
241 	// if the status window is the only thing to take over active state and
242 	// desktop was active to begin with, return focus back to desktop
243 	// when we are done
244 	bool desktopActive = false;
245 	{
246 		AutoLock<BLooper> lock(be_app);
247 		int32 count = be_app->CountWindows();
248 		for (int32 index = 0; index < count; index++) {
249 			if (dynamic_cast<BDeskWindow *>(be_app->WindowAt(index))
250 				&& be_app->WindowAt(index)->IsActive()) {
251 				desktopActive = true;
252 				break;
253 			}
254 		}
255 	}
256 
257 	if (IsHidden()) {
258 		fRetainDesktopFocus = desktopActive;
259 		Minimize(false);
260 		Show();
261 	} else
262 		fRetainDesktopFocus &= desktopActive;
263 }
264 
265 
266 void
267 BStatusWindow::InitStatusItem(thread_id thread, int32 totalItems,
268 	off_t totalSize, const entry_ref* destDir, bool showCount)
269 {
270 	AutoLock<BWindow> lock(this);
271 
272 	int32 numItems = fViewList.CountItems();
273 	for (int32 index = 0; index < numItems; index++) {
274 		BStatusView* view = fViewList.ItemAt(index);
275 		if (view->Thread() == thread) {
276 			view->InitStatus(totalItems, totalSize, destDir, showCount);
277 			break;
278 		}
279 	}
280 
281 }
282 
283 
284 void
285 BStatusWindow::UpdateStatus(thread_id thread, const char* curItem,
286 	off_t itemSize, bool optional)
287 {
288 	AutoLock<BWindow> lock(this);
289 
290 	int32 numItems = fViewList.CountItems();
291 	for (int32 index = 0; index < numItems; index++) {
292 		BStatusView* view = fViewList.ItemAt(index);
293 		if (view->Thread() == thread) {
294 			view->UpdateStatus(curItem, itemSize, optional);
295 			break;
296 		}
297 	}
298 }
299 
300 
301 void
302 BStatusWindow::RemoveStatusItem(thread_id thread)
303 {
304 	AutoLock<BWindow> lock(this);
305 	BStatusView* winner = NULL;
306 
307 	int32 numItems = fViewList.CountItems();
308 	int32 index;
309 	for (index = 0; index < numItems; index++) {
310 		BStatusView* view = fViewList.ItemAt(index);
311 		if (view->Thread() == thread) {
312 			winner = view;
313 			break;
314 		}
315 	}
316 
317 	if (winner != NULL) {
318 		// The height by which the other views will have to be moved (in pixel
319 		// count).
320 		float height = winner->Bounds().Height() + 1;
321 		fViewList.RemoveItem(winner);
322 		winner->RemoveSelf();
323 		delete winner;
324 
325 		if (--numItems == 0 && !IsHidden()) {
326 			BDeskWindow* desktop = NULL;
327 			if (fRetainDesktopFocus) {
328 				AutoLock<BLooper> lock(be_app);
329 				int32 count = be_app->CountWindows();
330 				for (int32 index = 0; index < count; index++) {
331 					desktop = dynamic_cast<BDeskWindow*>(
332 						be_app->WindowAt(index));
333 					if (desktop != NULL)
334 						break;
335 				}
336 			}
337 			Hide();
338 			if (desktop != NULL) {
339 				// desktop was active when we first started,
340 				// make it active again
341 				desktop->Activate();
342 			}
343 		}
344 
345 		for (; index < numItems; index++)
346 			fViewList.ItemAt(index)->MoveBy(0, -height);
347 
348 		ResizeTo(Bounds().Width(), Bounds().Height() - height);
349 	}
350 }
351 
352 
353 bool
354 BStatusWindow::CheckCanceledOrPaused(thread_id thread)
355 {
356 	bool wasCanceled = false;
357 	bool isPaused = false;
358 
359 	BStatusView* view = NULL;
360 
361 	AutoLock<BWindow> lock(this);
362 	// check if cancel or pause hit
363 	for (int32 index = fViewList.CountItems() - 1; index >= 0; index--) {
364 		view = fViewList.ItemAt(index);
365 		if (view && view->Thread() == thread) {
366 			isPaused = view->IsPaused();
367 			wasCanceled = view->WasCanceled();
368 			break;
369 		}
370 	}
371 
372 	if (wasCanceled || !isPaused)
373 		return wasCanceled;
374 
375 	if (isPaused && view != NULL) {
376 		// say we are paused
377 		view->Invalidate();
378 		thread_id thread = view->Thread();
379 
380 		lock.Unlock();
381 
382 		// and suspend ourselves
383 		// we will get resumed from BStatusView::MessageReceived
384 		ASSERT(find_thread(NULL) == thread);
385 		suspend_thread(thread);
386 	}
387 
388 	return wasCanceled;
389 }
390 
391 
392 bool
393 BStatusWindow::AttemptToQuit()
394 {
395 	// called when tracker is quitting
396 	// try to cancel all the move/copy/empty trash threads in a nice way
397 	// by issuing cancels
398 	int32 count = fViewList.CountItems();
399 
400 	if (count == 0)
401 		return true;
402 
403 	for (int32 index = 0; index < count; index++)
404 		fViewList.ItemAt(index)->SetWasCanceled();
405 
406 	// maybe next time everything will have been canceled
407 	return false;
408 }
409 
410 
411 void
412 BStatusWindow::WindowActivated(bool state)
413 {
414 	if (!state)
415 		fRetainDesktopFocus = false;
416 
417 	return _inherited::WindowActivated(state);
418 }
419 
420 
421 // #pragma mark - BStatusView
422 
423 
424 BStatusView::BStatusView(BRect bounds, thread_id thread,
425 	StatusWindowState type)
426 	:
427 	BView(bounds, "StatusView", B_FOLLOW_NONE, B_WILL_DRAW),
428 	fType(type),
429 	fBitmap(NULL),
430 	fThread(thread)
431 {
432 	Init();
433 
434 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
435 	SetLowColor(ViewColor());
436 	SetHighColor(20, 20, 20);
437 	SetDrawingMode(B_OP_OVER);
438 
439 	const float buttonWidth = 22;
440 	const float buttonHeight = 20;
441 
442 	BRect rect(bounds);
443 	rect.OffsetTo(B_ORIGIN);
444 	rect.left += 40;
445 	rect.right -= buttonWidth * 2 + 12;
446 	rect.top += 6;
447 	rect.bottom = rect.top + 15;
448 
449 	BString caption;
450 	int32 id = 0;
451 
452 	switch (type) {
453 		case kCopyState:
454 			caption = B_TRANSLATE("Preparing to copy items" B_UTF8_ELLIPSIS);
455 			id = R_CopyStatusBitmap;
456 			break;
457 
458 		case kMoveState:
459 			caption = B_TRANSLATE("Preparing to move items" B_UTF8_ELLIPSIS);
460 			id = R_MoveStatusBitmap;
461 			break;
462 
463 		case kCreateLinkState:
464 			caption = B_TRANSLATE("Preparing to create links" B_UTF8_ELLIPSIS);
465 			id = R_MoveStatusBitmap;
466 			break;
467 
468 		case kTrashState:
469 			caption = B_TRANSLATE("Preparing to empty Trash" B_UTF8_ELLIPSIS);
470 			id = R_TrashStatusBitmap;
471 			break;
472 
473 		case kVolumeState:
474 			caption = B_TRANSLATE("Searching for disks to mount" B_UTF8_ELLIPSIS);
475 			break;
476 
477 		case kDeleteState:
478 			caption = B_TRANSLATE("Preparing to delete items" B_UTF8_ELLIPSIS);
479 			id = R_TrashStatusBitmap;
480 			break;
481 
482 		case kRestoreFromTrashState:
483 			caption = B_TRANSLATE("Preparing to restore items" B_UTF8_ELLIPSIS);
484 			break;
485 
486 		default:
487 			TRESPASS();
488 			break;
489 	}
490 
491 	if (caption.Length() != 0) {
492 		fStatusBar = new BStatusBar(rect, "StatusBar", caption.String());
493 		fStatusBar->SetBarHeight(12);
494 		float width, height;
495 		fStatusBar->GetPreferredSize(&width, &height);
496 		fStatusBar->ResizeTo(fStatusBar->Frame().Width(), height);
497 		AddChild(fStatusBar);
498 
499 		// Figure out how much room we need to display the additional status
500 		// message below the bar
501 		font_height fh;
502 		GetFontHeight(&fh);
503 		BRect f = fStatusBar->Frame();
504 		// Height is 3 x the "room from the top" + bar height + room for
505 		// string.
506 		ResizeTo(Bounds().Width(), f.top + f.Height() + fh.leading + fh.ascent
507 			+ fh.descent + f.top);
508 	}
509 
510 	if (id != 0)
511 	 	GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, id, &fBitmap);
512 
513 	rect = Bounds();
514 	rect.left = rect.right - buttonWidth * 2 - 7;
515 	rect.right = rect.left + buttonWidth;
516 	rect.top = floorf((rect.top + rect.bottom) / 2 + 0.5) - buttonHeight / 2;
517 	rect.bottom = rect.top + buttonHeight;
518 
519 	fPauseButton = new TCustomButton(rect, kPauseButton);
520 	fPauseButton->ResizeTo(buttonWidth, buttonHeight);
521 	AddChild(fPauseButton);
522 
523 	rect.OffsetBy(buttonWidth + 2, 0);
524 	fStopButton = new TCustomButton(rect, kStopButton);
525 	fStopButton->ResizeTo(buttonWidth, buttonHeight);
526 	AddChild(fStopButton);
527 }
528 
529 
530 BStatusView::~BStatusView()
531 {
532 	delete fBitmap;
533 }
534 
535 
536 void
537 BStatusView::Init()
538 {
539 	fDestDir = "";
540 	fCurItem = 0;
541 	fPendingStatusString[0] = '\0';
542 	fWasCanceled = false;
543 	fIsPaused = false;
544 	fLastUpdateTime = 0;
545 	fBytesPerSecond = 0.0;
546 	for (size_t i = 0; i < kBytesPerSecondSlots; i++)
547 		fBytesPerSecondSlot[i] = 0.0;
548 	fCurrentBytesPerSecondSlot = 0;
549 	fItemSize = 0;
550 	fSizeProcessed = 0;
551 	fLastSpeedReferenceSize = 0;
552 	fEstimatedFinishReferenceSize = 0;
553 
554 	fProcessStartTime = fLastSpeedReferenceTime = fEstimatedFinishReferenceTime
555 		= system_time();
556 }
557 
558 
559 void
560 BStatusView::InitStatus(int32 totalItems, off_t totalSize,
561 	const entry_ref* destDir, bool showCount)
562 {
563 	Init();
564 	fTotalSize = totalSize;
565 	fShowCount = showCount;
566 
567 	BEntry entry;
568 	char name[B_FILE_NAME_LENGTH];
569 	if (destDir && (entry.SetTo(destDir) == B_OK)) {
570 		entry.GetName(name);
571 		fDestDir = name;
572 	}
573 
574 	BString buffer;
575 	if (totalItems > 0) {
576 		char totalStr[32];
577 		buffer.SetTo(B_TRANSLATE("of %items"));
578 		snprintf(totalStr, sizeof(totalStr), "%ld", totalItems);
579 		buffer.ReplaceFirst("%items", totalStr);
580 	}
581 
582 	switch (fType) {
583 		case kCopyState:
584 			fStatusBar->Reset(B_TRANSLATE("Copying: "),	buffer.String());
585 			break;
586 
587 		case kCreateLinkState:
588 			fStatusBar->Reset(B_TRANSLATE("Creating links: "), buffer.String());
589 			break;
590 
591 		case kMoveState:
592 			fStatusBar->Reset(B_TRANSLATE("Moving: "), buffer.String());
593 			break;
594 
595 		case kTrashState:
596 			fStatusBar->Reset(B_TRANSLATE("Emptying Trash" B_UTF8_ELLIPSIS " "),
597 				buffer.String());
598 			break;
599 
600 		case kDeleteState:
601 			fStatusBar->Reset(B_TRANSLATE("Deleting: "), buffer.String());
602 			break;
603 
604 		case kRestoreFromTrashState:
605 			fStatusBar->Reset(B_TRANSLATE("Restoring: "), buffer.String());
606 			break;
607 
608 		default:
609 			break;
610 	}
611 
612 	fStatusBar->SetMaxValue(1);
613 		// SetMaxValue has to be here because Reset changes it to 100
614 	Invalidate();
615 }
616 
617 
618 void
619 BStatusView::Draw(BRect updateRect)
620 {
621 	if (fBitmap) {
622 		BPoint location;
623 		location.x = (fStatusBar->Frame().left - fBitmap->Bounds().Width()) / 2;
624 		location.y = (Bounds().Height()- fBitmap->Bounds().Height()) / 2;
625 		DrawBitmap(fBitmap, location);
626 	}
627 
628 	BRect bounds(Bounds());
629 	be_control_look->DrawRaisedBorder(this, bounds, updateRect, ViewColor());
630 
631 	SetHighColor(0, 0, 0);
632 
633 	BPoint tp = fStatusBar->Frame().LeftBottom();
634 	font_height fh;
635 	GetFontHeight(&fh);
636 	tp.y += ceilf(fh.leading) + ceilf(fh.ascent);
637 	if (IsPaused()) {
638 		DrawString(B_TRANSLATE("Paused: click to resume or stop"), tp);
639 		return;
640 	}
641 
642 	BFont font;
643 	GetFont(&font);
644 	float normalFontSize = font.Size();
645 	float smallFontSize = max_c(normalFontSize * 0.8f, 8.0f);
646 	float availableSpace = fStatusBar->Frame().Width();
647 	availableSpace -= be_control_look->DefaultLabelSpacing();
648 		// subtract to provide some room between our two strings
649 
650 	float destinationStringWidth = 0.f;
651 	BString destinationString(_DestinationString(&destinationStringWidth));
652 	availableSpace -= destinationStringWidth;
653 
654 	float statusStringWidth = 0.f;
655 	BString statusString(_StatusString(availableSpace, smallFontSize,
656 		&statusStringWidth));
657 
658 	if (statusStringWidth > availableSpace) {
659 		TruncateString(&destinationString, B_TRUNCATE_MIDDLE,
660 			availableSpace + destinationStringWidth - statusStringWidth);
661 	}
662 
663 	BPoint textPoint = fStatusBar->Frame().LeftBottom();
664 	textPoint.y += ceilf(fh.leading) + ceilf(fh.ascent);
665 
666 	if (destinationStringWidth > 0) {
667 		DrawString(destinationString.String(), textPoint);
668 	}
669 
670 	SetHighColor(tint_color(LowColor(), B_DARKEN_4_TINT));
671 	font.SetSize(smallFontSize);
672 	SetFont(&font, B_FONT_SIZE);
673 
674 	textPoint.x = fStatusBar->Frame().right - statusStringWidth;
675 	DrawString(statusString.String(), textPoint);
676 
677 	font.SetSize(normalFontSize);
678 	SetFont(&font, B_FONT_SIZE);
679 }
680 
681 
682 BString
683 BStatusView::_DestinationString(float* _width)
684 {
685 	if (fDestDir.Length() > 0) {
686 		BString buffer(B_TRANSLATE("To: %dir"));
687 		buffer.ReplaceFirst("%dir", fDestDir);
688 
689 		*_width = ceilf(StringWidth(buffer.String()));
690 		return buffer;
691 	} else {
692 		*_width = 0;
693 		return BString();
694 	}
695 }
696 
697 
698 BString
699 BStatusView::_StatusString(float availableSpace, float fontSize, float* _width)
700 {
701 	BFont font;
702 	GetFont(&font);
703 	float oldSize = font.Size();
704 	font.SetSize(fontSize);
705 	SetFont(&font, B_FONT_SIZE);
706 
707 	BString status;
708 	if (sShowSpeed) {
709 		status = _SpeedStatusString(availableSpace, _width);
710 	} else
711 		status = _TimeStatusString(availableSpace, _width);
712 
713 	font.SetSize(oldSize);
714 	SetFont(&font, B_FONT_SIZE);
715 	return status;
716 }
717 
718 
719 BString
720 BStatusView::_SpeedStatusString(float availableSpace, float* _width)
721 {
722 	BString string(_FullSpeedString());
723 	*_width = StringWidth(string.String());
724 	if (*_width > availableSpace) {
725 		string.SetTo(_ShortSpeedString());
726 		*_width = StringWidth(string.String());
727 	}
728 	*_width = ceilf(*_width);
729 	return string;
730 }
731 
732 
733 BString
734 BStatusView::_FullSpeedString()
735 {
736 	BString buffer;
737 	if (fBytesPerSecond != 0.0) {
738 		char sizeBuffer[128];
739 		buffer.SetTo(B_TRANSLATE(
740 			"(%SizeProcessed of %TotalSize, %BytesPerSecond/s)"));
741 		buffer.ReplaceFirst("%SizeProcessed",
742 			string_for_size((double)fSizeProcessed, sizeBuffer,
743 			sizeof(sizeBuffer)));
744 		buffer.ReplaceFirst("%TotalSize",
745 			string_for_size((double)fTotalSize, sizeBuffer,
746 			sizeof(sizeBuffer)));
747 		buffer.ReplaceFirst("%BytesPerSecond",
748 			string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer)));
749 	}
750 	return buffer;
751 }
752 
753 
754 BString
755 BStatusView::_ShortSpeedString()
756 {
757 	BString buffer;
758 	if (fBytesPerSecond != 0.0) {
759 		char sizeBuffer[128];
760 		buffer << B_TRANSLATE("%BytesPerSecond/s");
761 		buffer.ReplaceFirst("%BytesPerSecond",
762 			string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer)));
763 	}
764 	return buffer;
765 }
766 
767 
768 BString
769 BStatusView::_TimeStatusString(float availableSpace, float* _width)
770 {
771 	double totalBytesPerSecond = (double)(fSizeProcessed
772 			- fEstimatedFinishReferenceSize)
773 		* 1000000LL / (system_time() - fEstimatedFinishReferenceTime);
774 	double secondsRemaining = (fTotalSize - fSizeProcessed)
775 		/ totalBytesPerSecond;
776 	time_t now = (time_t)real_time_clock();
777 	time_t finishTime = (time_t)(now + secondsRemaining);
778 
779 	char timeText[32];
780 	const BLocale* locale = BLocale::Default();
781 	if (finishTime - now > kSecondsPerDay) {
782 		locale->FormatDateTime(timeText, sizeof(timeText), finishTime,
783 			B_MEDIUM_DATE_FORMAT, B_MEDIUM_TIME_FORMAT);
784 	} else {
785 		locale->FormatTime(timeText, sizeof(timeText), finishTime,
786 			B_MEDIUM_TIME_FORMAT);
787 	}
788 
789 	BString string(_FullTimeRemainingString(now, finishTime, timeText));
790 	*_width = StringWidth(string.String());
791 	if (*_width > availableSpace) {
792 		string.SetTo(_ShortTimeRemainingString(timeText));
793 		*_width = StringWidth(string.String());
794 	}
795 
796 	return string;
797 }
798 
799 
800 BString
801 BStatusView::_ShortTimeRemainingString(const char* timeText)
802 {
803 	BString buffer;
804 
805 	// complete string too wide, try with shorter version
806 	buffer.SetTo(B_TRANSLATE("(Finish: %time)"));
807 	buffer.ReplaceFirst("%time", timeText);
808 
809 	return buffer;
810 }
811 
812 
813 BString
814 BStatusView::_FullTimeRemainingString(time_t now, time_t finishTime,
815 	const char* timeText)
816 {
817 	BDurationFormat formatter;
818 	BString buffer;
819 	BString finishStr;
820 	if (finishTime - now > 60 * 60) {
821 		buffer.SetTo(B_TRANSLATE("(Finish: %time - Over %finishtime left)"));
822 		formatter.Format(now * 1000000LL, finishTime * 1000000LL, &finishStr);
823 	} else {
824 		buffer.SetTo(B_TRANSLATE("(Finish: %time - %finishtime left)"));
825 		formatter.Format(now * 1000000LL, finishTime * 1000000LL, &finishStr);
826 	}
827 
828 	buffer.ReplaceFirst("%time", timeText);
829 	buffer.ReplaceFirst("%finishtime", finishStr);
830 
831 	return buffer;
832 }
833 
834 
835 void
836 BStatusView::AttachedToWindow()
837 {
838 	fPauseButton->SetTarget(this);
839 	fStopButton->SetTarget(this);
840 }
841 
842 
843 void
844 BStatusView::MessageReceived(BMessage *message)
845 {
846 	switch (message->what) {
847 		case kPauseButton:
848 			fIsPaused = !fIsPaused;
849 			fPauseButton->SetValue(fIsPaused ? B_CONTROL_ON : B_CONTROL_OFF);
850 			if (fBytesPerSecond != 0.0) {
851 				fBytesPerSecond = 0.0;
852 				for (size_t i = 0; i < kBytesPerSecondSlots; i++)
853 					fBytesPerSecondSlot[i] = 0.0;
854 				Invalidate();
855 			}
856 			if (!fIsPaused) {
857 				fEstimatedFinishReferenceTime = system_time();
858 				fEstimatedFinishReferenceSize = fSizeProcessed;
859 
860 				// force window update
861 				Invalidate();
862 
863 				// let 'er rip
864 				resume_thread(Thread());
865 			}
866 			break;
867 
868 		case kStopButton:
869 			fWasCanceled = true;
870 			if (fIsPaused) {
871 				// resume so that the copy loop gets a chance to finish up
872 				fIsPaused = false;
873 
874 				// force window update
875 				Invalidate();
876 
877 				// let 'er rip
878 				resume_thread(Thread());
879 			}
880 			break;
881 
882 		default:
883 			_inherited::MessageReceived(message);
884 			break;
885 	}
886 }
887 
888 
889 void
890 BStatusView::UpdateStatus(const char *curItem, off_t itemSize, bool optional)
891 {
892 	if (!fShowCount) {
893 		fStatusBar->Update((float)fItemSize / fTotalSize);
894 		fItemSize = 0;
895 		return;
896 	}
897 
898 	if (curItem != NULL)
899 		fCurItem++;
900 
901 	fItemSize += itemSize;
902 	fSizeProcessed += itemSize;
903 
904 	bigtime_t currentTime = system_time();
905 	if (!optional || ((currentTime - fLastUpdateTime) > kMaxUpdateInterval)) {
906 		if (curItem != NULL || fPendingStatusString[0]) {
907 			// forced update or past update time
908 
909 			BString buffer;
910 			buffer <<  fCurItem << " ";
911 
912 			// if we don't have curItem, take the one from the stash
913 			const char *statusItem = curItem != NULL
914 				? curItem : fPendingStatusString;
915 
916 			fStatusBar->Update((float)fItemSize / fTotalSize, statusItem,
917 				buffer.String());
918 
919 			// we already displayed this item, clear the stash
920 			fPendingStatusString[0] =  '\0';
921 
922 			fLastUpdateTime = currentTime;
923 		} else {
924 			// don't have a file to show, just update the bar
925 			fStatusBar->Update((float)fItemSize / fTotalSize);
926 		}
927 
928 		if (currentTime
929 				>= fLastSpeedReferenceTime + kSpeedReferenceInterval) {
930 			// update current speed every kSpeedReferenceInterval
931 			fCurrentBytesPerSecondSlot
932 				= (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots;
933 			fBytesPerSecondSlot[fCurrentBytesPerSecondSlot]
934 				= (double)(fSizeProcessed - fLastSpeedReferenceSize)
935 					* 1000000LL / (currentTime - fLastSpeedReferenceTime);
936 			fLastSpeedReferenceSize = fSizeProcessed;
937 			fLastSpeedReferenceTime = currentTime;
938 			fBytesPerSecond = 0.0;
939 			size_t count = 0;
940 			for (size_t i = 0; i < kBytesPerSecondSlots; i++) {
941 				if (fBytesPerSecondSlot[i] != 0.0) {
942 					fBytesPerSecond += fBytesPerSecondSlot[i];
943 					count++;
944 				}
945 			}
946 			if (count > 0)
947 				fBytesPerSecond /= count;
948 			Invalidate();
949 		}
950 
951 		fItemSize = 0;
952 	} else if (curItem != NULL) {
953 		// stash away the name of the item we are currently processing
954 		// so we can show it when the time comes
955 		strncpy(fPendingStatusString, curItem, 127);
956 		fPendingStatusString[127] = '0';
957 	}
958 }
959 
960 
961 void
962 BStatusView::SetWasCanceled()
963 {
964 	fWasCanceled = true;
965 }
966 
967