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