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