xref: /haiku/src/kits/tracker/StatusWindow.cpp (revision 7d48219b470e22b5147f0ae9adc8ee2049c981d3)
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_TRANSLATION_CONTEXT
190 #define B_TRANSLATION_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, 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 	if (fDestDir.Length() > 0) {
685 		BString buffer(B_TRANSLATE("To: %dir"));
686 		buffer.ReplaceFirst("%dir", fDestDir);
687 
688 		*_width = ceilf(StringWidth(buffer.String()));
689 		return buffer;
690 	} else {
691 		*_width = 0;
692 		return BString();
693 	}
694 }
695 
696 
697 BString
698 BStatusView::_StatusString(float availableSpace, float fontSize, float* _width)
699 {
700 	BFont font;
701 	GetFont(&font);
702 	float oldSize = font.Size();
703 	font.SetSize(fontSize);
704 	SetFont(&font, B_FONT_SIZE);
705 
706 	BString status;
707 	if (sShowSpeed) {
708 		status = _SpeedStatusString(availableSpace, _width);
709 	} else
710 		status = _TimeStatusString(availableSpace, _width);
711 
712 	font.SetSize(oldSize);
713 	SetFont(&font, B_FONT_SIZE);
714 	return status;
715 }
716 
717 
718 BString
719 BStatusView::_SpeedStatusString(float availableSpace, float* _width)
720 {
721 	BString string(_FullSpeedString());
722 	*_width = StringWidth(string.String());
723 	if (*_width > availableSpace) {
724 		string.SetTo(_ShortSpeedString());
725 		*_width = StringWidth(string.String());
726 	}
727 	*_width = ceilf(*_width);
728 	return string;
729 }
730 
731 
732 BString
733 BStatusView::_FullSpeedString()
734 {
735 	BString buffer;
736 	if (fBytesPerSecond != 0.0) {
737 		char sizeBuffer[128];
738 		buffer.SetTo(B_TRANSLATE(
739 			"%SizeProcessed of %TotalSize, %BytesPerSecond/s"));
740 		buffer.ReplaceFirst("%SizeProcessed",
741 			string_for_size((double)fSizeProcessed, sizeBuffer,
742 			sizeof(sizeBuffer)));
743 		buffer.ReplaceFirst("%TotalSize",
744 			string_for_size((double)fTotalSize, sizeBuffer,
745 			sizeof(sizeBuffer)));
746 		buffer.ReplaceFirst("%BytesPerSecond",
747 			string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer)));
748 	}
749 	return buffer;
750 }
751 
752 
753 BString
754 BStatusView::_ShortSpeedString()
755 {
756 	BString buffer;
757 	if (fBytesPerSecond != 0.0) {
758 		char sizeBuffer[128];
759 		buffer << B_TRANSLATE("%BytesPerSecond/s");
760 		buffer.ReplaceFirst("%BytesPerSecond",
761 			string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer)));
762 	}
763 	return buffer;
764 }
765 
766 
767 BString
768 BStatusView::_TimeStatusString(float availableSpace, float* _width)
769 {
770 	double totalBytesPerSecond = (double)(fSizeProcessed
771 			- fEstimatedFinishReferenceSize)
772 		* 1000000LL / (system_time() - fEstimatedFinishReferenceTime);
773 	double secondsRemaining = (fTotalSize - fSizeProcessed)
774 		/ totalBytesPerSecond;
775 	time_t now = (time_t)real_time_clock();
776 	time_t finishTime = (time_t)(now + secondsRemaining);
777 
778 	char timeText[32];
779 	const BLocale* locale = BLocale::Default();
780 	if (finishTime - now > kSecondsPerDay) {
781 		locale->FormatDateTime(timeText, sizeof(timeText), finishTime,
782 			B_MEDIUM_DATE_FORMAT, B_MEDIUM_TIME_FORMAT);
783 	} else {
784 		locale->FormatTime(timeText, sizeof(timeText), finishTime,
785 			B_MEDIUM_TIME_FORMAT);
786 	}
787 
788 	BString string(_FullTimeRemainingString(now, finishTime, timeText));
789 	float width = StringWidth(string.String());
790 	if (width > availableSpace) {
791 		string.SetTo(_ShortTimeRemainingString(timeText));
792 		width = StringWidth(string.String());
793 	}
794 
795 	if (_width != NULL)
796 		*_width = width;
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 now, time_t finishTime,
816 	const char* timeText)
817 {
818 	BDurationFormat formatter;
819 	BString buffer;
820 	BString finishStr;
821 	if (finishTime - now > 60 * 60) {
822 		buffer.SetTo(B_TRANSLATE("Finish: %time - Over %finishtime left"));
823 		formatter.Format(now * 1000000LL, finishTime * 1000000LL, &finishStr);
824 	} else {
825 		buffer.SetTo(B_TRANSLATE("Finish: %time - %finishtime left"));
826 		formatter.Format(now * 1000000LL, finishTime * 1000000LL, &finishStr);
827 	}
828 
829 	buffer.ReplaceFirst("%time", timeText);
830 	buffer.ReplaceFirst("%finishtime", finishStr);
831 
832 	return buffer;
833 }
834 
835 
836 void
837 BStatusView::AttachedToWindow()
838 {
839 	fPauseButton->SetTarget(this);
840 	fStopButton->SetTarget(this);
841 }
842 
843 
844 void
845 BStatusView::MessageReceived(BMessage *message)
846 {
847 	switch (message->what) {
848 		case kPauseButton:
849 			fIsPaused = !fIsPaused;
850 			fPauseButton->SetValue(fIsPaused ? B_CONTROL_ON : B_CONTROL_OFF);
851 			if (fBytesPerSecond != 0.0) {
852 				fBytesPerSecond = 0.0;
853 				for (size_t i = 0; i < kBytesPerSecondSlots; i++)
854 					fBytesPerSecondSlot[i] = 0.0;
855 				Invalidate();
856 			}
857 			if (!fIsPaused) {
858 				fEstimatedFinishReferenceTime = system_time();
859 				fEstimatedFinishReferenceSize = fSizeProcessed;
860 
861 				// force window update
862 				Invalidate();
863 
864 				// let 'er rip
865 				resume_thread(Thread());
866 			}
867 			break;
868 
869 		case kStopButton:
870 			fWasCanceled = true;
871 			if (fIsPaused) {
872 				// resume so that the copy loop gets a chance to finish up
873 				fIsPaused = false;
874 
875 				// force window update
876 				Invalidate();
877 
878 				// let 'er rip
879 				resume_thread(Thread());
880 			}
881 			break;
882 
883 		default:
884 			_inherited::MessageReceived(message);
885 			break;
886 	}
887 }
888 
889 
890 void
891 BStatusView::UpdateStatus(const char *curItem, off_t itemSize, bool optional)
892 {
893 	if (!fShowCount) {
894 		fStatusBar->Update((float)fItemSize / fTotalSize);
895 		fItemSize = 0;
896 		return;
897 	}
898 
899 	if (curItem != NULL)
900 		fCurItem++;
901 
902 	fItemSize += itemSize;
903 	fSizeProcessed += itemSize;
904 
905 	bigtime_t currentTime = system_time();
906 	if (!optional || ((currentTime - fLastUpdateTime) > kMaxUpdateInterval)) {
907 		if (curItem != NULL || fPendingStatusString[0]) {
908 			// forced update or past update time
909 
910 			BString buffer;
911 			buffer <<  fCurItem << " ";
912 
913 			// if we don't have curItem, take the one from the stash
914 			const char *statusItem = curItem != NULL
915 				? curItem : fPendingStatusString;
916 
917 			fStatusBar->Update((float)fItemSize / fTotalSize, statusItem,
918 				buffer.String());
919 
920 			// we already displayed this item, clear the stash
921 			fPendingStatusString[0] =  '\0';
922 
923 			fLastUpdateTime = currentTime;
924 		} else {
925 			// don't have a file to show, just update the bar
926 			fStatusBar->Update((float)fItemSize / fTotalSize);
927 		}
928 
929 		if (currentTime
930 				>= fLastSpeedReferenceTime + kSpeedReferenceInterval) {
931 			// update current speed every kSpeedReferenceInterval
932 			fCurrentBytesPerSecondSlot
933 				= (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots;
934 			fBytesPerSecondSlot[fCurrentBytesPerSecondSlot]
935 				= (double)(fSizeProcessed - fLastSpeedReferenceSize)
936 					* 1000000LL / (currentTime - fLastSpeedReferenceTime);
937 			fLastSpeedReferenceSize = fSizeProcessed;
938 			fLastSpeedReferenceTime = currentTime;
939 			fBytesPerSecond = 0.0;
940 			size_t count = 0;
941 			for (size_t i = 0; i < kBytesPerSecondSlots; i++) {
942 				if (fBytesPerSecondSlot[i] != 0.0) {
943 					fBytesPerSecond += fBytesPerSecondSlot[i];
944 					count++;
945 				}
946 			}
947 			if (count > 0)
948 				fBytesPerSecond /= count;
949 
950 			BString toolTip = _TimeStatusString(1024.f, NULL);
951 			toolTip << "\n" << _FullSpeedString();
952 			SetToolTip(toolTip.String());
953 
954 			Invalidate();
955 		}
956 
957 		fItemSize = 0;
958 	} else if (curItem != NULL) {
959 		// stash away the name of the item we are currently processing
960 		// so we can show it when the time comes
961 		strncpy(fPendingStatusString, curItem, 127);
962 		fPendingStatusString[127] = '0';
963 	} else
964 		SetToolTip((const char*)NULL);
965 }
966 
967 
968 void
969 BStatusView::SetWasCanceled()
970 {
971 	fWasCanceled = true;
972 }
973 
974