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