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