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