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