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