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 36 // the status of the tracker (Copying, Deleting, etc.). 37 38 #include <Application.h> 39 #include <Button.h> 40 #include <ControlLook.h> 41 #include <Debug.h> 42 #include <MessageFilter.h> 43 #include <StringView.h> 44 #include <String.h> 45 46 #include <string.h> 47 48 #include "AutoLock.h" 49 #include "Bitmaps.h" 50 #include "Commands.h" 51 #include "StatusWindow.h" 52 #include "DeskWindow.h" 53 54 55 const float kDefaultStatusViewHeight = 50; 56 const float kUpdateGrain = 100000; 57 const BRect kStatusRect(200, 200, 550, 200); 58 59 60 class TCustomButton : public BButton { 61 public: 62 TCustomButton(BRect frame, uint32 command); 63 virtual void Draw(BRect); 64 private: 65 typedef BButton _inherited; 66 }; 67 68 class BStatusMouseFilter : public BMessageFilter { 69 public: 70 BStatusMouseFilter() 71 : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_MOUSE_DOWN) 72 {} 73 74 virtual filter_result Filter(BMessage *message, BHandler **target); 75 }; 76 77 78 namespace BPrivate { 79 BStatusWindow *gStatusWindow = NULL; 80 } 81 82 83 filter_result 84 BStatusMouseFilter::Filter(BMessage *, BHandler **target) 85 { 86 if ((*target)->Name() 87 && strcmp((*target)->Name(), "StatusBar") == 0) { 88 BView *view = dynamic_cast<BView *>(*target); 89 if (view) 90 view = view->Parent(); 91 if (view) 92 *target = view; 93 } 94 95 return B_DISPATCH_MESSAGE; 96 } 97 98 99 TCustomButton::TCustomButton(BRect frame, uint32 what) 100 : BButton(frame, "", "", new BMessage(what), B_FOLLOW_LEFT | B_FOLLOW_TOP, 101 B_WILL_DRAW) 102 { 103 } 104 105 106 void 107 TCustomButton::Draw(BRect updateRect) 108 { 109 _inherited::Draw(updateRect); 110 111 if (Message()->what == kStopButton) { 112 updateRect = Bounds(); 113 updateRect.InsetBy(9, 8); 114 SetHighColor(0, 0, 0); 115 if (Value() == B_CONTROL_ON) 116 updateRect.OffsetBy(1, 1); 117 FillRect(updateRect); 118 } else { 119 updateRect = Bounds(); 120 updateRect.InsetBy(9, 7); 121 BRect rect(updateRect); 122 rect.right -= 3; 123 124 updateRect.left += 3; 125 updateRect.OffsetBy(1, 0); 126 SetHighColor(0, 0, 0); 127 if (Value() == B_CONTROL_ON) { 128 updateRect.OffsetBy(1, 1); 129 rect.OffsetBy(1, 1); 130 } 131 FillRect(updateRect); 132 FillRect(rect); 133 } 134 } 135 136 137 BStatusWindow::BStatusWindow() 138 : BWindow(kStatusRect, "Tracker Status", B_TITLED_WINDOW, 139 B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_NOT_ZOOMABLE, 140 B_ALL_WORKSPACES), 141 fRetainDesktopFocus(false) 142 { 143 SetSizeLimits(0, 100000, 0, 100000); 144 fMouseDownFilter = new BStatusMouseFilter(); 145 AddCommonFilter(fMouseDownFilter); 146 147 BRect bounds(Bounds()); 148 149 BView *view = new BView(bounds, "BackView", B_FOLLOW_ALL, B_WILL_DRAW); 150 view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 151 AddChild(view); 152 153 Run(); 154 } 155 156 157 BStatusWindow::~BStatusWindow() 158 { 159 } 160 161 162 bool 163 BStatusWindow::CheckCanceledOrPaused(thread_id thread) 164 { 165 bool wasCanceled = false; 166 bool isPaused = false; 167 168 BStatusView *view = NULL; 169 170 for (;;) { 171 172 AutoLock<BWindow> lock(this); 173 // check if cancel or pause hit 174 for (int32 index = fViewList.CountItems() - 1; index >= 0; index--) { 175 176 view = fViewList.ItemAt(index); 177 if (view && view->Thread() == thread) { 178 isPaused = view->IsPaused(); 179 wasCanceled = view->WasCanceled(); 180 break; 181 } 182 } 183 lock.Unlock(); 184 185 if (wasCanceled || !isPaused) 186 break; 187 188 if (isPaused && view) { 189 AutoLock<BWindow> lock(this); 190 // say we are paused 191 view->Invalidate(); 192 lock.Unlock(); 193 194 ASSERT(find_thread(NULL) == view->Thread()); 195 196 // and suspend ourselves 197 // we will get resumend from BStatusView::MessageReceived 198 suspend_thread(view->Thread()); 199 } 200 break; 201 202 } 203 204 return wasCanceled; 205 } 206 207 208 bool 209 BStatusWindow::AttemptToQuit() 210 { 211 // called when tracker is quitting 212 // try to cancel all the move/copy/empty trash threads in a nice way 213 // by issuing cancels 214 int32 count = fViewList.CountItems(); 215 216 if (count == 0) 217 return true; 218 219 for (int32 index = 0; index < count; index++) 220 fViewList.ItemAt(index)->SetWasCanceled(); 221 222 // maybe next time everything will have been canceled 223 return false; 224 } 225 226 227 void 228 BStatusWindow::CreateStatusItem(thread_id thread, StatusWindowState type) 229 { 230 AutoLock<BWindow> lock(this); 231 232 BRect rect(Bounds()); 233 if (BStatusView* lastView = fViewList.LastItem()) 234 rect.top = lastView->Frame().bottom + 1; 235 rect.bottom = rect.top + kDefaultStatusViewHeight - 1; 236 237 BStatusView *view = new BStatusView(rect, thread, type); 238 // the BStatusView will resize itself if needed in its constructor 239 ChildAt(0)->AddChild(view); 240 fViewList.AddItem(view); 241 242 ResizeTo(Bounds().Width(), view->Frame().bottom); 243 244 // find out if the desktop is the active window 245 // if the status window is the only thing to take over active state and 246 // desktop was active to begin with, return focus back to desktop 247 // when we are done 248 bool desktopActive = false; 249 { 250 AutoLock<BLooper> lock(be_app); 251 int32 count = be_app->CountWindows(); 252 for (int32 index = 0; index < count; index++) { 253 if (dynamic_cast<BDeskWindow *>(be_app->WindowAt(index)) 254 && be_app->WindowAt(index)->IsActive()) { 255 desktopActive = true; 256 break; 257 } 258 } 259 } 260 261 if (IsHidden()) { 262 fRetainDesktopFocus = desktopActive; 263 Minimize(false); 264 Show(); 265 } else 266 fRetainDesktopFocus &= desktopActive; 267 } 268 269 270 void 271 BStatusWindow::WindowActivated(bool state) 272 { 273 if (!state) 274 fRetainDesktopFocus = false; 275 276 return _inherited::WindowActivated(state); 277 } 278 279 280 void 281 BStatusWindow::RemoveStatusItem(thread_id thread) 282 { 283 AutoLock<BWindow> lock(this); 284 BStatusView *winner = NULL; 285 286 int32 numItems = fViewList.CountItems(); 287 int32 index; 288 for (index = 0; index < numItems; index++) { 289 BStatusView *view = fViewList.ItemAt(index); 290 if (view->Thread() == thread) { 291 winner = view; 292 break; 293 } 294 } 295 296 if (winner) { 297 // the height by which the other views will have to be moved (in pixel count) 298 float height = winner->Bounds().Height() + 1; 299 fViewList.RemoveItem(winner); 300 winner->RemoveSelf(); 301 delete winner; 302 303 if (--numItems == 0 && !IsHidden()) { 304 BDeskWindow *desktop = NULL; 305 if (fRetainDesktopFocus) { 306 AutoLock<BLooper> lock(be_app); 307 int32 count = be_app->CountWindows(); 308 for (int32 index = 0; index < count; index++) { 309 desktop = dynamic_cast<BDeskWindow *>(be_app->WindowAt(index)); 310 if (desktop) 311 break; 312 } 313 } 314 Hide(); 315 if (desktop) 316 // desktop was active when we first started, 317 // make it active again 318 desktop->Activate(); 319 } 320 321 for (; index < numItems; index++) 322 fViewList.ItemAt(index)->MoveBy(0, -height); 323 324 ResizeTo(Bounds().Width(), Bounds().Height() - height); 325 } 326 327 } 328 329 330 bool 331 BStatusWindow::HasStatus(thread_id thread) 332 { 333 AutoLock<BWindow> lock(this); 334 335 int32 numItems = fViewList.CountItems(); 336 for (int32 index = 0; index < numItems; index++) { 337 BStatusView *view = fViewList.ItemAt(index); 338 if (view->Thread() == thread) 339 return true; 340 341 } 342 343 return false; 344 } 345 346 347 void 348 BStatusWindow::UpdateStatus(thread_id thread, const char *curItem, off_t itemSize, 349 bool optional) 350 { 351 AutoLock<BWindow> lock(this); 352 353 int32 numItems = fViewList.CountItems(); 354 for (int32 index = 0; index < numItems; index++) { 355 BStatusView *view = fViewList.ItemAt(index); 356 if (view->Thread() == thread) { 357 view->UpdateStatus(curItem, itemSize, optional); 358 break; 359 } 360 } 361 362 } 363 364 365 void 366 BStatusWindow::InitStatusItem(thread_id thread, int32 totalItems, off_t totalSize, 367 const entry_ref *destDir, bool showCount) 368 { 369 AutoLock<BWindow> lock(this); 370 371 int32 numItems = fViewList.CountItems(); 372 for (int32 index = 0; index < numItems; index++) { 373 BStatusView *view = fViewList.ItemAt(index); 374 if (view->Thread() == thread) { 375 view->InitStatus(totalItems, totalSize, destDir, showCount); 376 break; 377 } 378 } 379 380 } 381 382 383 BStatusView::BStatusView(BRect bounds, thread_id thread, StatusWindowState type) 384 : BView(bounds, "StatusView", B_FOLLOW_NONE, B_WILL_DRAW), 385 fBitmap(NULL) 386 { 387 Init(); 388 389 fThread = thread; 390 fType = type; 391 392 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 393 SetLowColor(ViewColor()); 394 SetHighColor(20, 20, 20); 395 SetDrawingMode(B_OP_OVER); 396 397 const float buttonWidth = 22; 398 const float buttonHeight = 20; 399 400 BRect rect(bounds); 401 rect.OffsetTo(B_ORIGIN); 402 rect.left += 40; 403 rect.right -= buttonWidth * 2 + 12; 404 rect.top += 6; 405 rect.bottom = rect.top + 15; 406 407 408 const char *caption = NULL; 409 int32 id = 0; 410 411 switch (type) { 412 case kCopyState: 413 caption = "Preparing to copy items" B_UTF8_ELLIPSIS; 414 id = kResCopyStatusBitmap; 415 break; 416 417 case kMoveState: 418 caption = "Preparing to move items" B_UTF8_ELLIPSIS; 419 id = kResMoveStatusBitmap; 420 break; 421 422 case kCreateLinkState: 423 caption = "Preparing to create links" B_UTF8_ELLIPSIS; 424 id = kResMoveStatusBitmap; 425 break; 426 427 case kTrashState: 428 caption = "Preparing to empty Trash" B_UTF8_ELLIPSIS; 429 id = kResTrashStatusBitmap; 430 break; 431 432 433 case kVolumeState: 434 caption = "Searching for disks to mount" B_UTF8_ELLIPSIS; 435 break; 436 437 case kDeleteState: 438 caption = "Preparing to delete items" B_UTF8_ELLIPSIS; 439 id = kResTrashStatusBitmap; 440 break; 441 442 case kRestoreFromTrashState: 443 caption = "Preparing to restore items" B_UTF8_ELLIPSIS; 444 break; 445 446 default: 447 TRESPASS(); 448 break; 449 } 450 451 if (caption) { 452 fStatusBar = new BStatusBar(rect, "StatusBar", caption); 453 fStatusBar->SetBarHeight(12); 454 float width, height; 455 fStatusBar->GetPreferredSize(&width, &height); 456 fStatusBar->ResizeTo(fStatusBar->Frame().Width(), height); 457 AddChild(fStatusBar); 458 459 // figure out how much room we need to display 460 // the additional status message below the bar 461 font_height fh; 462 GetFontHeight(&fh); 463 BRect f = fStatusBar->Frame(); 464 // height is 3 x the "room from the top" + bar height + room for string 465 ResizeTo(Bounds().Width(), f.top + f.Height() + fh.leading + fh.ascent + fh.descent + f.top); 466 } 467 468 if (id) 469 GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, id, &fBitmap); 470 471 rect = Bounds(); 472 rect.left = rect.right - buttonWidth * 2 - 7; 473 rect.right = rect.left + buttonWidth; 474 rect.top = floorf((rect.top + rect.bottom) / 2 + 0.5) - buttonHeight / 2; 475 rect.bottom = rect.top + buttonHeight; 476 477 fPauseButton = new TCustomButton(rect, kPauseButton); 478 fPauseButton->ResizeTo(buttonWidth, buttonHeight); 479 AddChild(fPauseButton); 480 481 rect.OffsetBy(buttonWidth + 2, 0); 482 fStopButton = new TCustomButton(rect, kStopButton); 483 fStopButton->ResizeTo(buttonWidth, buttonHeight); 484 AddChild(fStopButton); 485 } 486 487 488 BStatusView::~BStatusView() 489 { 490 delete fBitmap; 491 } 492 493 494 void 495 BStatusView::Init() 496 { 497 fDestDir = ""; 498 fCurItem = 0; 499 fPendingStatusString[0] = '\0'; 500 fWasCanceled = false; 501 fIsPaused = false; 502 fLastUpdateTime = 0; 503 fItemSize = 0; 504 } 505 506 507 void 508 BStatusView::AttachedToWindow() 509 { 510 fPauseButton->SetTarget(this); 511 fStopButton->SetTarget(this); 512 } 513 514 515 void 516 BStatusView::InitStatus(int32 totalItems, off_t totalSize, 517 const entry_ref *destDir, bool showCount) 518 { 519 Init(); 520 fTotalSize = totalSize; 521 fShowCount = showCount; 522 523 BEntry entry; 524 char name[B_FILE_NAME_LENGTH]; 525 if (destDir && (entry.SetTo(destDir) == B_OK)) { 526 entry.GetName(name); 527 fDestDir = name; 528 } 529 530 BString buffer; 531 if (totalItems > 0) 532 buffer << "of " << totalItems; 533 534 switch (fType) { 535 case kCopyState: 536 fStatusBar->Reset("Copying: ", buffer.String()); 537 break; 538 539 case kCreateLinkState: 540 fStatusBar->Reset("Creating Links: ", buffer.String()); 541 break; 542 543 case kMoveState: 544 fStatusBar->Reset("Moving: ", buffer.String()); 545 break; 546 547 case kTrashState: 548 fStatusBar->Reset("Emptying Trash" B_UTF8_ELLIPSIS " ", buffer.String()); 549 break; 550 551 case kDeleteState: 552 fStatusBar->Reset("Deleting: ", buffer.String()); 553 break; 554 555 case kRestoreFromTrashState: 556 fStatusBar->Reset("Restoring: ", buffer.String()); 557 break; 558 559 default: 560 break; 561 } 562 563 fStatusBar->SetMaxValue(1); 564 // SetMaxValue has to be here because Reset changes it to 100 565 Invalidate(); 566 } 567 568 569 void 570 BStatusView::UpdateStatus(const char *curItem, off_t itemSize, bool optional) 571 { 572 float currentTime = system_time(); 573 574 if (fShowCount) { 575 576 if (curItem) 577 fCurItem++; 578 579 fItemSize += itemSize; 580 581 if (!optional || ((currentTime - fLastUpdateTime) > kUpdateGrain)) { 582 if (curItem != NULL || fPendingStatusString[0]) { 583 // forced update or past update time 584 585 BString buffer; 586 buffer << fCurItem << " "; 587 588 // if we don't have curItem, take the one from the stash 589 const char *statusItem = curItem != NULL 590 ? curItem : fPendingStatusString; 591 592 fStatusBar->Update((float)fItemSize / fTotalSize, statusItem, 593 buffer.String()); 594 595 // we already displayed this item, clear the stash 596 fPendingStatusString[0] = '\0'; 597 598 fLastUpdateTime = currentTime; 599 } 600 else 601 // don't have a file to show, just update the bar 602 fStatusBar->Update((float)fItemSize / fTotalSize); 603 604 fItemSize = 0; 605 } else if (curItem != NULL) { 606 // stash away the name of the item we are currently processing 607 // so we can show it when the time comes 608 strncpy(fPendingStatusString, curItem, 127); 609 fPendingStatusString[127] = '0'; 610 } 611 } else { 612 fStatusBar->Update((float)fItemSize / fTotalSize); 613 fItemSize = 0; 614 } 615 } 616 617 618 void 619 BStatusView::MessageReceived(BMessage *message) 620 { 621 switch (message->what) { 622 case kPauseButton: 623 fIsPaused = !fIsPaused; 624 fPauseButton->SetValue(fIsPaused ? B_CONTROL_ON : B_CONTROL_OFF); 625 if (!fIsPaused) { 626 627 // force window update 628 Invalidate(); 629 630 // let 'er rip 631 resume_thread(Thread()); 632 } 633 break; 634 635 case kStopButton: 636 fWasCanceled = true; 637 if (fIsPaused) { 638 // resume so that the copy loop gets a chance to finish up 639 fIsPaused = false; 640 641 // force window update 642 Invalidate(); 643 644 // let 'er rip 645 resume_thread(Thread()); 646 } 647 break; 648 649 default: 650 _inherited::MessageReceived(message); 651 break; 652 } 653 } 654 655 656 void 657 BStatusView::Draw(BRect updateRect) 658 { 659 if (fBitmap) { 660 BPoint location; 661 location.x = (fStatusBar->Frame().left - fBitmap->Bounds().Width()) / 2; 662 location.y = (Bounds().Height()- fBitmap->Bounds().Height()) / 2; 663 DrawBitmap(fBitmap, location); 664 } 665 666 BRect bounds(Bounds()); 667 668 if (be_control_look != NULL) { 669 be_control_look->DrawRaisedBorder(this, bounds, updateRect, 670 ViewColor()); 671 } else { 672 // draw a frame, which also separates multiple BStatusViews 673 rgb_color light = tint_color(ViewColor(), B_LIGHTEN_MAX_TINT); 674 rgb_color shadow = tint_color(ViewColor(), B_DARKEN_1_TINT); 675 BeginLineArray(4); 676 AddLine(BPoint(bounds.left, bounds.bottom - 1.0f), 677 BPoint(bounds.left, bounds.top), light); 678 AddLine(BPoint(bounds.left + 1.0f, bounds.top), 679 BPoint(bounds.right, bounds.top), light); 680 AddLine(BPoint(bounds.right, bounds.top + 1.0f), 681 BPoint(bounds.right, bounds.bottom), shadow); 682 AddLine(BPoint(bounds.right - 1.0f, bounds.bottom), 683 BPoint(bounds.left, bounds.bottom), shadow); 684 EndLineArray(); 685 } 686 687 SetHighColor(0, 0, 0); 688 689 BPoint tp = fStatusBar->Frame().LeftBottom(); 690 font_height fh; 691 GetFontHeight(&fh); 692 tp.y += ceilf(fh.leading) + ceilf(fh.ascent); 693 694 if (IsPaused()) 695 DrawString("Paused: click to resume or stop", tp); 696 else if (fDestDir.Length()) { 697 BString buffer; 698 buffer << "To: " << fDestDir; 699 SetHighColor(0, 0, 0); 700 DrawString(buffer.String(), tp); 701 } 702 } 703 704 705 void 706 BStatusView::SetWasCanceled() 707 { 708 fWasCanceled = true; 709 } 710 711