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