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 #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
66 const float kDefaultStatusViewHeight = 50;
67 const bigtime_t kMaxUpdateInterval = 100000LL;
68 const bigtime_t kSpeedReferenceInterval = 2000000LL;
69 const bigtime_t kShowSpeedInterval = 8000000LL;
70 const bigtime_t kShowEstimatedFinishInterval = 4000000LL;
71 const BRect kStatusRect(200, 200, 550, 200);
72
73 static bigtime_t sLastEstimatedFinishSpeedToggleTime = -1;
74 static bool sShowSpeed = true;
75 static const time_t kSecondsPerDay = 24 * 60 * 60;
76
77
78 class TCustomButton : public BButton {
79 public:
80 TCustomButton(BRect frame, uint32 command);
81 virtual void Draw(BRect updateRect);
82 private:
83 typedef BButton _inherited;
84 };
85
86
87 class BStatusMouseFilter : public BMessageFilter {
88 public:
89 BStatusMouseFilter();
90 virtual filter_result Filter(BMessage* message, BHandler** target);
91 };
92
93
94 namespace BPrivate {
95 BStatusWindow* gStatusWindow = NULL;
96 }
97
98
99 // #pragma mark - BStatusMouseFilter
100
101
BStatusMouseFilter()102 BStatusMouseFilter::BStatusMouseFilter()
103 :
104 BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_MOUSE_DOWN)
105 {
106 }
107
108
109 filter_result
Filter(BMessage * message,BHandler ** target)110 BStatusMouseFilter::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
TCustomButton(BRect frame,uint32 what)131 TCustomButton::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
139 void
Draw(BRect updateRect)140 TCustomButton::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
173 class StatusBackgroundView : public BView {
174 public:
StatusBackgroundView(BRect frame)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
Pulse()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
BStatusWindow()203 BStatusWindow::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
~BStatusWindow()223 BStatusWindow::~BStatusWindow()
224 {
225 }
226
227
228 void
CreateStatusItem(thread_id thread,StatusWindowState type)229 BStatusWindow::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
277 void
InitStatusItem(thread_id thread,int32 totalItems,off_t totalSize,const entry_ref * destDir,bool showCount)278 BStatusWindow::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
295 void
UpdateStatus(thread_id thread,const char * curItem,off_t itemSize,bool optional)296 BStatusWindow::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
312 void
RemoveStatusItem(thread_id thread)313 BStatusWindow::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
364 bool
CheckCanceledOrPaused(thread_id thread)365 BStatusWindow::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
403 bool
AttemptToQuit()404 BStatusWindow::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
422 void
WindowActivated(bool state)423 BStatusWindow::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
BStatusView(BRect bounds,thread_id thread,StatusWindowState type)435 BStatusView::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
~BStatusView()550 BStatusView::~BStatusView()
551 {
552 delete fBitmap;
553 }
554
555
556 void
Init()557 BStatusView::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
576 void
InitStatus(int32 totalItems,off_t totalSize,const entry_ref * destDir,bool showCount)577 BStatusView::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
634 void
Draw(BRect updateRect)635 BStatusView::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 SetHighUIColor(B_PANEL_TEXT_COLOR);
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 if (LowColor().IsLight())
688 SetHighColor(tint_color(LowColor(), B_DARKEN_4_TINT));
689 else
690 SetHighColor(tint_color(LowColor(), B_LIGHTEN_2_TINT));
691
692 font.SetSize(smallFontSize);
693 SetFont(&font, B_FONT_SIZE);
694
695 textPoint.x = fStatusBar->Frame().right - statusStringWidth;
696 DrawString(statusString.String(), textPoint);
697
698 font.SetSize(normalFontSize);
699 SetFont(&font, B_FONT_SIZE);
700 }
701
702
703 BString
_DestinationString(float * _width)704 BStatusView::_DestinationString(float* _width)
705 {
706 if (fDestDir.Length() > 0) {
707 BString buffer(B_TRANSLATE("To: %dir"));
708 buffer.ReplaceFirst("%dir", fDestDir);
709
710 *_width = ceilf(StringWidth(buffer.String()));
711 return buffer;
712 } else {
713 *_width = 0;
714 return BString();
715 }
716 }
717
718
719 BString
_StatusString(float availableSpace,float fontSize,float * _width)720 BStatusView::_StatusString(float availableSpace, float fontSize,
721 float* _width)
722 {
723 BFont font;
724 GetFont(&font);
725 float oldSize = font.Size();
726 font.SetSize(fontSize);
727 SetFont(&font, B_FONT_SIZE);
728
729 BString status;
730 if (sShowSpeed) {
731 status = _SpeedStatusString(availableSpace, _width);
732 } else
733 status = _TimeStatusString(availableSpace, _width);
734
735 font.SetSize(oldSize);
736 SetFont(&font, B_FONT_SIZE);
737 return status;
738 }
739
740
741 BString
_SpeedStatusString(float availableSpace,float * _width)742 BStatusView::_SpeedStatusString(float availableSpace, float* _width)
743 {
744 BString string(_FullSpeedString());
745 *_width = StringWidth(string.String());
746 if (*_width > availableSpace) {
747 string.SetTo(_ShortSpeedString());
748 *_width = StringWidth(string.String());
749 }
750 *_width = ceilf(*_width);
751 return string;
752 }
753
754
755 BString
_FullSpeedString()756 BStatusView::_FullSpeedString()
757 {
758 BString buffer;
759 if (fBytesPerSecond != 0.0) {
760 char sizeBuffer[128];
761 buffer.SetTo(B_TRANSLATE(
762 "%SizeProcessed of %TotalSize, %BytesPerSecond/s"));
763 buffer.ReplaceFirst("%SizeProcessed",
764 string_for_size((double)fSizeProcessed, sizeBuffer,
765 sizeof(sizeBuffer)));
766 buffer.ReplaceFirst("%TotalSize",
767 string_for_size((double)fTotalSize, sizeBuffer,
768 sizeof(sizeBuffer)));
769 buffer.ReplaceFirst("%BytesPerSecond",
770 string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer)));
771 }
772
773 return buffer;
774 }
775
776
777 BString
_ShortSpeedString()778 BStatusView::_ShortSpeedString()
779 {
780 BString buffer;
781 if (fBytesPerSecond != 0.0) {
782 char sizeBuffer[128];
783 buffer << B_TRANSLATE("%BytesPerSecond/s");
784 buffer.ReplaceFirst("%BytesPerSecond",
785 string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer)));
786 }
787
788 return buffer;
789 }
790
791
792 BString
_TimeStatusString(float availableSpace,float * _width)793 BStatusView::_TimeStatusString(float availableSpace, float* _width)
794 {
795 double totalBytesPerSecond = (double)(fSizeProcessed
796 - fEstimatedFinishReferenceSize)
797 * 1000000LL / (system_time() - fEstimatedFinishReferenceTime);
798 double secondsRemaining = (fTotalSize - fSizeProcessed)
799 / totalBytesPerSecond;
800 time_t now = (time_t)real_time_clock();
801
802 BString string;
803 if (secondsRemaining < 0 || (sizeof(time_t) == 4
804 && now + secondsRemaining > INT32_MAX)) {
805 string = B_TRANSLATE("Finish: after several years");
806 } else {
807 char timeText[32];
808 time_t finishTime = (time_t)(now + secondsRemaining);
809
810 if (finishTime - now > kSecondsPerDay) {
811 BDateTimeFormat().Format(timeText, sizeof(timeText), finishTime,
812 B_MEDIUM_DATE_FORMAT, B_MEDIUM_TIME_FORMAT);
813 } else {
814 BTimeFormat().Format(timeText, sizeof(timeText), finishTime,
815 B_MEDIUM_TIME_FORMAT);
816 }
817 string = _FullTimeRemainingString(now, finishTime, timeText);
818 float width = StringWidth(string.String());
819 if (width > availableSpace) {
820 string.SetTo(_ShortTimeRemainingString(timeText));
821 }
822 }
823
824 if (_width != NULL)
825 *_width = StringWidth(string.String());
826
827 return string;
828 }
829
830
831 BString
_ShortTimeRemainingString(const char * timeText)832 BStatusView::_ShortTimeRemainingString(const char* timeText)
833 {
834 BString buffer;
835
836 // complete string too wide, try with shorter version
837 buffer.SetTo(B_TRANSLATE("Finish: %time"));
838 buffer.ReplaceFirst("%time", timeText);
839
840 return buffer;
841 }
842
843
844 BString
_FullTimeRemainingString(time_t now,time_t finishTime,const char * timeText)845 BStatusView::_FullTimeRemainingString(time_t now, time_t finishTime,
846 const char* timeText)
847 {
848 BDurationFormat formatter;
849 BString buffer;
850 BString finishStr;
851 if (finishTime - now > 60 * 60) {
852 buffer.SetTo(B_TRANSLATE("Finish: %time - Over %finishtime left"));
853 formatter.Format(finishStr, now * 1000000LL, finishTime * 1000000LL);
854 } else {
855 buffer.SetTo(B_TRANSLATE("Finish: %time - %finishtime left"));
856 formatter.Format(finishStr, now * 1000000LL, finishTime * 1000000LL);
857 }
858
859 buffer.ReplaceFirst("%time", timeText);
860 buffer.ReplaceFirst("%finishtime", finishStr);
861
862 return buffer;
863 }
864
865
866 void
AttachedToWindow()867 BStatusView::AttachedToWindow()
868 {
869 fPauseButton->SetTarget(this);
870 fStopButton->SetTarget(this);
871 }
872
873
874 void
MessageReceived(BMessage * message)875 BStatusView::MessageReceived(BMessage* message)
876 {
877 switch (message->what) {
878 case kPauseButton:
879 fIsPaused = !fIsPaused;
880 fPauseButton->SetValue(fIsPaused ? B_CONTROL_ON : B_CONTROL_OFF);
881 if (fBytesPerSecond != 0.0) {
882 fBytesPerSecond = 0.0;
883 for (size_t i = 0; i < kBytesPerSecondSlots; i++)
884 fBytesPerSecondSlot[i] = 0.0;
885 Invalidate();
886 }
887 if (!fIsPaused) {
888 fEstimatedFinishReferenceTime = system_time();
889 fEstimatedFinishReferenceSize = fSizeProcessed;
890
891 // force window update
892 Invalidate();
893
894 // let 'er rip
895 resume_thread(Thread());
896 }
897 break;
898
899 case kStopButton:
900 fWasCanceled = true;
901 if (fIsPaused) {
902 // resume so that the copy loop gets a chance to finish up
903 fIsPaused = false;
904
905 // force window update
906 Invalidate();
907
908 // let 'er rip
909 resume_thread(Thread());
910 }
911 break;
912
913 default:
914 _inherited::MessageReceived(message);
915 break;
916 }
917 }
918
919
920 void
UpdateStatus(const char * curItem,off_t itemSize,bool optional)921 BStatusView::UpdateStatus(const char* curItem, off_t itemSize, bool optional)
922 {
923 if (!fShowCount) {
924 fStatusBar->Update((float)fItemSize / fTotalSize);
925 fItemSize = 0;
926 return;
927 }
928
929 if (curItem != NULL)
930 fCurItem++;
931
932 fItemSize += itemSize;
933 fSizeProcessed += itemSize;
934
935 bigtime_t currentTime = system_time();
936 if (!optional || ((currentTime - fLastUpdateTime) > kMaxUpdateInterval)) {
937 if (curItem != NULL || fPendingStatusString[0]) {
938 // forced update or past update time
939
940 BString buffer;
941 buffer << fCurItem << " ";
942
943 // if we don't have curItem, take the one from the stash
944 const char* statusItem = curItem != NULL
945 ? curItem : fPendingStatusString;
946
947 fStatusBar->Update((float)fItemSize / fTotalSize, statusItem,
948 buffer.String());
949
950 // we already displayed this item, clear the stash
951 fPendingStatusString[0] = '\0';
952
953 fLastUpdateTime = currentTime;
954 } else {
955 // don't have a file to show, just update the bar
956 fStatusBar->Update((float)fItemSize / fTotalSize);
957 }
958
959 if (currentTime
960 >= fLastSpeedReferenceTime + kSpeedReferenceInterval) {
961 // update current speed every kSpeedReferenceInterval
962 fCurrentBytesPerSecondSlot
963 = (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots;
964 fBytesPerSecondSlot[fCurrentBytesPerSecondSlot]
965 = (double)(fSizeProcessed - fLastSpeedReferenceSize)
966 * 1000000LL / (currentTime - fLastSpeedReferenceTime);
967 fLastSpeedReferenceSize = fSizeProcessed;
968 fLastSpeedReferenceTime = currentTime;
969 fBytesPerSecond = 0.0;
970 size_t count = 0;
971 for (size_t i = 0; i < kBytesPerSecondSlots; i++) {
972 if (fBytesPerSecondSlot[i] != 0.0) {
973 fBytesPerSecond += fBytesPerSecondSlot[i];
974 count++;
975 }
976 }
977 if (count > 0)
978 fBytesPerSecond /= count;
979
980 BString toolTip = _TimeStatusString(1024.f, NULL);
981 toolTip << "\n" << _FullSpeedString();
982 SetToolTip(toolTip.String());
983
984 Invalidate();
985 }
986
987 fItemSize = 0;
988 } else if (curItem != NULL) {
989 // stash away the name of the item we are currently processing
990 // so we can show it when the time comes
991 strncpy(fPendingStatusString, curItem, 127);
992 fPendingStatusString[127] = '0';
993 } else
994 SetToolTip((const char*)NULL);
995 }
996
997
998 void
SetWasCanceled()999 BStatusView::SetWasCanceled()
1000 {
1001 fWasCanceled = true;
1002 }
1003