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 float labelHeight = 0; 211 if (_HasText()) { 212 font_height fontHeight; 213 GetFontHeight(&fontHeight); 214 labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 6; 215 } 216 217 *_height = labelHeight + BarHeight(); 218 } 219 } 220 221 222 BSize 223 BStatusBar::MinSize() 224 { 225 float width, height; 226 GetPreferredSize(&width, &height); 227 228 return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(width, height)); 229 } 230 231 232 BSize 233 BStatusBar::MaxSize() 234 { 235 float width, height; 236 GetPreferredSize(&width, &height); 237 238 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 239 BSize(B_SIZE_UNLIMITED, height)); 240 } 241 242 243 BSize 244 BStatusBar::PreferredSize() 245 { 246 float width, height; 247 GetPreferredSize(&width, &height); 248 249 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), 250 BSize(width, height)); 251 } 252 253 254 void 255 BStatusBar::ResizeToPreferred() 256 { 257 BView::ResizeToPreferred(); 258 } 259 260 261 void 262 BStatusBar::FrameMoved(BPoint newPosition) 263 { 264 BView::FrameMoved(newPosition); 265 } 266 267 268 void 269 BStatusBar::FrameResized(float newWidth, float newHeight) 270 { 271 BView::FrameResized(newWidth, newHeight); 272 Invalidate(); 273 } 274 275 276 // #pragma mark - 277 278 279 void 280 BStatusBar::Draw(BRect updateRect) 281 { 282 rgb_color backgroundColor = LowColor(); 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 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) 479 InvalidateLayout(); 480 else { 481 float width, height; 482 GetPreferredSize(&width, &height); 483 ResizeTo(Bounds().Width(), height); 484 } 485 } 486 487 488 void 489 BStatusBar::SetText(const char* string) 490 { 491 _SetTextData(fText, string, fLabel, false); 492 } 493 494 495 void 496 BStatusBar::SetTrailingText(const char* string) 497 { 498 _SetTextData(fTrailingText, string, fTrailingLabel, true); 499 } 500 501 502 void 503 BStatusBar::SetMaxValue(float max) 504 { 505 // R5 and/or Zeta's SetMaxValue does not trigger an invalidate here. 506 // this is probably not ideal behavior, but it does break apps in some cases 507 // as observed with SpaceMonitor. 508 // TODO: revisit this when we break binary compatibility 509 fMax = max; 510 } 511 512 513 void 514 BStatusBar::Update(float delta, const char* text, const char* trailingText) 515 { 516 // If any of these are NULL, the existing text remains (BeBook) 517 if (text == NULL) 518 text = fText.String(); 519 if (trailingText == NULL) 520 trailingText = fTrailingText.String(); 521 BStatusBar::SetTo(fCurrent + delta, text, trailingText); 522 } 523 524 525 void 526 BStatusBar::Reset(const char *label, const char *trailingLabel) 527 { 528 // Reset replaces the label and trailing label with copies of the 529 // strings passed as arguments. If either argument is NULL, the 530 // label or trailing label will be deleted and erased. 531 fLabel = label ? label : ""; 532 fTrailingLabel = trailingLabel ? trailingLabel : ""; 533 534 // Reset deletes and erases any text or trailing text 535 fText = ""; 536 fTrailingText = ""; 537 538 fCurrent = 0; 539 fMax = 100; 540 541 Invalidate(); 542 } 543 544 545 void 546 BStatusBar::SetTo(float value, const char* text, const char* trailingText) 547 { 548 SetText(text); 549 SetTrailingText(trailingText); 550 551 if (value > fMax) 552 value = fMax; 553 else if (value < 0) 554 value = 0; 555 if (value == fCurrent) 556 return; 557 558 BRect barFrame = _BarFrame(); 559 float oldPosition = _BarPosition(barFrame); 560 561 fCurrent = value; 562 563 float newPosition = _BarPosition(barFrame); 564 if (oldPosition == newPosition) 565 return; 566 567 // update only the part of the status bar with actual changes 568 BRect update = barFrame; 569 if (oldPosition < newPosition) { 570 update.left = floorf(max_c(oldPosition - 1, update.left)); 571 update.right = ceilf(newPosition); 572 } else { 573 update.left = floorf(max_c(newPosition - 1, update.left)); 574 update.right = ceilf(oldPosition); 575 } 576 577 // TODO: Ask the BControlLook in the first place about dirty rect. 578 if (be_control_look != NULL) 579 update.InsetBy(-1, -1); 580 581 Invalidate(update); 582 } 583 584 585 float 586 BStatusBar::CurrentValue() const 587 { 588 return fCurrent; 589 } 590 591 592 float 593 BStatusBar::MaxValue() const 594 { 595 return fMax; 596 } 597 598 599 rgb_color 600 BStatusBar::BarColor() const 601 { 602 return fBarColor; 603 } 604 605 606 float 607 BStatusBar::BarHeight() const 608 { 609 if (!fCustomBarHeight && fBarHeight == -1) { 610 // the default bar height is as height as the label 611 font_height fontHeight; 612 GetFontHeight(&fontHeight); 613 const_cast<BStatusBar *>(this)->fBarHeight = fontHeight.ascent 614 + fontHeight.descent + 5; 615 } 616 617 return ceilf(fBarHeight); 618 } 619 620 621 const char * 622 BStatusBar::Text() const 623 { 624 return fText.String(); 625 } 626 627 628 const char * 629 BStatusBar::TrailingText() const 630 { 631 return fTrailingText.String(); 632 } 633 634 635 const char * 636 BStatusBar::Label() const 637 { 638 return fLabel.String(); 639 } 640 641 642 const char * 643 BStatusBar::TrailingLabel() const 644 { 645 return fTrailingLabel.String(); 646 } 647 648 649 // #pragma mark - 650 651 652 BHandler * 653 BStatusBar::ResolveSpecifier(BMessage* message, int32 index, 654 BMessage* specifier, int32 what, const char *property) 655 { 656 return BView::ResolveSpecifier(message, index, specifier, what, property); 657 } 658 659 660 status_t 661 BStatusBar::GetSupportedSuites(BMessage* data) 662 { 663 return BView::GetSupportedSuites(data); 664 } 665 666 667 status_t 668 BStatusBar::Perform(perform_code code, void* _data) 669 { 670 switch (code) { 671 case PERFORM_CODE_MIN_SIZE: 672 ((perform_data_min_size*)_data)->return_value 673 = BStatusBar::MinSize(); 674 return B_OK; 675 case PERFORM_CODE_MAX_SIZE: 676 ((perform_data_max_size*)_data)->return_value 677 = BStatusBar::MaxSize(); 678 return B_OK; 679 case PERFORM_CODE_PREFERRED_SIZE: 680 ((perform_data_preferred_size*)_data)->return_value 681 = BStatusBar::PreferredSize(); 682 return B_OK; 683 case PERFORM_CODE_LAYOUT_ALIGNMENT: 684 ((perform_data_layout_alignment*)_data)->return_value 685 = BStatusBar::LayoutAlignment(); 686 return B_OK; 687 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 688 ((perform_data_has_height_for_width*)_data)->return_value 689 = BStatusBar::HasHeightForWidth(); 690 return B_OK; 691 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 692 { 693 perform_data_get_height_for_width* data 694 = (perform_data_get_height_for_width*)_data; 695 BStatusBar::GetHeightForWidth(data->width, &data->min, &data->max, 696 &data->preferred); 697 return B_OK; 698 } 699 case PERFORM_CODE_SET_LAYOUT: 700 { 701 perform_data_set_layout* data = (perform_data_set_layout*)_data; 702 BStatusBar::SetLayout(data->layout); 703 return B_OK; 704 } 705 case PERFORM_CODE_LAYOUT_INVALIDATED: 706 { 707 perform_data_layout_invalidated* data 708 = (perform_data_layout_invalidated*)_data; 709 BStatusBar::LayoutInvalidated(data->descendants); 710 return B_OK; 711 } 712 case PERFORM_CODE_DO_LAYOUT: 713 { 714 BStatusBar::DoLayout(); 715 return B_OK; 716 } 717 } 718 719 return BView::Perform(code, _data); 720 } 721 722 723 // #pragma mark - 724 725 726 extern "C" void 727 _ReservedStatusBar1__10BStatusBar(BStatusBar* self, float value, 728 const char* text, const char* trailingText) 729 { 730 self->BStatusBar::SetTo(value, text, trailingText); 731 } 732 733 734 void BStatusBar::_ReservedStatusBar2() {} 735 void BStatusBar::_ReservedStatusBar3() {} 736 void BStatusBar::_ReservedStatusBar4() {} 737 738 739 BStatusBar & 740 BStatusBar::operator=(const BStatusBar& other) 741 { 742 return *this; 743 } 744 745 746 // #pragma mark - 747 748 749 void 750 BStatusBar::_InitObject() 751 { 752 fMax = 100.0; 753 fCurrent = 0.0; 754 755 fBarHeight = -1.0; 756 fTextDivider = Bounds().Width(); 757 758 fBarColor = kDefaultBarColor; 759 fCustomBarHeight = false; 760 761 SetFlags(Flags() | B_FRAME_EVENTS); 762 } 763 764 765 void 766 BStatusBar::_SetTextData(BString& text, const char* source, 767 const BString& combineWith, bool rightAligned) 768 { 769 if (source == NULL) 770 source = ""; 771 772 // If there were no changes, we don't have to do anything 773 if (text == source) 774 return; 775 776 bool oldHasText = _HasText(); 777 text = source; 778 779 BString newString; 780 if (rightAligned) 781 newString << text << combineWith; 782 else 783 newString << combineWith << text; 784 785 if (oldHasText != _HasText()) 786 InvalidateLayout(); 787 788 font_height fontHeight; 789 GetFontHeight(&fontHeight); 790 791 // Invalidate(BRect(position, 0, position + invalidateWidth, 792 // ceilf(fontHeight.ascent) + ceilf(fontHeight.descent))); 793 // TODO: redrawing the entire area takes care of the edge case 794 // where the left side string changes because of truncation and 795 // part of it needs to be redrawn as well. 796 Invalidate(BRect(0, 0, Bounds().right, 797 ceilf(fontHeight.ascent) + ceilf(fontHeight.descent))); 798 } 799 800 801 /*! 802 Returns the inner bar frame without the surrounding bevel. 803 */ 804 BRect 805 BStatusBar::_BarFrame(const font_height* fontHeight) const 806 { 807 float top = 2; 808 if (_HasText()) { 809 if (fontHeight == NULL) { 810 font_height height; 811 GetFontHeight(&height); 812 top = ceilf(height.ascent + height.descent) + 6; 813 } else 814 top = ceilf(fontHeight->ascent + fontHeight->descent) + 6; 815 } 816 817 return BRect(2, top, Bounds().right - 2, top + BarHeight() - 4); 818 } 819 820 821 float 822 BStatusBar::_BarPosition(const BRect& barFrame) const 823 { 824 if (fCurrent == 0) 825 return barFrame.left - 1; 826 827 return roundf(barFrame.left - 1 828 + (fCurrent * (barFrame.Width() + 3) / fMax)); 829 } 830 831 832 bool 833 BStatusBar::_HasText() const 834 { 835 // Force BeOS behavior where the size of the BStatusBar always included 836 // room for labels, even when there weren't any. 837 if ((Flags() & B_SUPPORTS_LAYOUT) == 0) 838 return true; 839 return fLabel.Length() > 0 || fTrailingLabel.Length() > 0 840 || fTrailingText.Length() > 0 || fText.Length() > 0; 841 } 842