/* * Copyright 2001-2015, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Marc Flerackers (mflerackers@androme.be) * Axel Dörfler, axeld@pinc-software.de * Stephan Aßmus * Joseph Groover */ /*! BStatusBar displays a "percentage-of-completion" gauge. */ #include #include #include #include #include #include #include #include #include #include enum internalFlags { kCustomBarColor = 1 }; BStatusBar::BStatusBar(BRect frame, const char *name, const char *label, const char *trailingLabel) : BView(frame, name, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW), fLabel(label), fTrailingLabel(trailingLabel) { _InitObject(); } BStatusBar::BStatusBar(const char *name, const char *label, const char *trailingLabel) : BView(BRect(0, 0, -1, -1), name, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW | B_SUPPORTS_LAYOUT), fLabel(label), fTrailingLabel(trailingLabel) { _InitObject(); } BStatusBar::BStatusBar(BMessage *archive) : BView(archive) { _InitObject(); archive->FindString("_label", &fLabel); archive->FindString("_tlabel", &fTrailingLabel); archive->FindString("_text", &fText); archive->FindString("_ttext", &fTrailingText); float floatValue; if (archive->FindFloat("_high", &floatValue) == B_OK) { fBarHeight = floatValue; fCustomBarHeight = true; } int32 color; if (archive->FindInt32("_bcolor", (int32 *)&color) == B_OK) { fBarColor = *(rgb_color *)&color; fInternalFlags |= kCustomBarColor; } if (archive->FindFloat("_val", &floatValue) == B_OK) fCurrent = floatValue; if (archive->FindFloat("_max", &floatValue) == B_OK) fMax = floatValue; } BStatusBar::~BStatusBar() { } BArchivable * BStatusBar::Instantiate(BMessage *archive) { if (validate_instantiation(archive, "BStatusBar")) return new BStatusBar(archive); return NULL; } status_t BStatusBar::Archive(BMessage *archive, bool deep) const { status_t err = BView::Archive(archive, deep); if (err < B_OK) return err; if (fCustomBarHeight) err = archive->AddFloat("_high", fBarHeight); if (err == B_OK && fInternalFlags & kCustomBarColor) err = archive->AddInt32("_bcolor", (const uint32 &)fBarColor); if (err == B_OK && fCurrent != 0) err = archive->AddFloat("_val", fCurrent); if (err == B_OK && fMax != 100 ) err = archive->AddFloat("_max", fMax); if (err == B_OK && fText.Length()) err = archive->AddString("_text", fText); if (err == B_OK && fTrailingText.Length()) err = archive->AddString("_ttext", fTrailingText); if (err == B_OK && fLabel.Length()) err = archive->AddString("_label", fLabel); if (err == B_OK && fTrailingLabel.Length()) err = archive->AddString ("_tlabel", fTrailingLabel); return err; } // #pragma mark - void BStatusBar::AttachedToWindow() { // resize so that the height fits float width, height; GetPreferredSize(&width, &height); ResizeTo(Bounds().Width(), height); SetViewColor(B_TRANSPARENT_COLOR); AdoptParentColors(); fTextDivider = Bounds().Width(); if ((fInternalFlags & kCustomBarColor) == 0) fBarColor = ui_color(B_STATUS_BAR_COLOR); } void BStatusBar::DetachedFromWindow() { BView::DetachedFromWindow(); } void BStatusBar::AllAttached() { BView::AllAttached(); } void BStatusBar::AllDetached() { BView::AllDetached(); } // #pragma mark - void BStatusBar::WindowActivated(bool state) { BView::WindowActivated(state); } void BStatusBar::MakeFocus(bool state) { BView::MakeFocus(state); } // #pragma mark - void BStatusBar::GetPreferredSize(float* _width, float* _height) { if (_width) { // AttachedToWindow() might not have been called yet *_width = ceilf(StringWidth(fLabel.String())) + ceilf(StringWidth(fTrailingLabel.String())) + ceilf(StringWidth(fText.String())) + ceilf(StringWidth(fTrailingText.String())) + 5; } if (_height) { float labelHeight = 0; if (_HasText()) { font_height fontHeight; GetFontHeight(&fontHeight); labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 6; } *_height = labelHeight + BarHeight(); } } BSize BStatusBar::MinSize() { float width, height; GetPreferredSize(&width, &height); return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(width, height)); } BSize BStatusBar::MaxSize() { float width, height; GetPreferredSize(&width, &height); return BLayoutUtils::ComposeSize(ExplicitMaxSize(), BSize(B_SIZE_UNLIMITED, height)); } BSize BStatusBar::PreferredSize() { float width, height; GetPreferredSize(&width, &height); return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(width, height)); } void BStatusBar::ResizeToPreferred() { BView::ResizeToPreferred(); } void BStatusBar::FrameMoved(BPoint newPosition) { BView::FrameMoved(newPosition); } void BStatusBar::FrameResized(float newWidth, float newHeight) { BView::FrameResized(newWidth, newHeight); Invalidate(); } // #pragma mark - void BStatusBar::Draw(BRect updateRect) { rgb_color backgroundColor = LowColor(); font_height fontHeight; GetFontHeight(&fontHeight); BRect barFrame = _BarFrame(&fontHeight); BRect outerFrame = barFrame.InsetByCopy(-2, -2); BRegion background(updateRect); background.Exclude(outerFrame); FillRegion(&background, B_SOLID_LOW); // Draw labels/texts BRect rect = outerFrame; rect.top = 0; rect.bottom = outerFrame.top - 1; if (updateRect.Intersects(rect)) { // update labels BString leftText; leftText << fLabel << fText; BString rightText; rightText << fTrailingText << fTrailingLabel; float baseLine = ceilf(fontHeight.ascent) + 1; fTextDivider = rect.right; BFont font; GetFont(&font); if (rightText.Length()) { font.TruncateString(&rightText, B_TRUNCATE_BEGINNING, rect.Width()); fTextDivider -= StringWidth(rightText.String()); } if (leftText.Length()) { float width = max_c(0.0, fTextDivider - rect.left); font.TruncateString(&leftText, B_TRUNCATE_END, width); } rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR); if (backgroundColor != ui_color(B_PANEL_BACKGROUND_COLOR)) { if (backgroundColor.IsLight()) textColor = make_color(0, 0, 0, 255); else textColor = make_color(255, 255, 255, 255); } SetHighColor(textColor); if (leftText.Length()) DrawString(leftText.String(), BPoint(rect.left, baseLine)); if (rightText.Length()) DrawString(rightText.String(), BPoint(fTextDivider, baseLine)); } // Draw bar if (!updateRect.Intersects(outerFrame)) return; rect = outerFrame; be_control_look->DrawStatusBar(this, rect, updateRect, backgroundColor, fBarColor, _BarPosition(barFrame)); } void BStatusBar::MessageReceived(BMessage *message) { switch(message->what) { case B_UPDATE_STATUS_BAR: { float delta; const char *text = NULL, *trailing_text = NULL; message->FindFloat("delta", &delta); message->FindString("text", &text); message->FindString("trailing_text", &trailing_text); Update(delta, text, trailing_text); break; } case B_RESET_STATUS_BAR: { const char *label = NULL, *trailing_label = NULL; message->FindString("label", &label); message->FindString("trailing_label", &trailing_label); Reset(label, trailing_label); break; } case B_COLORS_UPDATED: { // Change the bar color IF we don't have an application-set color. if ((fInternalFlags & kCustomBarColor) == 0) { message->FindColor(ui_color_name(B_STATUS_BAR_COLOR), &fBarColor); } break; } default: BView::MessageReceived(message); break; } } void BStatusBar::MouseDown(BPoint point) { BView::MouseDown(point); } void BStatusBar::MouseUp(BPoint point) { BView::MouseUp(point); } void BStatusBar::MouseMoved(BPoint point, uint32 transit, const BMessage *message) { BView::MouseMoved(point, transit, message); } // #pragma mark - void BStatusBar::SetBarColor(rgb_color color) { fInternalFlags |= kCustomBarColor; fBarColor = color; Invalidate(); } void BStatusBar::SetBarHeight(float barHeight) { float oldHeight = BarHeight(); fCustomBarHeight = true; fBarHeight = barHeight; if (barHeight == oldHeight) return; // resize so that the height fits if ((Flags() & B_SUPPORTS_LAYOUT) != 0) InvalidateLayout(); else { float width, height; GetPreferredSize(&width, &height); ResizeTo(Bounds().Width(), height); } } void BStatusBar::SetText(const char* string) { _SetTextData(fText, string, fLabel, false); } void BStatusBar::SetTrailingText(const char* string) { _SetTextData(fTrailingText, string, fTrailingLabel, true); } void BStatusBar::SetMaxValue(float max) { // R5 and/or Zeta's SetMaxValue does not trigger an invalidate here. // this is probably not ideal behavior, but it does break apps in some cases // as observed with SpaceMonitor. // TODO: revisit this when we break binary compatibility fMax = max; } void BStatusBar::Update(float delta, const char* text, const char* trailingText) { // If any of these are NULL, the existing text remains (BeBook) if (text == NULL) text = fText.String(); if (trailingText == NULL) trailingText = fTrailingText.String(); BStatusBar::SetTo(fCurrent + delta, text, trailingText); } void BStatusBar::Reset(const char *label, const char *trailingLabel) { // Reset replaces the label and trailing label with copies of the // strings passed as arguments. If either argument is NULL, the // label or trailing label will be deleted and erased. fLabel = label ? label : ""; fTrailingLabel = trailingLabel ? trailingLabel : ""; // Reset deletes and erases any text or trailing text fText = ""; fTrailingText = ""; fCurrent = 0; fMax = 100; Invalidate(); } void BStatusBar::SetTo(float value, const char* text, const char* trailingText) { SetText(text); SetTrailingText(trailingText); if (value > fMax) value = fMax; else if (value < 0) value = 0; if (value == fCurrent) return; BRect barFrame = _BarFrame(); float oldPosition = _BarPosition(barFrame); fCurrent = value; float newPosition = _BarPosition(barFrame); if (oldPosition == newPosition) return; // update only the part of the status bar with actual changes BRect update = barFrame; if (oldPosition < newPosition) { update.left = floorf(max_c(oldPosition - 1, update.left)); update.right = ceilf(newPosition); } else { update.left = floorf(max_c(newPosition - 1, update.left)); update.right = ceilf(oldPosition); } // TODO: Ask the BControlLook in the first place about dirty rect. update.InsetBy(-1, -1); Invalidate(update); } float BStatusBar::CurrentValue() const { return fCurrent; } float BStatusBar::MaxValue() const { return fMax; } rgb_color BStatusBar::BarColor() const { return fBarColor; } float BStatusBar::BarHeight() const { if (!fCustomBarHeight && fBarHeight == -1) { // the default bar height is as height as the label font_height fontHeight; GetFontHeight(&fontHeight); const_cast(this)->fBarHeight = fontHeight.ascent + fontHeight.descent + 5; } return ceilf(fBarHeight); } const char * BStatusBar::Text() const { return fText.String(); } const char * BStatusBar::TrailingText() const { return fTrailingText.String(); } const char * BStatusBar::Label() const { return fLabel.String(); } const char * BStatusBar::TrailingLabel() const { return fTrailingLabel.String(); } // #pragma mark - BHandler * BStatusBar::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, int32 what, const char *property) { return BView::ResolveSpecifier(message, index, specifier, what, property); } status_t BStatusBar::GetSupportedSuites(BMessage* data) { return BView::GetSupportedSuites(data); } status_t BStatusBar::Perform(perform_code code, void* _data) { switch (code) { case PERFORM_CODE_MIN_SIZE: ((perform_data_min_size*)_data)->return_value = BStatusBar::MinSize(); return B_OK; case PERFORM_CODE_MAX_SIZE: ((perform_data_max_size*)_data)->return_value = BStatusBar::MaxSize(); return B_OK; case PERFORM_CODE_PREFERRED_SIZE: ((perform_data_preferred_size*)_data)->return_value = BStatusBar::PreferredSize(); return B_OK; case PERFORM_CODE_LAYOUT_ALIGNMENT: ((perform_data_layout_alignment*)_data)->return_value = BStatusBar::LayoutAlignment(); return B_OK; case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: ((perform_data_has_height_for_width*)_data)->return_value = BStatusBar::HasHeightForWidth(); return B_OK; case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: { perform_data_get_height_for_width* data = (perform_data_get_height_for_width*)_data; BStatusBar::GetHeightForWidth(data->width, &data->min, &data->max, &data->preferred); return B_OK; } case PERFORM_CODE_SET_LAYOUT: { perform_data_set_layout* data = (perform_data_set_layout*)_data; BStatusBar::SetLayout(data->layout); return B_OK; } case PERFORM_CODE_LAYOUT_INVALIDATED: { perform_data_layout_invalidated* data = (perform_data_layout_invalidated*)_data; BStatusBar::LayoutInvalidated(data->descendants); return B_OK; } case PERFORM_CODE_DO_LAYOUT: { BStatusBar::DoLayout(); return B_OK; } } return BView::Perform(code, _data); } // #pragma mark - extern "C" void _ReservedStatusBar1__10BStatusBar(BStatusBar* self, float value, const char* text, const char* trailingText) { self->BStatusBar::SetTo(value, text, trailingText); } void BStatusBar::_ReservedStatusBar2() {} void BStatusBar::_ReservedStatusBar3() {} void BStatusBar::_ReservedStatusBar4() {} BStatusBar & BStatusBar::operator=(const BStatusBar& other) { return *this; } // #pragma mark - void BStatusBar::_InitObject() { fMax = 100.0; fCurrent = 0.0; fBarHeight = -1.0; fTextDivider = Bounds().Width(); fCustomBarHeight = false; fInternalFlags = 0; SetFlags(Flags() | B_FRAME_EVENTS); } void BStatusBar::_SetTextData(BString& text, const char* source, const BString& combineWith, bool rightAligned) { if (source == NULL) source = ""; // If there were no changes, we don't have to do anything if (text == source) return; bool oldHasText = _HasText(); text = source; BString newString; if (rightAligned) newString << text << combineWith; else newString << combineWith << text; if (oldHasText != _HasText()) InvalidateLayout(); font_height fontHeight; GetFontHeight(&fontHeight); // Invalidate(BRect(position, 0, position + invalidateWidth, // ceilf(fontHeight.ascent) + ceilf(fontHeight.descent))); // TODO: redrawing the entire area takes care of the edge case // where the left side string changes because of truncation and // part of it needs to be redrawn as well. Invalidate(BRect(0, 0, Bounds().right, ceilf(fontHeight.ascent) + ceilf(fontHeight.descent))); } /*! Returns the inner bar frame without the surrounding bevel. */ BRect BStatusBar::_BarFrame(const font_height* fontHeight) const { float top = 2; if (_HasText()) { if (fontHeight == NULL) { font_height height; GetFontHeight(&height); top = ceilf(height.ascent + height.descent) + 6; } else top = ceilf(fontHeight->ascent + fontHeight->descent) + 6; } return BRect(2, top, Bounds().right - 2, top + BarHeight() - 4); } float BStatusBar::_BarPosition(const BRect& barFrame) const { if (fCurrent == 0) return barFrame.left - 1; return roundf(barFrame.left - 1 + (fCurrent * (barFrame.Width() + 3) / fMax)); } bool BStatusBar::_HasText() const { // Force BeOS behavior where the size of the BStatusBar always included // room for labels, even when there weren't any. if ((Flags() & B_SUPPORTS_LAYOUT) == 0) return true; return fLabel.Length() > 0 || fTrailingLabel.Length() > 0 || fTrailingText.Length() > 0 || fText.Length() > 0; }