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