1 /* 2 * Copyright 2001-2008, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Axel Dörfler, axeld@pinc-software.de 8 * Stephan Aßmus <superstippi@gmx.de> 9 */ 10 11 /*! BStatusBar displays a "percentage-of-completion" gauge. */ 12 #include <StatusBar.h> 13 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 18 #include <ControlLook.h> 19 #include <Layout.h> 20 #include <LayoutUtils.h> 21 #include <Message.h> 22 #include <Region.h> 23 24 #include <binary_compatibility/Interface.h> 25 26 27 static const rgb_color kDefaultBarColor = {50, 150, 255, 255}; 28 29 30 BStatusBar::BStatusBar(BRect frame, const char *name, const char *label, 31 const char *trailingLabel) 32 : 33 BView(frame, name, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW), 34 fLabel(label), 35 fTrailingLabel(trailingLabel) 36 { 37 _InitObject(); 38 } 39 40 41 BStatusBar::BStatusBar(const char *name, const char *label, 42 const char *trailingLabel) 43 : 44 BView(BRect(0, 0, -1, -1), name, B_FOLLOW_LEFT | B_FOLLOW_TOP, 45 B_WILL_DRAW | B_SUPPORTS_LAYOUT), 46 fLabel(label), 47 fTrailingLabel(trailingLabel) 48 { 49 _InitObject(); 50 } 51 52 53 BStatusBar::BStatusBar(BMessage *archive) 54 : 55 BView(archive) 56 { 57 _InitObject(); 58 59 archive->FindString("_label", &fLabel); 60 archive->FindString("_tlabel", &fTrailingLabel); 61 62 archive->FindString("_text", &fText); 63 archive->FindString("_ttext", &fTrailingText); 64 65 float floatValue; 66 if (archive->FindFloat("_high", &floatValue) == B_OK) { 67 fBarHeight = floatValue; 68 fCustomBarHeight = true; 69 } 70 71 int32 color; 72 if (archive->FindInt32("_bcolor", (int32 *)&color) == B_OK) 73 fBarColor = *(rgb_color *)&color; 74 75 if (archive->FindFloat("_val", &floatValue) == B_OK) 76 fCurrent = floatValue; 77 if (archive->FindFloat("_max", &floatValue) == B_OK) 78 fMax = floatValue; 79 } 80 81 82 BStatusBar::~BStatusBar() 83 { 84 } 85 86 87 BArchivable * 88 BStatusBar::Instantiate(BMessage *archive) 89 { 90 if (validate_instantiation(archive, "BStatusBar")) 91 return new BStatusBar(archive); 92 93 return NULL; 94 } 95 96 97 status_t 98 BStatusBar::Archive(BMessage *archive, bool deep) const 99 { 100 status_t err = BView::Archive(archive, deep); 101 if (err < B_OK) 102 return err; 103 104 if (fCustomBarHeight) 105 err = archive->AddFloat("_high", fBarHeight); 106 107 if (err == B_OK && fBarColor != kDefaultBarColor) 108 err = archive->AddInt32("_bcolor", (const uint32 &)fBarColor); 109 110 if (err == B_OK && fCurrent != 0) 111 err = archive->AddFloat("_val", fCurrent); 112 if (err == B_OK && fMax != 100 ) 113 err = archive->AddFloat("_max", fMax); 114 115 if (err == B_OK && fText.Length()) 116 err = archive->AddString("_text", fText); 117 if (err == B_OK && fTrailingText.Length()) 118 err = archive->AddString("_ttext", fTrailingText); 119 120 if (err == B_OK && fLabel.Length()) 121 err = archive->AddString("_label", fLabel); 122 if (err == B_OK && fTrailingLabel.Length()) 123 err = archive->AddString ("_tlabel", fTrailingLabel); 124 125 return err; 126 } 127 128 129 // #pragma mark - 130 131 132 void 133 BStatusBar::AttachedToWindow() 134 { 135 // resize so that the height fits 136 float width, height; 137 GetPreferredSize(&width, &height); 138 ResizeTo(Bounds().Width(), height); 139 140 SetViewColor(B_TRANSPARENT_COLOR); 141 rgb_color lowColor = B_TRANSPARENT_COLOR; 142 143 BView* parent = Parent(); 144 if (parent != NULL) 145 lowColor = parent->ViewColor(); 146 147 if (lowColor == B_TRANSPARENT_COLOR) 148 lowColor = ui_color(B_PANEL_BACKGROUND_COLOR); 149 150 SetLowColor(lowColor); 151 152 fTextDivider = Bounds().Width(); 153 } 154 155 156 void 157 BStatusBar::DetachedFromWindow() 158 { 159 BView::DetachedFromWindow(); 160 } 161 162 163 void 164 BStatusBar::AllAttached() 165 { 166 BView::AllAttached(); 167 } 168 169 170 void 171 BStatusBar::AllDetached() 172 { 173 BView::AllDetached(); 174 } 175 176 177 // #pragma mark - 178 179 180 void 181 BStatusBar::WindowActivated(bool state) 182 { 183 BView::WindowActivated(state); 184 } 185 186 187 void 188 BStatusBar::MakeFocus(bool state) 189 { 190 BView::MakeFocus(state); 191 } 192 193 194 // #pragma mark - 195 196 197 void 198 BStatusBar::GetPreferredSize(float* _width, float* _height) 199 { 200 if (_width) { 201 // AttachedToWindow() might not have been called yet 202 *_width = ceilf(StringWidth(fLabel.String())) 203 + ceilf(StringWidth(fTrailingLabel.String())) 204 + ceilf(StringWidth(fText.String())) 205 + ceilf(StringWidth(fTrailingText.String())) 206 + 5; 207 } 208 209 if (_height) { 210 font_height fontHeight; 211 GetFontHeight(&fontHeight); 212 213 *_height = ceilf(fontHeight.ascent + fontHeight.descent) + 6 214 + BarHeight(); 215 } 216 } 217 218 219 BSize 220 BStatusBar::MinSize() 221 { 222 float width, height; 223 GetPreferredSize(&width, &height); 224 225 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), BSize(width, height)); 226 } 227 228 229 BSize 230 BStatusBar::MaxSize() 231 { 232 float width, height; 233 GetPreferredSize(&width, &height); 234 235 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 236 BSize(B_SIZE_UNLIMITED, height)); 237 } 238 239 240 BSize 241 BStatusBar::PreferredSize() 242 { 243 float width, height; 244 GetPreferredSize(&width, &height); 245 246 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), BSize(width, height)); 247 } 248 249 250 void 251 BStatusBar::ResizeToPreferred() 252 { 253 BView::ResizeToPreferred(); 254 } 255 256 257 void 258 BStatusBar::FrameMoved(BPoint newPosition) 259 { 260 BView::FrameMoved(newPosition); 261 } 262 263 264 void 265 BStatusBar::FrameResized(float newWidth, float newHeight) 266 { 267 BView::FrameResized(newWidth, newHeight); 268 Invalidate(); 269 } 270 271 272 // #pragma mark - 273 274 275 void 276 BStatusBar::Draw(BRect updateRect) 277 { 278 rgb_color backgroundColor; 279 if (Parent()) 280 backgroundColor = Parent()->ViewColor(); 281 else 282 backgroundColor = ui_color(B_PANEL_BACKGROUND_COLOR); 283 284 font_height fontHeight; 285 GetFontHeight(&fontHeight); 286 BRect barFrame = _BarFrame(&fontHeight); 287 BRect outerFrame = barFrame.InsetByCopy(-2, -2); 288 289 BRegion background(updateRect); 290 background.Exclude(outerFrame); 291 FillRegion(&background, B_SOLID_LOW); 292 293 // Draw labels/texts 294 295 BRect rect = outerFrame; 296 rect.top = 0; 297 rect.bottom = outerFrame.top - 1; 298 299 if (updateRect.Intersects(rect)) { 300 // update labels 301 BString leftText; 302 leftText << fLabel << fText; 303 304 BString rightText; 305 rightText << fTrailingText << fTrailingLabel; 306 307 float baseLine = ceilf(fontHeight.ascent) + 1; 308 fTextDivider = rect.right; 309 310 BFont font; 311 GetFont(&font); 312 313 if (rightText.Length()) { 314 font.TruncateString(&rightText, B_TRUNCATE_BEGINNING, rect.Width()); 315 fTextDivider -= StringWidth(rightText.String()); 316 } 317 318 if (leftText.Length()) { 319 float width = max_c(0.0, fTextDivider - rect.left); 320 font.TruncateString(&leftText, B_TRUNCATE_END, width); 321 } 322 323 SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 324 325 if (leftText.Length()) 326 DrawString(leftText.String(), BPoint(rect.left, baseLine)); 327 328 if (rightText.Length()) 329 DrawString(rightText.String(), BPoint(fTextDivider, baseLine)); 330 331 } 332 333 // Draw bar 334 335 if (!updateRect.Intersects(outerFrame)) 336 return; 337 338 rect = outerFrame; 339 340 if (be_control_look != NULL) { 341 be_control_look->DrawStatusBar(this, rect, updateRect, 342 backgroundColor, fBarColor, _BarPosition(barFrame)); 343 return; 344 } 345 346 // First bevel 347 SetHighColor(tint_color(ui_color ( B_PANEL_BACKGROUND_COLOR ), B_DARKEN_1_TINT)); 348 StrokeLine(rect.LeftBottom(), rect.LeftTop()); 349 StrokeLine(rect.RightTop()); 350 351 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_LIGHTEN_2_TINT)); 352 StrokeLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom()); 353 StrokeLine(BPoint(rect.right, rect.top + 1)); 354 355 rect.InsetBy(1, 1); 356 357 // Second bevel 358 SetHighColor(tint_color(ui_color ( B_PANEL_BACKGROUND_COLOR ), B_DARKEN_4_TINT)); 359 StrokeLine(rect.LeftBottom(), rect.LeftTop()); 360 StrokeLine(rect.RightTop()); 361 362 SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 363 StrokeLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom()); 364 StrokeLine(BPoint(rect.right, rect.top + 1)); 365 366 rect = barFrame; 367 rect.right = _BarPosition(barFrame); 368 369 // draw bar itself 370 371 if (rect.right >= rect.left) { 372 // Bevel 373 SetHighColor(tint_color(fBarColor, B_LIGHTEN_2_TINT)); 374 StrokeLine(rect.LeftBottom(), rect.LeftTop()); 375 StrokeLine(rect.RightTop()); 376 377 SetHighColor(tint_color(fBarColor, B_DARKEN_2_TINT)); 378 StrokeLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom()); 379 StrokeLine(BPoint(rect.right, rect.top + 1)); 380 381 // filling 382 SetHighColor(fBarColor); 383 FillRect(rect.InsetByCopy(1, 1)); 384 } 385 386 if (rect.right < barFrame.right) { 387 // empty space 388 rect.left = rect.right + 1; 389 rect.right = barFrame.right; 390 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_LIGHTEN_MAX_TINT)); 391 FillRect(rect); 392 } 393 } 394 395 396 void 397 BStatusBar::MessageReceived(BMessage *message) 398 { 399 switch(message->what) { 400 case B_UPDATE_STATUS_BAR: 401 { 402 float delta; 403 const char *text = NULL, *trailing_text = NULL; 404 405 message->FindFloat("delta", &delta); 406 message->FindString("text", &text); 407 message->FindString("trailing_text", &trailing_text); 408 409 Update(delta, text, trailing_text); 410 411 break; 412 } 413 414 case B_RESET_STATUS_BAR: 415 { 416 const char *label = NULL, *trailing_label = NULL; 417 418 message->FindString("label", &label); 419 message->FindString("trailing_label", &trailing_label); 420 421 Reset(label, trailing_label); 422 423 break; 424 } 425 426 default: 427 BView::MessageReceived(message); 428 break; 429 } 430 } 431 432 433 void 434 BStatusBar::MouseDown(BPoint point) 435 { 436 BView::MouseDown(point); 437 } 438 439 440 void 441 BStatusBar::MouseUp(BPoint point) 442 { 443 BView::MouseUp(point); 444 } 445 446 447 void 448 BStatusBar::MouseMoved(BPoint point, uint32 transit, const BMessage *message) 449 { 450 BView::MouseMoved(point, transit, message); 451 } 452 453 454 // #pragma mark - 455 456 457 void 458 BStatusBar::SetBarColor(rgb_color color) 459 { 460 fBarColor = color; 461 462 Invalidate(); 463 } 464 465 466 void 467 BStatusBar::SetBarHeight(float barHeight) 468 { 469 float oldHeight = BarHeight(); 470 471 fCustomBarHeight = true; 472 fBarHeight = barHeight; 473 474 if (barHeight == oldHeight) 475 return; 476 477 // resize so that the height fits 478 float width, height; 479 GetPreferredSize(&width, &height); 480 ResizeTo(Bounds().Width(), height); 481 } 482 483 484 void 485 BStatusBar::SetText(const char* string) 486 { 487 _SetTextData(fText, string, fLabel, false); 488 } 489 490 491 void 492 BStatusBar::SetTrailingText(const char* string) 493 { 494 _SetTextData(fTrailingText, string, fTrailingLabel, true); 495 } 496 497 498 void 499 BStatusBar::SetMaxValue(float max) 500 { 501 // R5 and/or Zeta's SetMaxValue does not trigger an invalidate here. 502 // this is probably not ideal behavior, but it does break apps in some cases 503 // as observed with SpaceMonitor. 504 // TODO: revisit this when we break binary compatibility 505 fMax = max; 506 } 507 508 509 void 510 BStatusBar::Update(float delta, const char* text, const char* trailingText) 511 { 512 // If any of these are NULL, the existing text remains (BeBook) 513 if (text == NULL) 514 text = fText.String(); 515 if (trailingText == NULL) 516 trailingText = fTrailingText.String(); 517 BStatusBar::SetTo(fCurrent + delta, text, trailingText); 518 } 519 520 521 void 522 BStatusBar::Reset(const char *label, const char *trailingLabel) 523 { 524 // Reset replaces the label and trailing label with copies of the 525 // strings passed as arguments. If either argument is NULL, the 526 // label or trailing label will be deleted and erased. 527 fLabel = label ? label : ""; 528 fTrailingLabel = trailingLabel ? trailingLabel : ""; 529 530 // Reset deletes and erases any text or trailing text 531 fText = ""; 532 fTrailingText = ""; 533 534 fCurrent = 0; 535 fMax = 100; 536 537 Invalidate(); 538 } 539 540 541 void 542 BStatusBar::SetTo(float value, const char* text, const char* trailingText) 543 { 544 SetText(text); 545 SetTrailingText(trailingText); 546 547 if (value > fMax) 548 value = fMax; 549 else if (value < 0) 550 value = 0; 551 if (value == fCurrent) 552 return; 553 554 BRect barFrame = _BarFrame(); 555 float oldPosition = _BarPosition(barFrame); 556 557 fCurrent = value; 558 559 float newPosition = _BarPosition(barFrame); 560 if (oldPosition == newPosition) 561 return; 562 563 // update only the part of the status bar with actual changes 564 BRect update = barFrame; 565 if (oldPosition < newPosition) { 566 update.left = floorf(max_c(oldPosition - 1, update.left)); 567 update.right = ceilf(newPosition); 568 } else { 569 update.left = floorf(max_c(newPosition - 1, update.left)); 570 update.right = ceilf(oldPosition); 571 } 572 573 // TODO: Ask the BControlLook in the first place about dirty rect. 574 if (be_control_look) 575 update.InsetBy(-1, -1); 576 577 Invalidate(update); 578 } 579 580 581 float 582 BStatusBar::CurrentValue() const 583 { 584 return fCurrent; 585 } 586 587 588 float 589 BStatusBar::MaxValue() const 590 { 591 return fMax; 592 } 593 594 595 rgb_color 596 BStatusBar::BarColor() const 597 { 598 return fBarColor; 599 } 600 601 602 float 603 BStatusBar::BarHeight() const 604 { 605 if (!fCustomBarHeight && fBarHeight == -1) { 606 // the default bar height is as height as the label 607 font_height fontHeight; 608 GetFontHeight(&fontHeight); 609 const_cast<BStatusBar *>(this)->fBarHeight = fontHeight.ascent 610 + fontHeight.descent + 5; 611 } 612 613 return ceilf(fBarHeight); 614 } 615 616 617 const char * 618 BStatusBar::Text() const 619 { 620 return fText.String(); 621 } 622 623 624 const char * 625 BStatusBar::TrailingText() const 626 { 627 return fTrailingText.String(); 628 } 629 630 631 const char * 632 BStatusBar::Label() const 633 { 634 return fLabel.String(); 635 } 636 637 638 const char * 639 BStatusBar::TrailingLabel() const 640 { 641 return fTrailingLabel.String(); 642 } 643 644 645 // #pragma mark - 646 647 648 BHandler * 649 BStatusBar::ResolveSpecifier(BMessage* message, int32 index, 650 BMessage* specifier, int32 what, const char *property) 651 { 652 return BView::ResolveSpecifier(message, index, specifier, what, property); 653 } 654 655 656 status_t 657 BStatusBar::GetSupportedSuites(BMessage* data) 658 { 659 return BView::GetSupportedSuites(data); 660 } 661 662 663 status_t 664 BStatusBar::Perform(perform_code code, void* _data) 665 { 666 switch (code) { 667 case PERFORM_CODE_MIN_SIZE: 668 ((perform_data_min_size*)_data)->return_value 669 = BStatusBar::MinSize(); 670 return B_OK; 671 case PERFORM_CODE_MAX_SIZE: 672 ((perform_data_max_size*)_data)->return_value 673 = BStatusBar::MaxSize(); 674 return B_OK; 675 case PERFORM_CODE_PREFERRED_SIZE: 676 ((perform_data_preferred_size*)_data)->return_value 677 = BStatusBar::PreferredSize(); 678 return B_OK; 679 case PERFORM_CODE_LAYOUT_ALIGNMENT: 680 ((perform_data_layout_alignment*)_data)->return_value 681 = BStatusBar::LayoutAlignment(); 682 return B_OK; 683 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 684 ((perform_data_has_height_for_width*)_data)->return_value 685 = BStatusBar::HasHeightForWidth(); 686 return B_OK; 687 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 688 { 689 perform_data_get_height_for_width* data 690 = (perform_data_get_height_for_width*)_data; 691 BStatusBar::GetHeightForWidth(data->width, &data->min, &data->max, 692 &data->preferred); 693 return B_OK; 694 } 695 case PERFORM_CODE_SET_LAYOUT: 696 { 697 perform_data_set_layout* data = (perform_data_set_layout*)_data; 698 BStatusBar::SetLayout(data->layout); 699 return B_OK; 700 } 701 case PERFORM_CODE_INVALIDATE_LAYOUT: 702 { 703 perform_data_invalidate_layout* data 704 = (perform_data_invalidate_layout*)_data; 705 BStatusBar::InvalidateLayout(data->descendants); 706 return B_OK; 707 } 708 case PERFORM_CODE_DO_LAYOUT: 709 { 710 BStatusBar::DoLayout(); 711 return B_OK; 712 } 713 } 714 715 return BView::Perform(code, _data); 716 } 717 718 719 // #pragma mark - 720 721 722 extern "C" void 723 _ReservedStatusBar1__10BStatusBar(BStatusBar* self, float value, 724 const char* text, const char* trailingText) 725 { 726 self->BStatusBar::SetTo(value, text, trailingText); 727 } 728 729 730 void BStatusBar::_ReservedStatusBar2() {} 731 void BStatusBar::_ReservedStatusBar3() {} 732 void BStatusBar::_ReservedStatusBar4() {} 733 734 735 BStatusBar & 736 BStatusBar::operator=(const BStatusBar& other) 737 { 738 return *this; 739 } 740 741 742 // #pragma mark - 743 744 745 void 746 BStatusBar::_InitObject() 747 { 748 fMax = 100.0; 749 fCurrent = 0.0; 750 751 fBarHeight = -1.0; 752 fTextDivider = Bounds().Width(); 753 754 fBarColor = kDefaultBarColor; 755 fCustomBarHeight = false; 756 757 SetFlags(Flags() | B_FRAME_EVENTS); 758 } 759 760 761 void 762 BStatusBar::_SetTextData(BString& text, const char* source, 763 const BString& combineWith, bool rightAligned) 764 { 765 if (source == NULL) 766 source = ""; 767 768 // If there were no changes, we don't have to do anything 769 if (text == source) 770 return; 771 772 float oldWidth; 773 if (rightAligned) 774 oldWidth = Bounds().right - fTextDivider; 775 else 776 oldWidth = fTextDivider; 777 778 text = source; 779 780 BString newString; 781 if (rightAligned) 782 newString << text << combineWith; 783 else 784 newString << combineWith << text; 785 786 float newWidth = ceilf(StringWidth(newString.String())); 787 // might still be smaller in Draw(), but we use it 788 // only for the invalidation rect here 789 790 // determine which part of the view needs an update 791 float invalidateWidth = max_c(newWidth, oldWidth); 792 793 float position = 0.0; 794 if (rightAligned) 795 position = Bounds().right - invalidateWidth; 796 797 font_height fontHeight; 798 GetFontHeight(&fontHeight); 799 800 // Invalidate(BRect(position, 0, position + invalidateWidth, 801 // ceilf(fontHeight.ascent) + ceilf(fontHeight.descent))); 802 // TODO: redrawing the entire area takes care of the edge case 803 // where the left side string changes because of truncation and 804 // part of it needs to be redrawn as well. 805 Invalidate(BRect(0, 0, Bounds().right, 806 ceilf(fontHeight.ascent) + ceilf(fontHeight.descent))); 807 } 808 809 810 /*! 811 Returns the inner bar frame without the surrounding bevel. 812 */ 813 BRect 814 BStatusBar::_BarFrame(const font_height* fontHeight) const 815 { 816 float top; 817 if (fontHeight == NULL) { 818 font_height height; 819 GetFontHeight(&height); 820 top = ceilf(height.ascent + height.descent) + 6; 821 } else 822 top = ceilf(fontHeight->ascent + fontHeight->descent) + 6; 823 824 return BRect(2, top, Bounds().right - 2, top + BarHeight() - 4); 825 } 826 827 828 float 829 BStatusBar::_BarPosition(const BRect& barFrame) const 830 { 831 if (fCurrent == 0) 832 return barFrame.left - 1; 833 834 return roundf(barFrame.left - 1 835 + (fCurrent * (barFrame.Width() + 3) / fMax)); 836 } 837 838