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