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