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