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