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_TRANSLATION_CONTEXT 190 #define B_TRANSLATION_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 319 // (in pixel 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, StatusWindowState type) 425 : 426 BView(bounds, "StatusView", B_FOLLOW_NONE, B_WILL_DRAW), 427 fType(type), 428 fBitmap(NULL), 429 fThread(thread) 430 { 431 Init(); 432 433 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 434 SetLowColor(ViewColor()); 435 SetHighColor(20, 20, 20); 436 SetDrawingMode(B_OP_OVER); 437 438 const float buttonWidth = 22; 439 const float buttonHeight = 20; 440 441 BRect rect(bounds); 442 rect.OffsetTo(B_ORIGIN); 443 rect.left += 40; 444 rect.right -= buttonWidth * 2 + 12; 445 rect.top += 6; 446 rect.bottom = rect.top + 15; 447 448 BString caption; 449 int32 id = 0; 450 451 switch (type) { 452 case kCopyState: 453 caption = B_TRANSLATE("Preparing to copy items" B_UTF8_ELLIPSIS); 454 id = R_CopyStatusBitmap; 455 break; 456 457 case kMoveState: 458 caption = B_TRANSLATE("Preparing to move items" B_UTF8_ELLIPSIS); 459 id = R_MoveStatusBitmap; 460 break; 461 462 case kCreateLinkState: 463 caption = B_TRANSLATE("Preparing to create links" 464 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" 475 B_UTF8_ELLIPSIS); 476 break; 477 478 case kDeleteState: 479 caption = B_TRANSLATE("Preparing to delete items" 480 B_UTF8_ELLIPSIS); 481 id = R_TrashStatusBitmap; 482 break; 483 484 case kRestoreFromTrashState: 485 caption = B_TRANSLATE("Preparing to restore items" 486 B_UTF8_ELLIPSIS); 487 break; 488 489 default: 490 TRESPASS(); 491 break; 492 } 493 494 if (caption.Length() != 0) { 495 fStatusBar = new BStatusBar(rect, "StatusBar", caption.String()); 496 fStatusBar->SetBarHeight(12); 497 float width, height; 498 fStatusBar->GetPreferredSize(&width, &height); 499 fStatusBar->ResizeTo(fStatusBar->Frame().Width(), height); 500 AddChild(fStatusBar); 501 502 // Figure out how much room we need to display the additional status 503 // message below the bar 504 font_height fh; 505 GetFontHeight(&fh); 506 BRect f = fStatusBar->Frame(); 507 // Height is 3 x the "room from the top" + bar height + room for 508 // string. 509 ResizeTo(Bounds().Width(), f.top + f.Height() + fh.leading + fh.ascent 510 + fh.descent + f.top); 511 } 512 513 if (id != 0) { 514 GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, id, 515 &fBitmap); 516 } 517 518 rect = Bounds(); 519 rect.left = rect.right - buttonWidth * 2 - 7; 520 rect.right = rect.left + buttonWidth; 521 rect.top = floorf((rect.top + rect.bottom) / 2 + 0.5) - buttonHeight / 2; 522 rect.bottom = rect.top + buttonHeight; 523 524 fPauseButton = new TCustomButton(rect, kPauseButton); 525 fPauseButton->ResizeTo(buttonWidth, buttonHeight); 526 AddChild(fPauseButton); 527 528 rect.OffsetBy(buttonWidth + 2, 0); 529 fStopButton = new TCustomButton(rect, kStopButton); 530 fStopButton->ResizeTo(buttonWidth, buttonHeight); 531 AddChild(fStopButton); 532 } 533 534 535 BStatusView::~BStatusView() 536 { 537 delete fBitmap; 538 } 539 540 541 void 542 BStatusView::Init() 543 { 544 fDestDir = ""; 545 fCurItem = 0; 546 fPendingStatusString[0] = '\0'; 547 fWasCanceled = false; 548 fIsPaused = false; 549 fLastUpdateTime = 0; 550 fBytesPerSecond = 0.0; 551 for (size_t i = 0; i < kBytesPerSecondSlots; i++) 552 fBytesPerSecondSlot[i] = 0.0; 553 fCurrentBytesPerSecondSlot = 0; 554 fItemSize = 0; 555 fSizeProcessed = 0; 556 fLastSpeedReferenceSize = 0; 557 fEstimatedFinishReferenceSize = 0; 558 559 fProcessStartTime = fLastSpeedReferenceTime 560 = fEstimatedFinishReferenceTime = system_time(); 561 } 562 563 564 void 565 BStatusView::InitStatus(int32 totalItems, off_t totalSize, 566 const entry_ref* destDir, bool showCount) 567 { 568 Init(); 569 fTotalSize = totalSize; 570 fShowCount = showCount; 571 572 BEntry entry; 573 char name[B_FILE_NAME_LENGTH]; 574 if (destDir && (entry.SetTo(destDir) == B_OK)) { 575 entry.GetName(name); 576 fDestDir = name; 577 } 578 579 BString buffer; 580 if (totalItems > 0) { 581 char totalStr[32]; 582 buffer.SetTo(B_TRANSLATE("of %items")); 583 snprintf(totalStr, sizeof(totalStr), "%ld", totalItems); 584 buffer.ReplaceFirst("%items", totalStr); 585 } 586 587 switch (fType) { 588 case kCopyState: 589 fStatusBar->Reset(B_TRANSLATE("Copying: "), buffer.String()); 590 break; 591 592 case kCreateLinkState: 593 fStatusBar->Reset(B_TRANSLATE("Creating links: "), 594 buffer.String()); 595 break; 596 597 case kMoveState: 598 fStatusBar->Reset(B_TRANSLATE("Moving: "), buffer.String()); 599 break; 600 601 case kTrashState: 602 fStatusBar->Reset( 603 B_TRANSLATE("Emptying Trash" B_UTF8_ELLIPSIS " "), 604 buffer.String()); 605 break; 606 607 case kDeleteState: 608 fStatusBar->Reset(B_TRANSLATE("Deleting: "), buffer.String()); 609 break; 610 611 case kRestoreFromTrashState: 612 fStatusBar->Reset(B_TRANSLATE("Restoring: "), buffer.String()); 613 break; 614 615 default: 616 break; 617 } 618 619 fStatusBar->SetMaxValue(1); 620 // SetMaxValue has to be here because Reset changes it to 100 621 Invalidate(); 622 } 623 624 625 void 626 BStatusView::Draw(BRect updateRect) 627 { 628 if (fBitmap) { 629 BPoint location; 630 location.x = (fStatusBar->Frame().left 631 - fBitmap->Bounds().Width()) / 2; 632 location.y = (Bounds().Height()- fBitmap->Bounds().Height()) / 2; 633 DrawBitmap(fBitmap, location); 634 } 635 636 BRect bounds(Bounds()); 637 be_control_look->DrawRaisedBorder(this, bounds, updateRect, ViewColor()); 638 639 SetHighColor(0, 0, 0); 640 641 BPoint tp = fStatusBar->Frame().LeftBottom(); 642 font_height fh; 643 GetFontHeight(&fh); 644 tp.y += ceilf(fh.leading) + ceilf(fh.ascent); 645 if (IsPaused()) { 646 DrawString(B_TRANSLATE("Paused: click to resume or stop"), tp); 647 return; 648 } 649 650 BFont font; 651 GetFont(&font); 652 float normalFontSize = font.Size(); 653 float smallFontSize = max_c(normalFontSize * 0.8f, 8.0f); 654 float availableSpace = fStatusBar->Frame().Width(); 655 availableSpace -= be_control_look->DefaultLabelSpacing(); 656 // subtract to provide some room between our two strings 657 658 float destinationStringWidth = 0.f; 659 BString destinationString(_DestinationString(&destinationStringWidth)); 660 availableSpace -= destinationStringWidth; 661 662 float statusStringWidth = 0.f; 663 BString statusString(_StatusString(availableSpace, smallFontSize, 664 &statusStringWidth)); 665 666 if (statusStringWidth > availableSpace) { 667 TruncateString(&destinationString, B_TRUNCATE_MIDDLE, 668 availableSpace + destinationStringWidth - statusStringWidth); 669 } 670 671 BPoint textPoint = fStatusBar->Frame().LeftBottom(); 672 textPoint.y += ceilf(fh.leading) + ceilf(fh.ascent); 673 674 if (destinationStringWidth > 0) { 675 DrawString(destinationString.String(), textPoint); 676 } 677 678 SetHighColor(tint_color(LowColor(), B_DARKEN_4_TINT)); 679 font.SetSize(smallFontSize); 680 SetFont(&font, B_FONT_SIZE); 681 682 textPoint.x = fStatusBar->Frame().right - statusStringWidth; 683 DrawString(statusString.String(), textPoint); 684 685 font.SetSize(normalFontSize); 686 SetFont(&font, B_FONT_SIZE); 687 } 688 689 690 BString 691 BStatusView::_DestinationString(float* _width) 692 { 693 if (fDestDir.Length() > 0) { 694 BString buffer(B_TRANSLATE("To: %dir")); 695 buffer.ReplaceFirst("%dir", fDestDir); 696 697 *_width = ceilf(StringWidth(buffer.String())); 698 return buffer; 699 } else { 700 *_width = 0; 701 return BString(); 702 } 703 } 704 705 706 BString 707 BStatusView::_StatusString(float availableSpace, float fontSize, 708 float* _width) 709 { 710 BFont font; 711 GetFont(&font); 712 float oldSize = font.Size(); 713 font.SetSize(fontSize); 714 SetFont(&font, B_FONT_SIZE); 715 716 BString status; 717 if (sShowSpeed) { 718 status = _SpeedStatusString(availableSpace, _width); 719 } else 720 status = _TimeStatusString(availableSpace, _width); 721 722 font.SetSize(oldSize); 723 SetFont(&font, B_FONT_SIZE); 724 return status; 725 } 726 727 728 BString 729 BStatusView::_SpeedStatusString(float availableSpace, float* _width) 730 { 731 BString string(_FullSpeedString()); 732 *_width = StringWidth(string.String()); 733 if (*_width > availableSpace) { 734 string.SetTo(_ShortSpeedString()); 735 *_width = StringWidth(string.String()); 736 } 737 *_width = ceilf(*_width); 738 return string; 739 } 740 741 742 BString 743 BStatusView::_FullSpeedString() 744 { 745 BString buffer; 746 if (fBytesPerSecond != 0.0) { 747 char sizeBuffer[128]; 748 buffer.SetTo(B_TRANSLATE( 749 "%SizeProcessed of %TotalSize, %BytesPerSecond/s")); 750 buffer.ReplaceFirst("%SizeProcessed", 751 string_for_size((double)fSizeProcessed, sizeBuffer, 752 sizeof(sizeBuffer))); 753 buffer.ReplaceFirst("%TotalSize", 754 string_for_size((double)fTotalSize, sizeBuffer, 755 sizeof(sizeBuffer))); 756 buffer.ReplaceFirst("%BytesPerSecond", 757 string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer))); 758 } 759 return buffer; 760 } 761 762 763 BString 764 BStatusView::_ShortSpeedString() 765 { 766 BString buffer; 767 if (fBytesPerSecond != 0.0) { 768 char sizeBuffer[128]; 769 buffer << B_TRANSLATE("%BytesPerSecond/s"); 770 buffer.ReplaceFirst("%BytesPerSecond", 771 string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer))); 772 } 773 return buffer; 774 } 775 776 777 BString 778 BStatusView::_TimeStatusString(float availableSpace, float* _width) 779 { 780 double totalBytesPerSecond = (double)(fSizeProcessed 781 - fEstimatedFinishReferenceSize) 782 * 1000000LL / (system_time() - fEstimatedFinishReferenceTime); 783 double secondsRemaining = (fTotalSize - fSizeProcessed) 784 / totalBytesPerSecond; 785 time_t now = (time_t)real_time_clock(); 786 time_t finishTime = (time_t)(now + secondsRemaining); 787 788 char timeText[32]; 789 const BLocale* locale = BLocale::Default(); 790 if (finishTime - now > kSecondsPerDay) { 791 locale->FormatDateTime(timeText, sizeof(timeText), finishTime, 792 B_MEDIUM_DATE_FORMAT, B_MEDIUM_TIME_FORMAT); 793 } else { 794 locale->FormatTime(timeText, sizeof(timeText), finishTime, 795 B_MEDIUM_TIME_FORMAT); 796 } 797 798 BString string(_FullTimeRemainingString(now, finishTime, timeText)); 799 float width = StringWidth(string.String()); 800 if (width > availableSpace) { 801 string.SetTo(_ShortTimeRemainingString(timeText)); 802 width = StringWidth(string.String()); 803 } 804 805 if (_width != NULL) 806 *_width = width; 807 return string; 808 } 809 810 811 BString 812 BStatusView::_ShortTimeRemainingString(const char* timeText) 813 { 814 BString buffer; 815 816 // complete string too wide, try with shorter version 817 buffer.SetTo(B_TRANSLATE("Finish: %time")); 818 buffer.ReplaceFirst("%time", timeText); 819 820 return buffer; 821 } 822 823 824 BString 825 BStatusView::_FullTimeRemainingString(time_t now, time_t finishTime, 826 const char* timeText) 827 { 828 BDurationFormat formatter; 829 BString buffer; 830 BString finishStr; 831 if (finishTime - now > 60 * 60) { 832 buffer.SetTo(B_TRANSLATE("Finish: %time - Over %finishtime left")); 833 formatter.Format(now * 1000000LL, finishTime * 1000000LL, &finishStr); 834 } else { 835 buffer.SetTo(B_TRANSLATE("Finish: %time - %finishtime left")); 836 formatter.Format(now * 1000000LL, finishTime * 1000000LL, &finishStr); 837 } 838 839 buffer.ReplaceFirst("%time", timeText); 840 buffer.ReplaceFirst("%finishtime", finishStr); 841 842 return buffer; 843 } 844 845 846 void 847 BStatusView::AttachedToWindow() 848 { 849 fPauseButton->SetTarget(this); 850 fStopButton->SetTarget(this); 851 } 852 853 854 void 855 BStatusView::MessageReceived(BMessage* message) 856 { 857 switch (message->what) { 858 case kPauseButton: 859 fIsPaused = !fIsPaused; 860 fPauseButton->SetValue(fIsPaused ? B_CONTROL_ON : B_CONTROL_OFF); 861 if (fBytesPerSecond != 0.0) { 862 fBytesPerSecond = 0.0; 863 for (size_t i = 0; i < kBytesPerSecondSlots; i++) 864 fBytesPerSecondSlot[i] = 0.0; 865 Invalidate(); 866 } 867 if (!fIsPaused) { 868 fEstimatedFinishReferenceTime = system_time(); 869 fEstimatedFinishReferenceSize = fSizeProcessed; 870 871 // force window update 872 Invalidate(); 873 874 // let 'er rip 875 resume_thread(Thread()); 876 } 877 break; 878 879 case kStopButton: 880 fWasCanceled = true; 881 if (fIsPaused) { 882 // resume so that the copy loop gets a chance to finish up 883 fIsPaused = false; 884 885 // force window update 886 Invalidate(); 887 888 // let 'er rip 889 resume_thread(Thread()); 890 } 891 break; 892 893 default: 894 _inherited::MessageReceived(message); 895 break; 896 } 897 } 898 899 900 void 901 BStatusView::UpdateStatus(const char* curItem, off_t itemSize, bool optional) 902 { 903 if (!fShowCount) { 904 fStatusBar->Update((float)fItemSize / fTotalSize); 905 fItemSize = 0; 906 return; 907 } 908 909 if (curItem != NULL) 910 fCurItem++; 911 912 fItemSize += itemSize; 913 fSizeProcessed += itemSize; 914 915 bigtime_t currentTime = system_time(); 916 if (!optional || ((currentTime - fLastUpdateTime) > kMaxUpdateInterval)) { 917 if (curItem != NULL || fPendingStatusString[0]) { 918 // forced update or past update time 919 920 BString buffer; 921 buffer << fCurItem << " "; 922 923 // if we don't have curItem, take the one from the stash 924 const char* statusItem = curItem != NULL 925 ? curItem : fPendingStatusString; 926 927 fStatusBar->Update((float)fItemSize / fTotalSize, statusItem, 928 buffer.String()); 929 930 // we already displayed this item, clear the stash 931 fPendingStatusString[0] = '\0'; 932 933 fLastUpdateTime = currentTime; 934 } else { 935 // don't have a file to show, just update the bar 936 fStatusBar->Update((float)fItemSize / fTotalSize); 937 } 938 939 if (currentTime 940 >= fLastSpeedReferenceTime + kSpeedReferenceInterval) { 941 // update current speed every kSpeedReferenceInterval 942 fCurrentBytesPerSecondSlot 943 = (fCurrentBytesPerSecondSlot + 1) % kBytesPerSecondSlots; 944 fBytesPerSecondSlot[fCurrentBytesPerSecondSlot] 945 = (double)(fSizeProcessed - fLastSpeedReferenceSize) 946 * 1000000LL / (currentTime - fLastSpeedReferenceTime); 947 fLastSpeedReferenceSize = fSizeProcessed; 948 fLastSpeedReferenceTime = currentTime; 949 fBytesPerSecond = 0.0; 950 size_t count = 0; 951 for (size_t i = 0; i < kBytesPerSecondSlots; i++) { 952 if (fBytesPerSecondSlot[i] != 0.0) { 953 fBytesPerSecond += fBytesPerSecondSlot[i]; 954 count++; 955 } 956 } 957 if (count > 0) 958 fBytesPerSecond /= count; 959 960 BString toolTip = _TimeStatusString(1024.f, NULL); 961 toolTip << "\n" << _FullSpeedString(); 962 SetToolTip(toolTip.String()); 963 964 Invalidate(); 965 } 966 967 fItemSize = 0; 968 } else if (curItem != NULL) { 969 // stash away the name of the item we are currently processing 970 // so we can show it when the time comes 971 strncpy(fPendingStatusString, curItem, 127); 972 fPendingStatusString[127] = '0'; 973 } else 974 SetToolTip((const char*)NULL); 975 } 976 977 978 void 979 BStatusView::SetWasCanceled() 980 { 981 fWasCanceled = true; 982 } 983