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