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