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