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(ExplicitMaxSize(), 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(ExplicitMaxSize(), 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, rect.Width()); 314 fTextDivider -= StringWidth(rightText.String()); 315 } 316 317 if (leftText.Length()) { 318 float width = max_c(0.0, fTextDivider - rect.left); 319 font.TruncateString(&leftText, B_TRUNCATE_END, width); 320 } 321 322 SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 323 324 if (leftText.Length()) 325 DrawString(leftText.String(), BPoint(rect.left, baseLine)); 326 327 if (rightText.Length()) 328 DrawString(rightText.String(), BPoint(fTextDivider, baseLine)); 329 330 } 331 332 // Draw bar 333 334 if (!updateRect.Intersects(outerFrame)) 335 return; 336 337 rect = outerFrame; 338 339 if (be_control_look != NULL) { 340 be_control_look->DrawStatusBar(this, rect, updateRect, 341 backgroundColor, fBarColor, _BarPosition(barFrame)); 342 return; 343 } 344 345 // First bevel 346 SetHighColor(tint_color(ui_color ( B_PANEL_BACKGROUND_COLOR ), B_DARKEN_1_TINT)); 347 StrokeLine(rect.LeftBottom(), rect.LeftTop()); 348 StrokeLine(rect.RightTop()); 349 350 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_LIGHTEN_2_TINT)); 351 StrokeLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom()); 352 StrokeLine(BPoint(rect.right, rect.top + 1)); 353 354 rect.InsetBy(1, 1); 355 356 // Second bevel 357 SetHighColor(tint_color(ui_color ( B_PANEL_BACKGROUND_COLOR ), B_DARKEN_4_TINT)); 358 StrokeLine(rect.LeftBottom(), rect.LeftTop()); 359 StrokeLine(rect.RightTop()); 360 361 SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 362 StrokeLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom()); 363 StrokeLine(BPoint(rect.right, rect.top + 1)); 364 365 rect = barFrame; 366 rect.right = _BarPosition(barFrame); 367 368 // draw bar itself 369 370 if (rect.right >= rect.left) { 371 // Bevel 372 SetHighColor(tint_color(fBarColor, B_LIGHTEN_2_TINT)); 373 StrokeLine(rect.LeftBottom(), rect.LeftTop()); 374 StrokeLine(rect.RightTop()); 375 376 SetHighColor(tint_color(fBarColor, B_DARKEN_2_TINT)); 377 StrokeLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom()); 378 StrokeLine(BPoint(rect.right, rect.top + 1)); 379 380 // filling 381 SetHighColor(fBarColor); 382 FillRect(rect.InsetByCopy(1, 1)); 383 } 384 385 if (rect.right < barFrame.right) { 386 // empty space 387 rect.left = rect.right + 1; 388 rect.right = barFrame.right; 389 SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_LIGHTEN_MAX_TINT)); 390 FillRect(rect); 391 } 392 } 393 394 395 void 396 BStatusBar::MessageReceived(BMessage *message) 397 { 398 switch(message->what) { 399 case B_UPDATE_STATUS_BAR: 400 { 401 float delta; 402 const char *text = NULL, *trailing_text = NULL; 403 404 message->FindFloat("delta", &delta); 405 message->FindString("text", &text); 406 message->FindString("trailing_text", &trailing_text); 407 408 Update(delta, text, trailing_text); 409 410 break; 411 } 412 413 case B_RESET_STATUS_BAR: 414 { 415 const char *label = NULL, *trailing_label = NULL; 416 417 message->FindString("label", &label); 418 message->FindString("trailing_label", &trailing_label); 419 420 Reset(label, trailing_label); 421 422 break; 423 } 424 425 default: 426 BView::MessageReceived(message); 427 break; 428 } 429 } 430 431 432 void 433 BStatusBar::MouseDown(BPoint point) 434 { 435 BView::MouseDown(point); 436 } 437 438 439 void 440 BStatusBar::MouseUp(BPoint point) 441 { 442 BView::MouseUp(point); 443 } 444 445 446 void 447 BStatusBar::MouseMoved(BPoint point, uint32 transit, const BMessage *message) 448 { 449 BView::MouseMoved(point, transit, message); 450 } 451 452 453 // #pragma mark - 454 455 456 void 457 BStatusBar::SetBarColor(rgb_color color) 458 { 459 fBarColor = color; 460 461 Invalidate(); 462 } 463 464 465 void 466 BStatusBar::SetBarHeight(float barHeight) 467 { 468 float oldHeight = BarHeight(); 469 470 fCustomBarHeight = true; 471 fBarHeight = barHeight; 472 473 if (barHeight == oldHeight) 474 return; 475 476 // resize so that the height fits 477 if ((Flags() & B_SUPPORTS_LAYOUT) != 0) 478 InvalidateLayout(); 479 else { 480 float width, height; 481 GetPreferredSize(&width, &height); 482 ResizeTo(Bounds().Width(), height); 483 } 484 } 485 486 487 void 488 BStatusBar::SetText(const char* string) 489 { 490 _SetTextData(fText, string, fLabel, false); 491 } 492 493 494 void 495 BStatusBar::SetTrailingText(const char* string) 496 { 497 _SetTextData(fTrailingText, string, fTrailingLabel, true); 498 } 499 500 501 void 502 BStatusBar::SetMaxValue(float max) 503 { 504 // R5 and/or Zeta's SetMaxValue does not trigger an invalidate here. 505 // this is probably not ideal behavior, but it does break apps in some cases 506 // as observed with SpaceMonitor. 507 // TODO: revisit this when we break binary compatibility 508 fMax = max; 509 } 510 511 512 void 513 BStatusBar::Update(float delta, const char* text, const char* trailingText) 514 { 515 // If any of these are NULL, the existing text remains (BeBook) 516 if (text == NULL) 517 text = fText.String(); 518 if (trailingText == NULL) 519 trailingText = fTrailingText.String(); 520 BStatusBar::SetTo(fCurrent + delta, text, trailingText); 521 } 522 523 524 void 525 BStatusBar::Reset(const char *label, const char *trailingLabel) 526 { 527 // Reset replaces the label and trailing label with copies of the 528 // strings passed as arguments. If either argument is NULL, the 529 // label or trailing label will be deleted and erased. 530 fLabel = label ? label : ""; 531 fTrailingLabel = trailingLabel ? trailingLabel : ""; 532 533 // Reset deletes and erases any text or trailing text 534 fText = ""; 535 fTrailingText = ""; 536 537 fCurrent = 0; 538 fMax = 100; 539 540 Invalidate(); 541 } 542 543 544 void 545 BStatusBar::SetTo(float value, const char* text, const char* trailingText) 546 { 547 SetText(text); 548 SetTrailingText(trailingText); 549 550 if (value > fMax) 551 value = fMax; 552 else if (value < 0) 553 value = 0; 554 if (value == fCurrent) 555 return; 556 557 BRect barFrame = _BarFrame(); 558 float oldPosition = _BarPosition(barFrame); 559 560 fCurrent = value; 561 562 float newPosition = _BarPosition(barFrame); 563 if (oldPosition == newPosition) 564 return; 565 566 // update only the part of the status bar with actual changes 567 BRect update = barFrame; 568 if (oldPosition < newPosition) { 569 update.left = floorf(max_c(oldPosition - 1, update.left)); 570 update.right = ceilf(newPosition); 571 } else { 572 update.left = floorf(max_c(newPosition - 1, update.left)); 573 update.right = ceilf(oldPosition); 574 } 575 576 // TODO: Ask the BControlLook in the first place about dirty rect. 577 if (be_control_look) 578 update.InsetBy(-1, -1); 579 580 Invalidate(update); 581 } 582 583 584 float 585 BStatusBar::CurrentValue() const 586 { 587 return fCurrent; 588 } 589 590 591 float 592 BStatusBar::MaxValue() const 593 { 594 return fMax; 595 } 596 597 598 rgb_color 599 BStatusBar::BarColor() const 600 { 601 return fBarColor; 602 } 603 604 605 float 606 BStatusBar::BarHeight() const 607 { 608 if (!fCustomBarHeight && fBarHeight == -1) { 609 // the default bar height is as height as the label 610 font_height fontHeight; 611 GetFontHeight(&fontHeight); 612 const_cast<BStatusBar *>(this)->fBarHeight = fontHeight.ascent 613 + fontHeight.descent + 5; 614 } 615 616 return ceilf(fBarHeight); 617 } 618 619 620 const char * 621 BStatusBar::Text() const 622 { 623 return fText.String(); 624 } 625 626 627 const char * 628 BStatusBar::TrailingText() const 629 { 630 return fTrailingText.String(); 631 } 632 633 634 const char * 635 BStatusBar::Label() const 636 { 637 return fLabel.String(); 638 } 639 640 641 const char * 642 BStatusBar::TrailingLabel() const 643 { 644 return fTrailingLabel.String(); 645 } 646 647 648 // #pragma mark - 649 650 651 BHandler * 652 BStatusBar::ResolveSpecifier(BMessage* message, int32 index, 653 BMessage* specifier, int32 what, const char *property) 654 { 655 return BView::ResolveSpecifier(message, index, specifier, what, property); 656 } 657 658 659 status_t 660 BStatusBar::GetSupportedSuites(BMessage* data) 661 { 662 return BView::GetSupportedSuites(data); 663 } 664 665 666 status_t 667 BStatusBar::Perform(perform_code code, void* _data) 668 { 669 switch (code) { 670 case PERFORM_CODE_MIN_SIZE: 671 ((perform_data_min_size*)_data)->return_value 672 = BStatusBar::MinSize(); 673 return B_OK; 674 case PERFORM_CODE_MAX_SIZE: 675 ((perform_data_max_size*)_data)->return_value 676 = BStatusBar::MaxSize(); 677 return B_OK; 678 case PERFORM_CODE_PREFERRED_SIZE: 679 ((perform_data_preferred_size*)_data)->return_value 680 = BStatusBar::PreferredSize(); 681 return B_OK; 682 case PERFORM_CODE_LAYOUT_ALIGNMENT: 683 ((perform_data_layout_alignment*)_data)->return_value 684 = BStatusBar::LayoutAlignment(); 685 return B_OK; 686 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 687 ((perform_data_has_height_for_width*)_data)->return_value 688 = BStatusBar::HasHeightForWidth(); 689 return B_OK; 690 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 691 { 692 perform_data_get_height_for_width* data 693 = (perform_data_get_height_for_width*)_data; 694 BStatusBar::GetHeightForWidth(data->width, &data->min, &data->max, 695 &data->preferred); 696 return B_OK; 697 } 698 case PERFORM_CODE_SET_LAYOUT: 699 { 700 perform_data_set_layout* data = (perform_data_set_layout*)_data; 701 BStatusBar::SetLayout(data->layout); 702 return B_OK; 703 } 704 case PERFORM_CODE_LAYOUT_INVALIDATED: 705 { 706 perform_data_layout_invalidated* data 707 = (perform_data_layout_invalidated*)_data; 708 BStatusBar::LayoutInvalidated(data->descendants); 709 return B_OK; 710 } 711 case PERFORM_CODE_DO_LAYOUT: 712 { 713 BStatusBar::DoLayout(); 714 return B_OK; 715 } 716 } 717 718 return BView::Perform(code, _data); 719 } 720 721 722 // #pragma mark - 723 724 725 extern "C" void 726 _ReservedStatusBar1__10BStatusBar(BStatusBar* self, float value, 727 const char* text, const char* trailingText) 728 { 729 self->BStatusBar::SetTo(value, text, trailingText); 730 } 731 732 733 void BStatusBar::_ReservedStatusBar2() {} 734 void BStatusBar::_ReservedStatusBar3() {} 735 void BStatusBar::_ReservedStatusBar4() {} 736 737 738 BStatusBar & 739 BStatusBar::operator=(const BStatusBar& other) 740 { 741 return *this; 742 } 743 744 745 // #pragma mark - 746 747 748 void 749 BStatusBar::_InitObject() 750 { 751 fMax = 100.0; 752 fCurrent = 0.0; 753 754 fBarHeight = -1.0; 755 fTextDivider = Bounds().Width(); 756 757 fBarColor = kDefaultBarColor; 758 fCustomBarHeight = false; 759 760 SetFlags(Flags() | B_FRAME_EVENTS); 761 } 762 763 764 void 765 BStatusBar::_SetTextData(BString& text, const char* source, 766 const BString& combineWith, bool rightAligned) 767 { 768 if (source == NULL) 769 source = ""; 770 771 // If there were no changes, we don't have to do anything 772 if (text == source) 773 return; 774 775 bool oldHasText = _HasText(); 776 text = source; 777 778 BString newString; 779 if (rightAligned) 780 newString << text << combineWith; 781 else 782 newString << combineWith << text; 783 784 if (oldHasText != _HasText()) 785 InvalidateLayout(); 786 787 font_height fontHeight; 788 GetFontHeight(&fontHeight); 789 790 // Invalidate(BRect(position, 0, position + invalidateWidth, 791 // ceilf(fontHeight.ascent) + ceilf(fontHeight.descent))); 792 // TODO: redrawing the entire area takes care of the edge case 793 // where the left side string changes because of truncation and 794 // part of it needs to be redrawn as well. 795 Invalidate(BRect(0, 0, Bounds().right, 796 ceilf(fontHeight.ascent) + ceilf(fontHeight.descent))); 797 } 798 799 800 /*! 801 Returns the inner bar frame without the surrounding bevel. 802 */ 803 BRect 804 BStatusBar::_BarFrame(const font_height* fontHeight) const 805 { 806 float top = 2; 807 if (_HasText()) { 808 if (fontHeight == NULL) { 809 font_height height; 810 GetFontHeight(&height); 811 top = ceilf(height.ascent + height.descent) + 6; 812 } else 813 top = ceilf(fontHeight->ascent + fontHeight->descent) + 6; 814 } 815 816 return BRect(2, top, Bounds().right - 2, top + BarHeight() - 4); 817 } 818 819 820 float 821 BStatusBar::_BarPosition(const BRect& barFrame) const 822 { 823 if (fCurrent == 0) 824 return barFrame.left - 1; 825 826 return roundf(barFrame.left - 1 827 + (fCurrent * (barFrame.Width() + 3) / fMax)); 828 } 829 830 831 bool 832 BStatusBar::_HasText() const 833 { 834 // Force BeOS behavior where the size of the BStatusBar always included 835 // room for labels, even when there weren't any. 836 if ((Flags() & B_SUPPORTS_LAYOUT) == 0) 837 return true; 838 return fLabel.Length() > 0 || fTrailingLabel.Length() > 0 839 || fTrailingText.Length() > 0 || fText.Length() > 0; 840 } 841