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