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 <Locale.h> 46 #include <MessageFilter.h> 47 #include <StringView.h> 48 #include <String.h> 49 50 #include <string.h> 51 52 #include "AutoLock.h" 53 #include "Bitmaps.h" 54 #include "Commands.h" 55 #include "StatusWindow.h" 56 #include "StringForSize.h" 57 #include "DeskWindow.h" 58 59 60 const float kDefaultStatusViewHeight = 50; 61 const bigtime_t kMaxUpdateInterval = 100000LL; 62 const bigtime_t kSpeedReferenceInterval = 2000000LL; 63 const bigtime_t kShowSpeedInterval = 8000000LL; 64 const bigtime_t kShowEstimatedFinishInterval = 4000000LL; 65 const BRect kStatusRect(200, 200, 550, 200); 66 67 static bigtime_t sLastEstimatedFinishSpeedToggleTime = -1; 68 static bool sShowSpeed = true; 69 static const time_t kSecondsPerDay = 24 * 60 * 60; 70 71 class TCustomButton : public BButton { 72 public: 73 TCustomButton(BRect frame, uint32 command); 74 virtual void Draw(BRect updateRect); 75 private: 76 typedef BButton _inherited; 77 }; 78 79 80 class BStatusMouseFilter : public BMessageFilter { 81 public: 82 BStatusMouseFilter(); 83 virtual filter_result Filter(BMessage* message, BHandler** target); 84 }; 85 86 87 namespace BPrivate { 88 BStatusWindow *gStatusWindow = NULL; 89 } 90 91 92 BStatusMouseFilter::BStatusMouseFilter() 93 : 94 BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_MOUSE_DOWN) 95 { 96 } 97 98 99 filter_result 100 BStatusMouseFilter::Filter(BMessage* message, BHandler** target) 101 { 102 // If the target is the status bar, make sure the message goes to the 103 // parent view instead. 104 if ((*target)->Name() != NULL 105 && strcmp((*target)->Name(), "StatusBar") == 0) { 106 BView* view = dynamic_cast<BView*>(*target); 107 if (view != NULL) 108 view = view->Parent(); 109 if (view != NULL) 110 *target = view; 111 } 112 113 return B_DISPATCH_MESSAGE; 114 } 115 116 117 TCustomButton::TCustomButton(BRect frame, uint32 what) 118 : 119 BButton(frame, "", "", new BMessage(what), B_FOLLOW_LEFT | B_FOLLOW_TOP, 120 B_WILL_DRAW) 121 { 122 } 123 124 125 void 126 TCustomButton::Draw(BRect updateRect) 127 { 128 _inherited::Draw(updateRect); 129 130 if (Message()->what == kStopButton) { 131 updateRect = Bounds(); 132 updateRect.InsetBy(9, 8); 133 SetHighColor(0, 0, 0); 134 if (Value() == B_CONTROL_ON) 135 updateRect.OffsetBy(1, 1); 136 FillRect(updateRect); 137 } else { 138 updateRect = Bounds(); 139 updateRect.InsetBy(9, 7); 140 BRect rect(updateRect); 141 rect.right -= 3; 142 143 updateRect.left += 3; 144 updateRect.OffsetBy(1, 0); 145 SetHighColor(0, 0, 0); 146 if (Value() == B_CONTROL_ON) { 147 updateRect.OffsetBy(1, 1); 148 rect.OffsetBy(1, 1); 149 } 150 FillRect(updateRect); 151 FillRect(rect); 152 } 153 } 154 155 156 // #pragma mark - 157 158 159 class StatusBackgroundView : public BView { 160 public: 161 StatusBackgroundView(BRect frame) 162 : BView(frame, "BackView", B_FOLLOW_ALL, B_WILL_DRAW | B_PULSE_NEEDED) 163 { 164 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 165 } 166 167 virtual void Pulse() 168 { 169 bigtime_t now = system_time(); 170 if (sShowSpeed 171 && sLastEstimatedFinishSpeedToggleTime + kShowSpeedInterval 172 <= now) { 173 sShowSpeed = false; 174 sLastEstimatedFinishSpeedToggleTime = now; 175 } else if (!sShowSpeed 176 && sLastEstimatedFinishSpeedToggleTime 177 + kShowEstimatedFinishInterval <= now) { 178 sShowSpeed = true; 179 sLastEstimatedFinishSpeedToggleTime = now; 180 } 181 } 182 }; 183 184 185 // #pragma mark - BStatusWindow 186 187 188 #undef B_TRANSLATE_CONTEXT 189 #define B_TRANSLATE_CONTEXT "StatusWindow" 190 191 192 BStatusWindow::BStatusWindow() 193 : 194 BWindow(kStatusRect, B_TRANSLATE("Tracker status"), B_TITLED_WINDOW, 195 B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_NOT_ZOOMABLE, B_ALL_WORKSPACES), 196 fRetainDesktopFocus(false) 197 { 198 SetSizeLimits(0, 100000, 0, 100000); 199 fMouseDownFilter = new BStatusMouseFilter(); 200 AddCommonFilter(fMouseDownFilter); 201 202 BView* view = new StatusBackgroundView(Bounds()); 203 AddChild(view); 204 205 SetPulseRate(1000000); 206 207 Hide(); 208 Show(); 209 } 210 211 212 BStatusWindow::~BStatusWindow() 213 { 214 } 215 216 217 void 218 BStatusWindow::CreateStatusItem(thread_id thread, StatusWindowState type) 219 { 220 AutoLock<BWindow> lock(this); 221 222 BRect rect(Bounds()); 223 if (BStatusView* lastView = fViewList.LastItem()) 224 rect.top = lastView->Frame().bottom + 1; 225 else { 226 // This is the first status item, reset speed/estimated finish toggle. 227 sShowSpeed = true; 228 sLastEstimatedFinishSpeedToggleTime = system_time(); 229 } 230 rect.bottom = rect.top + kDefaultStatusViewHeight - 1; 231 232 BStatusView* view = new BStatusView(rect, thread, type); 233 // the BStatusView will resize itself if needed in its constructor 234 ChildAt(0)->AddChild(view); 235 fViewList.AddItem(view); 236 237 ResizeTo(Bounds().Width(), view->Frame().bottom); 238 239 // find out if the desktop is the active window 240 // if the status window is the only thing to take over active state and 241 // desktop was active to begin with, return focus back to desktop 242 // when we are done 243 bool desktopActive = false; 244 { 245 AutoLock<BLooper> lock(be_app); 246 int32 count = be_app->CountWindows(); 247 for (int32 index = 0; index < count; index++) { 248 if (dynamic_cast<BDeskWindow *>(be_app->WindowAt(index)) 249 && be_app->WindowAt(index)->IsActive()) { 250 desktopActive = true; 251 break; 252 } 253 } 254 } 255 256 if (IsHidden()) { 257 fRetainDesktopFocus = desktopActive; 258 Minimize(false); 259 Show(); 260 } else 261 fRetainDesktopFocus &= desktopActive; 262 } 263 264 265 void 266 BStatusWindow::InitStatusItem(thread_id thread, int32 totalItems, 267 off_t totalSize, const entry_ref* destDir, bool showCount) 268 { 269 AutoLock<BWindow> lock(this); 270 271 int32 numItems = fViewList.CountItems(); 272 for (int32 index = 0; index < numItems; index++) { 273 BStatusView* view = fViewList.ItemAt(index); 274 if (view->Thread() == thread) { 275 view->InitStatus(totalItems, totalSize, destDir, showCount); 276 break; 277 } 278 } 279 280 } 281 282 283 void 284 BStatusWindow::UpdateStatus(thread_id thread, const char* curItem, 285 off_t itemSize, bool optional) 286 { 287 AutoLock<BWindow> lock(this); 288 289 int32 numItems = fViewList.CountItems(); 290 for (int32 index = 0; index < numItems; index++) { 291 BStatusView* view = fViewList.ItemAt(index); 292 if (view->Thread() == thread) { 293 view->UpdateStatus(curItem, itemSize, optional); 294 break; 295 } 296 } 297 } 298 299 300 void 301 BStatusWindow::RemoveStatusItem(thread_id thread) 302 { 303 AutoLock<BWindow> lock(this); 304 BStatusView* winner = NULL; 305 306 int32 numItems = fViewList.CountItems(); 307 int32 index; 308 for (index = 0; index < numItems; index++) { 309 BStatusView* view = fViewList.ItemAt(index); 310 if (view->Thread() == thread) { 311 winner = view; 312 break; 313 } 314 } 315 316 if (winner != NULL) { 317 // The height by which the other views will have to be moved (in pixel 318 // count). 319 float height = winner->Bounds().Height() + 1; 320 fViewList.RemoveItem(winner); 321 winner->RemoveSelf(); 322 delete winner; 323 324 if (--numItems == 0 && !IsHidden()) { 325 BDeskWindow* desktop = NULL; 326 if (fRetainDesktopFocus) { 327 AutoLock<BLooper> lock(be_app); 328 int32 count = be_app->CountWindows(); 329 for (int32 index = 0; index < count; index++) { 330 desktop = dynamic_cast<BDeskWindow*>( 331 be_app->WindowAt(index)); 332 if (desktop != NULL) 333 break; 334 } 335 } 336 Hide(); 337 if (desktop != NULL) { 338 // desktop was active when we first started, 339 // make it active again 340 desktop->Activate(); 341 } 342 } 343 344 for (; index < numItems; index++) 345 fViewList.ItemAt(index)->MoveBy(0, -height); 346 347 ResizeTo(Bounds().Width(), Bounds().Height() - height); 348 } 349 } 350 351 352 bool 353 BStatusWindow::CheckCanceledOrPaused(thread_id thread) 354 { 355 bool wasCanceled = false; 356 bool isPaused = false; 357 358 BStatusView* view = NULL; 359 360 AutoLock<BWindow> lock(this); 361 // check if cancel or pause hit 362 for (int32 index = fViewList.CountItems() - 1; index >= 0; index--) { 363 view = fViewList.ItemAt(index); 364 if (view && view->Thread() == thread) { 365 isPaused = view->IsPaused(); 366 wasCanceled = view->WasCanceled(); 367 break; 368 } 369 } 370 371 if (wasCanceled || !isPaused) 372 return wasCanceled; 373 374 if (isPaused && view != NULL) { 375 // say we are paused 376 view->Invalidate(); 377 thread_id thread = view->Thread(); 378 379 lock.Unlock(); 380 381 // and suspend ourselves 382 // we will get resumed from BStatusView::MessageReceived 383 ASSERT(find_thread(NULL) == thread); 384 suspend_thread(thread); 385 } 386 387 return wasCanceled; 388 } 389 390 391 bool 392 BStatusWindow::AttemptToQuit() 393 { 394 // called when tracker is quitting 395 // try to cancel all the move/copy/empty trash threads in a nice way 396 // by issuing cancels 397 int32 count = fViewList.CountItems(); 398 399 if (count == 0) 400 return true; 401 402 for (int32 index = 0; index < count; index++) 403 fViewList.ItemAt(index)->SetWasCanceled(); 404 405 // maybe next time everything will have been canceled 406 return false; 407 } 408 409 410 void 411 BStatusWindow::WindowActivated(bool state) 412 { 413 if (!state) 414 fRetainDesktopFocus = false; 415 416 return _inherited::WindowActivated(state); 417 } 418 419 420 // #pragma mark - BStatusView 421 422 423 BStatusView::BStatusView(BRect bounds, thread_id thread, 424 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 BString buffer(B_TRANSLATE("To: %dir")); 685 buffer.ReplaceFirst("%dir", fDestDir); 686 687 *_width = ceilf(StringWidth(buffer.String())); 688 return buffer; 689 } 690 691 692 BString 693 BStatusView::_StatusString(float availableSpace, float fontSize, float* _width) 694 { 695 BFont font; 696 GetFont(&font); 697 float oldSize = font.Size(); 698 font.SetSize(fontSize); 699 SetFont(&font, B_FONT_SIZE); 700 701 BString status; 702 if (sShowSpeed) { 703 status = _SpeedStatusString(availableSpace, _width); 704 } else 705 status = _TimeStatusString(availableSpace, _width); 706 707 font.SetSize(oldSize); 708 SetFont(&font, B_FONT_SIZE); 709 return status; 710 } 711 712 713 BString 714 BStatusView::_SpeedStatusString(float availableSpace, float* _width) 715 { 716 BString string(_FullSpeedString()); 717 *_width = StringWidth(string.String()); 718 if (*_width > availableSpace) { 719 string.SetTo(_ShortSpeedString()); 720 *_width = StringWidth(string.String()); 721 } 722 *_width = ceilf(*_width); 723 return string; 724 } 725 726 727 BString 728 BStatusView::_FullSpeedString() 729 { 730 BString buffer; 731 if (fBytesPerSecond != 0.0) { 732 char sizeBuffer[128]; 733 buffer.SetTo(B_TRANSLATE( 734 "(%SizeProcessed of %TotalSize, %BytesPerSecond/s)")); 735 buffer.ReplaceFirst("%SizeProcessed", 736 string_for_size((double)fSizeProcessed, sizeBuffer, 737 sizeof(sizeBuffer))); 738 buffer.ReplaceFirst("%TotalSize", 739 string_for_size((double)fTotalSize, sizeBuffer, 740 sizeof(sizeBuffer))); 741 buffer.ReplaceFirst("%BytesPerSecond", 742 string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer))); 743 } 744 return buffer; 745 } 746 747 748 BString 749 BStatusView::_ShortSpeedString() 750 { 751 BString buffer; 752 if (fBytesPerSecond != 0.0) { 753 char sizeBuffer[128]; 754 buffer << B_TRANSLATE("%BytesPerSecond/s"); 755 buffer.ReplaceFirst("%BytesPerSecond", 756 string_for_size(fBytesPerSecond, sizeBuffer, sizeof(sizeBuffer))); 757 } 758 return buffer; 759 } 760 761 762 BString 763 BStatusView::_TimeStatusString(float availableSpace, float* _width) 764 { 765 double totalBytesPerSecond = (double)(fSizeProcessed 766 - fEstimatedFinishReferenceSize) 767 * 1000000LL / (system_time() - fEstimatedFinishReferenceTime); 768 double secondsRemaining = (fTotalSize - fSizeProcessed) 769 / totalBytesPerSecond; 770 time_t now = (time_t)real_time_clock(); 771 time_t finishTime = (time_t)(now + secondsRemaining); 772 773 tm _time; 774 tm* time = localtime_r(&finishTime, &_time); 775 int32 year = time->tm_year + 1900; 776 777 char timeText[32]; 778 // TODO: Localization of time string... 779 if (now < finishTime - kSecondsPerDay) { 780 // process is going to take more than a day! 781 snprintf(timeText, sizeof(timeText), "%0*d:%0*d %0*d/%0*d/%ld", 782 2, time->tm_hour, 2, time->tm_min, 783 2, time->tm_mon + 1, 2, time->tm_mday, year); 784 } else { 785 snprintf(timeText, sizeof(timeText), "%0*d:%0*d", 786 2, time->tm_hour, 2, time->tm_min); 787 } 788 789 finishTime -= now; 790 BString string(_FullTimeRemainingString(finishTime, timeText)); 791 *_width = StringWidth(string.String()); 792 if (*_width > availableSpace) { 793 string.SetTo(_ShortTimeRemainingString(timeText)); 794 *_width = StringWidth(string.String()); 795 } 796 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 finishTime, const char* timeText) 816 { 817 BString buffer; 818 char finishStr[32]; 819 if (finishTime > kSecondsPerDay) { 820 buffer.SetTo(B_TRANSLATE("(Finish: %time - Over %finishtime " 821 "days left)")); 822 snprintf(finishStr, sizeof(finishStr), "%ld", 823 finishTime / kSecondsPerDay); 824 } else if (finishTime > 60 * 60) { 825 buffer.SetTo(B_TRANSLATE("(Finish: %time - Over %finishtime " 826 "hours left)")); 827 snprintf(finishStr, sizeof(finishStr), "%ld", 828 finishTime / (60 * 60)); 829 } else if (finishTime > 60) { 830 buffer.SetTo(B_TRANSLATE("(Finish: %time - %finishtime minutes " 831 "left)")); 832 snprintf(finishStr, sizeof(finishStr), "%ld", finishTime / 60); 833 } else { 834 buffer.SetTo(B_TRANSLATE("(Finish: %time - %finishtime seconds " 835 "left)")); 836 snprintf(finishStr, sizeof(finishStr), "%ld", finishTime); 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 Invalidate(); 960 } 961 962 fItemSize = 0; 963 } else if (curItem != NULL) { 964 // stash away the name of the item we are currently processing 965 // so we can show it when the time comes 966 strncpy(fPendingStatusString, curItem, 127); 967 fPendingStatusString[127] = '0'; 968 } 969 } 970 971 972 void 973 BStatusView::SetWasCanceled() 974 { 975 fWasCanceled = true; 976 } 977 978