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