xref: /haiku/src/kits/tracker/StatusWindow.cpp (revision 83b1a68c52ba3e0e8796282759f694b7fdddf06d)
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 		SetViewColor(ui_color(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 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
448 	SetLowColor(ViewColor());
449 	SetHighColor(20, 20, 20);
450 	SetDrawingMode(B_OP_OVER);
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_CopyStatusBitmap;
469 			break;
470 
471 		case kMoveState:
472 			caption = B_TRANSLATE("Preparing to move items" B_UTF8_ELLIPSIS);
473 			id = R_MoveStatusBitmap;
474 			break;
475 
476 		case kCreateLinkState:
477 			caption = B_TRANSLATE("Preparing to create links"
478 				B_UTF8_ELLIPSIS);
479 			id = R_MoveStatusBitmap;
480 			break;
481 
482 		case kTrashState:
483 			caption = B_TRANSLATE("Preparing to empty Trash" B_UTF8_ELLIPSIS);
484 			id = R_TrashStatusBitmap;
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_TrashStatusBitmap;
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 		GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, id,
529 			&fBitmap);
530 	}
531 
532 	rect = Bounds();
533 	rect.left = rect.right - buttonWidth * 2 - 7;
534 	rect.right = rect.left + buttonWidth;
535 	rect.top = floorf((rect.top + rect.bottom) / 2 + 0.5) - buttonHeight / 2;
536 	rect.bottom = rect.top + buttonHeight;
537 
538 	fPauseButton = new TCustomButton(rect, kPauseButton);
539 	fPauseButton->ResizeTo(buttonWidth, buttonHeight);
540 	AddChild(fPauseButton);
541 
542 	rect.OffsetBy(buttonWidth + 2, 0);
543 	fStopButton = new TCustomButton(rect, kStopButton);
544 	fStopButton->ResizeTo(buttonWidth, buttonHeight);
545 	AddChild(fStopButton);
546 }
547 
548 
549 BStatusView::~BStatusView()
550 {
551 	delete fBitmap;
552 }
553 
554 
555 void
556 BStatusView::Init()
557 {
558 	fTotalSize = fItemSize = fSizeProcessed = fLastSpeedReferenceSize
559 		= fEstimatedFinishReferenceSize = 0;
560 	fCurItem = 0;
561 	fLastUpdateTime = fLastSpeedReferenceTime = fProcessStartTime
562 		= fLastSpeedUpdateTime = fEstimatedFinishReferenceTime
563 		= system_time();
564 	fCurrentBytesPerSecondSlot = 0;
565 	for (size_t i = 0; i < kBytesPerSecondSlots; i++)
566 		fBytesPerSecondSlot[i] = 0.0;
567 
568 	fBytesPerSecond = 0.0;
569 	fShowCount = fWasCanceled = fIsPaused = false;
570 	fDestDir.SetTo("");
571 	fPendingStatusString[0] = '\0';
572 }
573 
574 
575 void
576 BStatusView::InitStatus(int32 totalItems, off_t totalSize,
577 	const entry_ref* destDir, bool showCount)
578 {
579 	Init();
580 	fTotalSize = totalSize;
581 	fShowCount = showCount;
582 
583 	BEntry entry;
584 	char name[B_FILE_NAME_LENGTH];
585 	if (destDir != NULL && entry.SetTo(destDir) == B_OK) {
586 		entry.GetName(name);
587 		fDestDir.SetTo(name);
588 	}
589 
590 	BString buffer;
591 	if (totalItems > 0) {
592 		char totalStr[32];
593 		buffer.SetTo(B_TRANSLATE("of %items"));
594 		snprintf(totalStr, sizeof(totalStr), "%" B_PRId32, totalItems);
595 		buffer.ReplaceFirst("%items", totalStr);
596 	}
597 
598 	switch (fType) {
599 		case kCopyState:
600 			fStatusBar->Reset(B_TRANSLATE("Copying: "), buffer.String());
601 			break;
602 
603 		case kCreateLinkState:
604 			fStatusBar->Reset(B_TRANSLATE("Creating links: "),
605 				buffer.String());
606 			break;
607 
608 		case kMoveState:
609 			fStatusBar->Reset(B_TRANSLATE("Moving: "), buffer.String());
610 			break;
611 
612 		case kTrashState:
613 			fStatusBar->Reset(
614 				B_TRANSLATE("Emptying Trash" B_UTF8_ELLIPSIS " "),
615 				buffer.String());
616 			break;
617 
618 		case kDeleteState:
619 			fStatusBar->Reset(B_TRANSLATE("Deleting: "), buffer.String());
620 			break;
621 
622 		case kRestoreFromTrashState:
623 			fStatusBar->Reset(B_TRANSLATE("Restoring: "), buffer.String());
624 			break;
625 	}
626 
627 	fStatusBar->SetMaxValue(1);
628 		// SetMaxValue has to be here because Reset changes it to 100
629 	Invalidate();
630 }
631 
632 
633 void
634 BStatusView::Draw(BRect updateRect)
635 {
636 	if (fBitmap != NULL) {
637 		BPoint location;
638 		location.x = (fStatusBar->Frame().left
639 			- fBitmap->Bounds().Width()) / 2;
640 		location.y = (Bounds().Height()- fBitmap->Bounds().Height()) / 2;
641 		DrawBitmap(fBitmap, location);
642 	}
643 
644 	BRect bounds(Bounds());
645 	be_control_look->DrawRaisedBorder(this, bounds, updateRect, ViewColor());
646 
647 	SetHighColor(0, 0, 0);
648 
649 	BPoint tp = fStatusBar->Frame().LeftBottom();
650 	font_height fh;
651 	GetFontHeight(&fh);
652 	tp.y += ceilf(fh.leading) + ceilf(fh.ascent);
653 	if (IsPaused()) {
654 		DrawString(B_TRANSLATE("Paused: click to resume or stop"), tp);
655 		return;
656 	}
657 
658 	BFont font;
659 	GetFont(&font);
660 	float normalFontSize = font.Size();
661 	float smallFontSize = max_c(normalFontSize * 0.8f, 8.0f);
662 	float availableSpace = fStatusBar->Frame().Width();
663 	availableSpace -= be_control_look->DefaultLabelSpacing();
664 		// subtract to provide some room between our two strings
665 
666 	float destinationStringWidth = 0.f;
667 	BString destinationString(_DestinationString(&destinationStringWidth));
668 	availableSpace -= destinationStringWidth;
669 
670 	float statusStringWidth = 0.f;
671 	BString statusString(_StatusString(availableSpace, smallFontSize,
672 		&statusStringWidth));
673 
674 	if (statusStringWidth > availableSpace) {
675 		TruncateString(&destinationString, B_TRUNCATE_MIDDLE,
676 			availableSpace + destinationStringWidth - statusStringWidth);
677 	}
678 
679 	BPoint textPoint = fStatusBar->Frame().LeftBottom();
680 	textPoint.y += ceilf(fh.leading) + ceilf(fh.ascent);
681 
682 	if (destinationStringWidth > 0) {
683 		DrawString(destinationString.String(), textPoint);
684 	}
685 
686 	SetHighColor(tint_color(LowColor(), B_DARKEN_4_TINT));
687 	font.SetSize(smallFontSize);
688 	SetFont(&font, B_FONT_SIZE);
689 
690 	textPoint.x = fStatusBar->Frame().right - statusStringWidth;
691 	DrawString(statusString.String(), textPoint);
692 
693 	font.SetSize(normalFontSize);
694 	SetFont(&font, B_FONT_SIZE);
695 }
696 
697 
698 BString
699 BStatusView::_DestinationString(float* _width)
700 {
701 	if (fDestDir.Length() > 0) {
702 		BString buffer(B_TRANSLATE("To: %dir"));
703 		buffer.ReplaceFirst("%dir", fDestDir);
704 
705 		*_width = ceilf(StringWidth(buffer.String()));
706 		return buffer;
707 	} else {
708 		*_width = 0;
709 		return BString();
710 	}
711 }
712 
713 
714 BString
715 BStatusView::_StatusString(float availableSpace, float fontSize,
716 	float* _width)
717 {
718 	BFont font;
719 	GetFont(&font);
720 	float oldSize = font.Size();
721 	font.SetSize(fontSize);
722 	SetFont(&font, B_FONT_SIZE);
723 
724 	BString status;
725 	if (sShowSpeed) {
726 		status = _SpeedStatusString(availableSpace, _width);
727 	} else
728 		status = _TimeStatusString(availableSpace, _width);
729 
730 	font.SetSize(oldSize);
731 	SetFont(&font, B_FONT_SIZE);
732 	return status;
733 }
734 
735 
736 BString
737 BStatusView::_SpeedStatusString(float availableSpace, float* _width)
738 {
739 	BString string(_FullSpeedString());
740 	*_width = StringWidth(string.String());
741 	if (*_width > availableSpace) {
742 		string.SetTo(_ShortSpeedString());
743 		*_width = StringWidth(string.String());
744 	}
745 	*_width = ceilf(*_width);
746 	return string;
747 }
748 
749 
750 BString
751 BStatusView::_FullSpeedString()
752 {
753 	BString buffer;
754 	if (fBytesPerSecond != 0.0) {
755 		char sizeBuffer[128];
756 		buffer.SetTo(B_TRANSLATE(
757 			"%SizeProcessed of %TotalSize, %BytesPerSecond/s"));
758 		buffer.ReplaceFirst("%SizeProcessed",
759 			string_for_size((double)fSizeProcessed, sizeBuffer,
760 			sizeof(sizeBuffer)));
761 		buffer.ReplaceFirst("%TotalSize",
762 			string_for_size((double)fTotalSize, sizeBuffer,
763 			sizeof(sizeBuffer)));
764 		buffer.ReplaceFirst("%BytesPerSecond",
765 			string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer)));
766 	}
767 
768 	return buffer;
769 }
770 
771 
772 BString
773 BStatusView::_ShortSpeedString()
774 {
775 	BString buffer;
776 	if (fBytesPerSecond != 0.0) {
777 		char sizeBuffer[128];
778 		buffer << B_TRANSLATE("%BytesPerSecond/s");
779 		buffer.ReplaceFirst("%BytesPerSecond",
780 			string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer)));
781 	}
782 
783 	return buffer;
784 }
785 
786 
787 BString
788 BStatusView::_TimeStatusString(float availableSpace, float* _width)
789 {
790 	double totalBytesPerSecond = (double)(fSizeProcessed
791 			- fEstimatedFinishReferenceSize)
792 		* 1000000LL / (system_time() - fEstimatedFinishReferenceTime);
793 	double secondsRemaining = (fTotalSize - fSizeProcessed)
794 		/ totalBytesPerSecond;
795 	time_t now = (time_t)real_time_clock();
796 	time_t finishTime = (time_t)(now + secondsRemaining);
797 
798 	char timeText[32];
799 	if (finishTime - now > kSecondsPerDay) {
800 		BDateTimeFormat().Format(timeText, sizeof(timeText), finishTime,
801 			B_MEDIUM_DATE_FORMAT, B_MEDIUM_TIME_FORMAT);
802 	} else {
803 		BTimeFormat().Format(timeText, sizeof(timeText), finishTime,
804 			B_MEDIUM_TIME_FORMAT);
805 	}
806 
807 	BString string(_FullTimeRemainingString(now, finishTime, timeText));
808 	float width = StringWidth(string.String());
809 	if (width > availableSpace) {
810 		string.SetTo(_ShortTimeRemainingString(timeText));
811 		width = StringWidth(string.String());
812 	}
813 
814 	if (_width != NULL)
815 		*_width = width;
816 
817 	return string;
818 }
819 
820 
821 BString
822 BStatusView::_ShortTimeRemainingString(const char* timeText)
823 {
824 	BString buffer;
825 
826 	// complete string too wide, try with shorter version
827 	buffer.SetTo(B_TRANSLATE("Finish: %time"));
828 	buffer.ReplaceFirst("%time", timeText);
829 
830 	return buffer;
831 }
832 
833 
834 BString
835 BStatusView::_FullTimeRemainingString(time_t now, time_t finishTime,
836 	const char* timeText)
837 {
838 	BDurationFormat formatter;
839 	BString buffer;
840 	BString finishStr;
841 	if (finishTime - now > 60 * 60) {
842 		buffer.SetTo(B_TRANSLATE("Finish: %time - Over %finishtime left"));
843 		formatter.Format(finishStr, now * 1000000LL, finishTime * 1000000LL);
844 	} else {
845 		buffer.SetTo(B_TRANSLATE("Finish: %time - %finishtime left"));
846 		formatter.Format(finishStr, now * 1000000LL, finishTime * 1000000LL);
847 	}
848 
849 	buffer.ReplaceFirst("%time", timeText);
850 	buffer.ReplaceFirst("%finishtime", finishStr);
851 
852 	return buffer;
853 }
854 
855 
856 void
857 BStatusView::AttachedToWindow()
858 {
859 	fPauseButton->SetTarget(this);
860 	fStopButton->SetTarget(this);
861 }
862 
863 
864 void
865 BStatusView::MessageReceived(BMessage* message)
866 {
867 	switch (message->what) {
868 		case kPauseButton:
869 			fIsPaused = !fIsPaused;
870 			fPauseButton->SetValue(fIsPaused ? B_CONTROL_ON : B_CONTROL_OFF);
871 			if (fBytesPerSecond != 0.0) {
872 				fBytesPerSecond = 0.0;
873 				for (size_t i = 0; i < kBytesPerSecondSlots; i++)
874 					fBytesPerSecondSlot[i] = 0.0;
875 				Invalidate();
876 			}
877 			if (!fIsPaused) {
878 				fEstimatedFinishReferenceTime = system_time();
879 				fEstimatedFinishReferenceSize = fSizeProcessed;
880 
881 				// force window update
882 				Invalidate();
883 
884 				// let 'er rip
885 				resume_thread(Thread());
886 			}
887 			break;
888 
889 		case kStopButton:
890 			fWasCanceled = true;
891 			if (fIsPaused) {
892 				// resume so that the copy loop gets a chance to finish up
893 				fIsPaused = false;
894 
895 				// force window update
896 				Invalidate();
897 
898 				// let 'er rip
899 				resume_thread(Thread());
900 			}
901 			break;
902 
903 		default:
904 			_inherited::MessageReceived(message);
905 			break;
906 	}
907 }
908 
909 
910 void
911 BStatusView::UpdateStatus(const char* curItem, off_t itemSize, bool optional)
912 {
913 	if (!fShowCount) {
914 		fStatusBar->Update((float)fItemSize / fTotalSize);
915 		fItemSize = 0;
916 		return;
917 	}
918 
919 	if (curItem != NULL)
920 		fCurItem++;
921 
922 	fItemSize += itemSize;
923 	fSizeProcessed += itemSize;
924 
925 	bigtime_t currentTime = system_time();
926 	if (!optional || ((currentTime - fLastUpdateTime) > kMaxUpdateInterval)) {
927 		if (curItem != NULL || fPendingStatusString[0]) {
928 			// forced update or past update time
929 
930 			BString buffer;
931 			buffer <<  fCurItem << " ";
932 
933 			// if we don't have curItem, take the one from the stash
934 			const char* statusItem = curItem != NULL
935 				? curItem : fPendingStatusString;
936 
937 			fStatusBar->Update((float)fItemSize / fTotalSize, statusItem,
938 				buffer.String());
939 
940 			// we already displayed this item, clear the stash
941 			fPendingStatusString[0] =  '\0';
942 
943 			fLastUpdateTime = currentTime;
944 		} else {
945 			// don't have a file to show, just update the bar
946 			fStatusBar->Update((float)fItemSize / fTotalSize);
947 		}
948 
949 		if (currentTime
950 				>= fLastSpeedReferenceTime + kSpeedReferenceInterval) {
951 			// update current speed every kSpeedReferenceInterval
952 			fCurrentBytesPerSecondSlot
953 				= (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots;
954 			fBytesPerSecondSlot[fCurrentBytesPerSecondSlot]
955 				= (double)(fSizeProcessed - fLastSpeedReferenceSize)
956 					* 1000000LL / (currentTime - fLastSpeedReferenceTime);
957 			fLastSpeedReferenceSize = fSizeProcessed;
958 			fLastSpeedReferenceTime = currentTime;
959 			fBytesPerSecond = 0.0;
960 			size_t count = 0;
961 			for (size_t i = 0; i < kBytesPerSecondSlots; i++) {
962 				if (fBytesPerSecondSlot[i] != 0.0) {
963 					fBytesPerSecond += fBytesPerSecondSlot[i];
964 					count++;
965 				}
966 			}
967 			if (count > 0)
968 				fBytesPerSecond /= count;
969 
970 			BString toolTip = _TimeStatusString(1024.f, NULL);
971 			toolTip << "\n" << _FullSpeedString();
972 			SetToolTip(toolTip.String());
973 
974 			Invalidate();
975 		}
976 
977 		fItemSize = 0;
978 	} else if (curItem != NULL) {
979 		// stash away the name of the item we are currently processing
980 		// so we can show it when the time comes
981 		strncpy(fPendingStatusString, curItem, 127);
982 		fPendingStatusString[127] = '0';
983 	} else
984 		SetToolTip((const char*)NULL);
985 }
986 
987 
988 void
989 BStatusView::SetWasCanceled()
990 {
991 	fWasCanceled = true;
992 }
993