/* * Copyright 2001-2016 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus * Axel Dörfler, axeld@pinc-software.de * Marc Flerackers (mflerackers@androme.be) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USE_OFF_SCREEN_VIEW 0 BSlider::BSlider( BRect frame, const char* name, const char* label, BMessage* message, int32 minValue, int32 maxValue, thumb_style thumbType, uint32 resizingMode, uint32 flags) : BControl(frame, name, label, message, resizingMode, flags), fModificationMessage(NULL), fSnoozeAmount(20000), fMinLimitLabel(NULL), fMaxLimitLabel(NULL), fMinValue(minValue), fMaxValue(maxValue), fKeyIncrementValue(1), fHashMarkCount(0), fHashMarks(B_HASH_MARKS_NONE), fStyle(thumbType), fOrientation(B_HORIZONTAL), fBarThickness(6.0) { _InitBarColor(); _InitObject(); SetValue(0); } BSlider::BSlider(BRect frame, const char* name, const char* label, BMessage* message, int32 minValue, int32 maxValue, orientation posture, thumb_style thumbType, uint32 resizingMode, uint32 flags) : BControl(frame, name, label, message, resizingMode, flags), fModificationMessage(NULL), fSnoozeAmount(20000), fMinLimitLabel(NULL), fMaxLimitLabel(NULL), fMinValue(minValue), fMaxValue(maxValue), fKeyIncrementValue(1), fHashMarkCount(0), fHashMarks(B_HASH_MARKS_NONE), fStyle(thumbType), fOrientation(posture), fBarThickness(6.0) { _InitBarColor(); _InitObject(); SetValue(0); } BSlider::BSlider(const char* name, const char* label, BMessage* message, int32 minValue, int32 maxValue, orientation posture, thumb_style thumbType, uint32 flags) : BControl(name, label, message, flags), fModificationMessage(NULL), fSnoozeAmount(20000), fMinLimitLabel(NULL), fMaxLimitLabel(NULL), fMinValue(minValue), fMaxValue(maxValue), fKeyIncrementValue(1), fHashMarkCount(0), fHashMarks(B_HASH_MARKS_NONE), fStyle(thumbType), fOrientation(posture), fBarThickness(6.0) { _InitBarColor(); _InitObject(); SetValue(0); } BSlider::BSlider(BMessage* archive) : BControl(archive) { fModificationMessage = NULL; if (archive->HasMessage("_mod_msg")) { BMessage* message = new BMessage; archive->FindMessage("_mod_msg", message); SetModificationMessage(message); } if (archive->FindInt32("_sdelay", &fSnoozeAmount) != B_OK) SetSnoozeAmount(20000); rgb_color color; if (archive->FindInt32("_fcolor", (int32*)&color) == B_OK) UseFillColor(true, &color); else UseFillColor(false); int32 orient; if (archive->FindInt32("_orient", &orient) == B_OK) fOrientation = (orientation)orient; else fOrientation = B_HORIZONTAL; fMinLimitLabel = NULL; fMaxLimitLabel = NULL; const char* minlbl = NULL; const char* maxlbl = NULL; archive->FindString("_minlbl", &minlbl); archive->FindString("_maxlbl", &maxlbl); SetLimitLabels(minlbl, maxlbl); if (archive->FindInt32("_min", &fMinValue) != B_OK) fMinValue = 0; if (archive->FindInt32("_max", &fMaxValue) != B_OK) fMaxValue = 100; if (archive->FindInt32("_incrementvalue", &fKeyIncrementValue) != B_OK) fKeyIncrementValue = 1; if (archive->FindInt32("_hashcount", &fHashMarkCount) != B_OK) fHashMarkCount = 11; int16 hashloc; if (archive->FindInt16("_hashloc", &hashloc) == B_OK) fHashMarks = (hash_mark_location)hashloc; else fHashMarks = B_HASH_MARKS_NONE; int16 sstyle; if (archive->FindInt16("_sstyle", &sstyle) == B_OK) fStyle = (thumb_style)sstyle; else fStyle = B_BLOCK_THUMB; if (archive->FindInt32("_bcolor", (int32*)&color) != B_OK) color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT); SetBarColor(color); float bthickness; if (archive->FindFloat("_bthickness", &bthickness) == B_OK) fBarThickness = bthickness; else fBarThickness = 6.0f; _InitObject(); } BSlider::~BSlider() { #if USE_OFF_SCREEN_VIEW delete fOffScreenBits; #endif delete fModificationMessage; free(fMinLimitLabel); free(fMaxLimitLabel); } void BSlider::_InitBarColor() { SetBarColor(be_control_look->SliderBarColor( ui_color(B_PANEL_BACKGROUND_COLOR))); UseFillColor(false, NULL); } void BSlider::_InitObject() { fLocation.x = 0; fLocation.y = 0; fInitialLocation.x = 0; fInitialLocation.y = 0; #if USE_OFF_SCREEN_VIEW fOffScreenBits = NULL; fOffScreenView = NULL; #endif fUpdateText = NULL; fMinSize.Set(-1, -1); fMaxUpdateTextWidth = -1.0; } BArchivable* BSlider::Instantiate(BMessage* archive) { if (validate_instantiation(archive, "BSlider")) return new BSlider(archive); return NULL; } status_t BSlider::Archive(BMessage* archive, bool deep) const { status_t ret = BControl::Archive(archive, deep); if (ModificationMessage() && ret == B_OK) ret = archive->AddMessage("_mod_msg", ModificationMessage()); if (ret == B_OK) ret = archive->AddInt32("_sdelay", fSnoozeAmount); if (ret == B_OK) ret = archive->AddInt32("_bcolor", (const uint32&)fBarColor); if (FillColor(NULL) && ret == B_OK) ret = archive->AddInt32("_fcolor", (const uint32&)fFillColor); if (ret == B_OK && fMinLimitLabel != NULL) ret = archive->AddString("_minlbl", fMinLimitLabel); if (ret == B_OK && fMaxLimitLabel != NULL) ret = archive->AddString("_maxlbl", fMaxLimitLabel); if (ret == B_OK) ret = archive->AddInt32("_min", fMinValue); if (ret == B_OK) ret = archive->AddInt32("_max", fMaxValue); if (ret == B_OK) ret = archive->AddInt32("_incrementvalue", fKeyIncrementValue); if (ret == B_OK) ret = archive->AddInt32("_hashcount", fHashMarkCount); if (ret == B_OK) ret = archive->AddInt16("_hashloc", fHashMarks); if (ret == B_OK) ret = archive->AddInt16("_sstyle", fStyle); if (ret == B_OK) ret = archive->AddInt32("_orient", fOrientation); if (ret == B_OK) ret = archive->AddFloat("_bthickness", fBarThickness); return ret; } status_t BSlider::Perform(perform_code code, void* _data) { switch (code) { case PERFORM_CODE_MIN_SIZE: ((perform_data_min_size*)_data)->return_value = BSlider::MinSize(); return B_OK; case PERFORM_CODE_MAX_SIZE: ((perform_data_max_size*)_data)->return_value = BSlider::MaxSize(); return B_OK; case PERFORM_CODE_PREFERRED_SIZE: ((perform_data_preferred_size*)_data)->return_value = BSlider::PreferredSize(); return B_OK; case PERFORM_CODE_LAYOUT_ALIGNMENT: ((perform_data_layout_alignment*)_data)->return_value = BSlider::LayoutAlignment(); return B_OK; case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: ((perform_data_has_height_for_width*)_data)->return_value = BSlider::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; BSlider::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; BSlider::SetLayout(data->layout); return B_OK; } case PERFORM_CODE_LAYOUT_INVALIDATED: { perform_data_layout_invalidated* data = (perform_data_layout_invalidated*)_data; BSlider::LayoutInvalidated(data->descendants); return B_OK; } case PERFORM_CODE_DO_LAYOUT: { BSlider::DoLayout(); return B_OK; } case PERFORM_CODE_SET_ICON: { perform_data_set_icon* data = (perform_data_set_icon*)_data; return BSlider::SetIcon(data->icon, data->flags); } } return BControl::Perform(code, _data); } void BSlider::WindowActivated(bool state) { BControl::WindowActivated(state); } void BSlider::AttachedToWindow() { ResizeToPreferred(); #if USE_OFF_SCREEN_VIEW BRect bounds(Bounds()); if (!fOffScreenView) { fOffScreenView = new BView(bounds, "", B_FOLLOW_ALL, B_WILL_DRAW); BFont font; GetFont(&font); fOffScreenView->SetFont(&font); } if (!fOffScreenBits) { fOffScreenBits = new BBitmap(bounds, B_RGBA32, true, false); if (fOffScreenBits && fOffScreenView) fOffScreenBits->AddChild(fOffScreenView); } else if (fOffScreenView) fOffScreenBits->AddChild(fOffScreenView); #endif // USE_OFF_SCREEN_VIEW BControl::AttachedToWindow(); BView* view = OffscreenView(); if (view != NULL && view->LockLooper()) { view->SetViewColor(B_TRANSPARENT_COLOR); if (LowUIColor() != B_NO_COLOR) view->SetLowUIColor(LowUIColor()); else view->SetLowColor(LowColor()); view->UnlockLooper(); } int32 value = Value(); SetValue(value); // makes sure the value is within valid bounds _SetLocationForValue(Value()); // makes sure the location is correct UpdateTextChanged(); } void BSlider::AllAttached() { BControl::AllAttached(); // When using a layout we may not have a parent, so we need to employ the // standard system colors manually. Due to how layouts work, this must // happen here, rather than in AttachedToWindow(). if (Parent() == NULL) SetLowUIColor(B_PANEL_BACKGROUND_COLOR); } void BSlider::AllDetached() { BControl::AllDetached(); } void BSlider::DetachedFromWindow() { BControl::DetachedFromWindow(); #if USE_OFF_SCREEN_VIEW if (fOffScreenBits) { delete fOffScreenBits; fOffScreenBits = NULL; fOffScreenView = NULL; } #endif } void BSlider::MessageReceived(BMessage* message) { BControl::MessageReceived(message); } void BSlider::FrameMoved(BPoint new_position) { BControl::FrameMoved(new_position); } void BSlider::FrameResized(float w,float h) { BControl::FrameResized(w, h); BRect bounds(Bounds()); if (bounds.right <= 0.0f || bounds.bottom <= 0.0f) return; #if USE_OFF_SCREEN_VIEW if (fOffScreenBits) { fOffScreenBits->RemoveChild(fOffScreenView); delete fOffScreenBits; fOffScreenView->ResizeTo(bounds.Width(), bounds.Height()); fOffScreenBits = new BBitmap(Bounds(), B_RGBA32, true, false); fOffScreenBits->AddChild(fOffScreenView); } #endif Invalidate(); } void BSlider::KeyDown(const char* bytes, int32 numBytes) { if (!IsEnabled() || IsHidden()) return; int32 newValue = Value(); switch (bytes[0]) { case B_LEFT_ARROW: case B_DOWN_ARROW: newValue -= KeyIncrementValue(); break; case B_RIGHT_ARROW: case B_UP_ARROW: newValue += KeyIncrementValue(); break; case B_HOME: newValue = fMinValue; break; case B_END: newValue = fMaxValue; break; default: BControl::KeyDown(bytes, numBytes); return; } if (newValue < fMinValue) newValue = fMinValue; if (newValue > fMaxValue) newValue = fMaxValue; if (newValue != Value()) { fInitialLocation = _Location(); SetValue(newValue); InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED); } } void BSlider::KeyUp(const char* bytes, int32 numBytes) { if (fInitialLocation != _Location()) { // The last KeyDown event triggered the modification message or no // notification at all, we may also have sent the modification message // continually while the user kept pressing the key. In either case, // finish with the final message to make the behavior consistent with // changing the value by mouse. Invoke(); } } /*! Makes sure the \a point is within valid bounds. Returns \c true if the relevant coordinate (depending on the orientation of the slider) differs from \a comparePoint. */ bool BSlider::_ConstrainPoint(BPoint& point, BPoint comparePoint) const { if (fOrientation == B_HORIZONTAL) { if (point.x != comparePoint.x) { if (point.x < _MinPosition()) point.x = _MinPosition(); else if (point.x > _MaxPosition()) point.x = _MaxPosition(); return true; } } else { if (point.y != comparePoint.y) { if (point.y > _MinPosition()) point.y = _MinPosition(); else if (point.y < _MaxPosition()) point.y = _MaxPosition(); return true; } } return false; } void BSlider::MouseDown(BPoint point) { if (!IsEnabled()) return; if (BarFrame().Contains(point) || ThumbFrame().Contains(point)) fInitialLocation = _Location(); uint32 buttons; GetMouse(&point, &buttons, true); _ConstrainPoint(point, fInitialLocation); SetValue(ValueForPoint(point)); if (_Location() != fInitialLocation) InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED); if (Window()->Flags() & B_ASYNCHRONOUS_CONTROLS) { SetTracking(true); SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY); } else { // synchronous mouse tracking BPoint prevPoint; while (buttons) { prevPoint = point; snooze(SnoozeAmount()); GetMouse(&point, &buttons, true); if (_ConstrainPoint(point, prevPoint)) { int32 value = ValueForPoint(point); if (value != Value()) { SetValue(value); InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED); } } } if (_Location() != fInitialLocation) Invoke(); } } void BSlider::MouseUp(BPoint point) { if (IsTracking()) { if (_Location() != fInitialLocation) Invoke(); SetTracking(false); } else BControl::MouseUp(point); } void BSlider::MouseMoved(BPoint point, uint32 transit, const BMessage* message) { if (IsTracking()) { if (_ConstrainPoint(point, _Location())) { int32 value = ValueForPoint(point); if (value != Value()) { SetValue(value); InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED); } } } else BControl::MouseMoved(point, transit, message); } void BSlider::Pulse() { BControl::Pulse(); } void BSlider::SetLabel(const char* label) { BControl::SetLabel(label); } void BSlider::SetLimitLabels(const char* minLabel, const char* maxLabel) { free(fMinLimitLabel); fMinLimitLabel = minLabel ? strdup(minLabel) : NULL; free(fMaxLimitLabel); fMaxLimitLabel = maxLabel ? strdup(maxLabel) : NULL; InvalidateLayout(); // TODO: This is for backwards compatibility and should // probably be removed when breaking binary compatiblity. // Applications like our own Mouse rely on this behavior. if ((Flags() & B_SUPPORTS_LAYOUT) == 0) ResizeToPreferred(); Invalidate(); } const char* BSlider::MinLimitLabel() const { return fMinLimitLabel; } const char* BSlider::MaxLimitLabel() const { return fMaxLimitLabel; } void BSlider::SetValue(int32 value) { if (value < fMinValue) value = fMinValue; if (value > fMaxValue) value = fMaxValue; if (value == Value()) return; _SetLocationForValue(value); BRect oldThumbFrame = ThumbFrame(); // While it would be enough to do this dependent on fUseFillColor, // that doesn't work out if DrawBar() has been overridden by a sub class if (fOrientation == B_HORIZONTAL) oldThumbFrame.top = BarFrame().top; else oldThumbFrame.left = BarFrame().left; BControl::SetValueNoUpdate(value); BRect invalid = oldThumbFrame | ThumbFrame(); if (Style() == B_TRIANGLE_THUMB) { // 1) We need to take care of pixels touched because of anti-aliasing. // 2) We need to update the region with the focus mark as well. (A // method BSlider::FocusMarkFrame() would be nice as well.) if (fOrientation == B_HORIZONTAL) { if (IsFocus()) invalid.bottom += 2; invalid.InsetBy(-1, 0); } else { if (IsFocus()) invalid.left -= 2; invalid.InsetBy(0, -1); } } Invalidate(invalid); UpdateTextChanged(); } int32 BSlider::ValueForPoint(BPoint location) const { float min; float max; float position; if (fOrientation == B_HORIZONTAL) { min = _MinPosition(); max = _MaxPosition(); position = location.x; } else { max = _MinPosition(); min = _MaxPosition(); position = min + (max - location.y); } if (position < min) position = min; if (position > max) position = max; return (int32)roundf(((position - min) * (fMaxValue - fMinValue) / (max - min)) + fMinValue); } void BSlider::SetPosition(float position) { if (position <= 0.0f) SetValue(fMinValue); else if (position >= 1.0f) SetValue(fMaxValue); else SetValue((int32)(position * (fMaxValue - fMinValue) + fMinValue)); } float BSlider::Position() const { float range = (float)(fMaxValue - fMinValue); if (range == 0.0f) range = 1.0f; return (float)(Value() - fMinValue) / range; } void BSlider::SetEnabled(bool on) { BControl::SetEnabled(on); } void BSlider::GetLimits(int32* minimum, int32* maximum) const { if (minimum != NULL) *minimum = fMinValue; if (maximum != NULL) *maximum = fMaxValue; } // #pragma mark - drawing void BSlider::Draw(BRect updateRect) { // clear out background BRegion background(updateRect); background.Exclude(BarFrame()); bool drawBackground = true; if (Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) { // This view is embedded somewhere, most likely the Tracker Desktop // shelf. drawBackground = false; } #if USE_OFF_SCREEN_VIEW if (!fOffScreenBits) return; if (fOffScreenBits->Lock()) { fOffScreenView->SetViewColor(ViewColor()); fOffScreenView->SetLowColor(LowColor()); #endif if (drawBackground && background.Frame().IsValid()) OffscreenView()->FillRegion(&background, B_SOLID_LOW); #if USE_OFF_SCREEN_VIEW fOffScreenView->Sync(); fOffScreenBits->Unlock(); } #endif DrawSlider(); } void BSlider::DrawSlider() { if (LockLooper()) { #if USE_OFF_SCREEN_VIEW if (fOffScreenBits == NULL) return; if (fOffScreenBits->Lock()) { #endif DrawBar(); DrawHashMarks(); DrawThumb(); DrawFocusMark(); DrawText(); #if USE_OFF_SCREEN_VIEW fOffScreenView->Sync(); fOffScreenBits->Unlock(); DrawBitmap(fOffScreenBits, B_ORIGIN); } #endif UnlockLooper(); } } void BSlider::DrawBar() { BRect frame = BarFrame(); BView* view = OffscreenView(); uint32 flags = be_control_look->Flags(this); rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); rgb_color rightFillColor = fBarColor; rgb_color leftFillColor = fUseFillColor ? fFillColor : fBarColor; be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor, rightFillColor, Position(), flags, fOrientation); } void BSlider::DrawHashMarks() { if (fHashMarks == B_HASH_MARKS_NONE) return; BRect frame = HashMarksFrame(); BView* view = OffscreenView(); rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); uint32 flags = be_control_look->Flags(this); be_control_look->DrawSliderHashMarks(view, frame, frame, base, fHashMarkCount, fHashMarks, flags, fOrientation); } void BSlider::DrawThumb() { if (Style() == B_BLOCK_THUMB) _DrawBlockThumb(); else _DrawTriangleThumb(); } void BSlider::DrawFocusMark() { if (!IsFocus()) return; OffscreenView()->SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); BRect frame = ThumbFrame(); if (fStyle == B_BLOCK_THUMB) { frame.left += 2.0f; frame.top += 2.0f; frame.right -= 3.0f; frame.bottom -= 3.0f; OffscreenView()->StrokeRect(frame); } else { if (fOrientation == B_HORIZONTAL) { OffscreenView()->StrokeLine(BPoint(frame.left, frame.bottom + 2.0f), BPoint(frame.right, frame.bottom + 2.0f)); } else { OffscreenView()->StrokeLine(BPoint(frame.left - 2.0f, frame.top), BPoint(frame.left - 2.0f, frame.bottom)); } } } void BSlider::DrawText() { BRect bounds(Bounds()); BView* view = OffscreenView(); rgb_color base = LowColor(); uint32 flags = be_control_look->Flags(this); // erase the is control flag before drawing the label so that the label // will get drawn using B_PANEL_TEXT_COLOR flags &= ~BControlLook::B_IS_CONTROL; font_height fontHeight; GetFontHeight(&fontHeight); if (Orientation() == B_HORIZONTAL) { if (Label() != NULL) { be_control_look->DrawLabel(view, Label(), base, flags, BPoint(0.0f, ceilf(fontHeight.ascent))); } // the update text is updated in SetValue() only if (fUpdateText != NULL) { be_control_look->DrawLabel(view, fUpdateText, base, flags, BPoint(bounds.right - StringWidth(fUpdateText), ceilf(fontHeight.ascent))); } if (fMinLimitLabel != NULL) { be_control_look->DrawLabel(view, fMinLimitLabel, base, flags, BPoint(0.0f, bounds.bottom - fontHeight.descent)); } if (fMaxLimitLabel != NULL) { be_control_look->DrawLabel(view, fMaxLimitLabel, base, flags, BPoint(bounds.right - StringWidth(fMaxLimitLabel), bounds.bottom - fontHeight.descent)); } } else { float lineHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent) + ceilf(fontHeight.leading); float baseLine = ceilf(fontHeight.ascent); if (Label() != NULL) { be_control_look->DrawLabel(view, Label(), base, flags, BPoint((bounds.Width() - StringWidth(Label())) / 2.0, baseLine)); baseLine += lineHeight; } if (fMaxLimitLabel != NULL) { be_control_look->DrawLabel(view, fMaxLimitLabel, base, flags, BPoint((bounds.Width() - StringWidth(fMaxLimitLabel)) / 2.0, baseLine)); } baseLine = bounds.bottom - ceilf(fontHeight.descent); if (fMinLimitLabel != NULL) { be_control_look->DrawLabel(view, fMinLimitLabel, base, flags, BPoint((bounds.Width() - StringWidth(fMinLimitLabel)) / 2.0, baseLine)); baseLine -= lineHeight; } if (fUpdateText != NULL) { be_control_look->DrawLabel(view, fUpdateText, base, flags, BPoint((bounds.Width() - StringWidth(fUpdateText)) / 2.0, baseLine)); } } } // #pragma mark - const char* BSlider::UpdateText() const { return NULL; } void BSlider::UpdateTextChanged() { // update text label float oldWidth = 0.0; if (fUpdateText != NULL) oldWidth = StringWidth(fUpdateText); const char* oldUpdateText = fUpdateText; fUpdateText = UpdateText(); bool updateTextOnOff = (fUpdateText == NULL && oldUpdateText != NULL) || (fUpdateText != NULL && oldUpdateText == NULL); float newWidth = 0.0; if (fUpdateText != NULL) newWidth = StringWidth(fUpdateText); float width = ceilf(std::max(newWidth, oldWidth)) + 2.0f; if (width != 0) { font_height fontHeight; GetFontHeight(&fontHeight); float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); float lineHeight = height + ceilf(fontHeight.leading); BRect invalid(Bounds()); if (fOrientation == B_HORIZONTAL) invalid = BRect(invalid.right - width, 0, invalid.right, height); else { if (!updateTextOnOff) { invalid.left = (invalid.left + invalid.right - width) / 2; invalid.right = invalid.left + width; if (fMinLimitLabel != NULL) invalid.bottom -= lineHeight; invalid.top = invalid.bottom - height; } } Invalidate(invalid); } float oldMaxUpdateTextWidth = fMaxUpdateTextWidth; fMaxUpdateTextWidth = MaxUpdateTextWidth(); if (oldMaxUpdateTextWidth != fMaxUpdateTextWidth) InvalidateLayout(); } BRect BSlider::BarFrame() const { BRect frame(Bounds()); font_height fontHeight; GetFontHeight(&fontHeight); float textHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); float leading = ceilf(fontHeight.leading); float thumbInset; if (fStyle == B_BLOCK_THUMB) thumbInset = 8.0; else thumbInset = 7.0; if (Orientation() == B_HORIZONTAL) { frame.left = thumbInset; frame.top = 6.0 + (Label() || fUpdateText ? textHeight + 4.0 : 0.0); frame.right -= thumbInset; frame.bottom = frame.top + fBarThickness; } else { frame.left = floorf((frame.Width() - fBarThickness) / 2.0); frame.top = thumbInset; if (Label() != NULL) frame.top += textHeight; if (fMaxLimitLabel != NULL) { frame.top += textHeight; if (Label()) frame.top += leading; } frame.right = frame.left + fBarThickness; frame.bottom = frame.bottom - thumbInset; if (fMinLimitLabel != NULL) frame.bottom -= textHeight; if (fUpdateText != NULL) { frame.bottom -= textHeight; if (fMinLimitLabel != NULL) frame.bottom -= leading; } } return frame; } BRect BSlider::HashMarksFrame() const { BRect frame(BarFrame()); if (fOrientation == B_HORIZONTAL) { frame.top -= 6.0; frame.bottom += 6.0; } else { frame.left -= 6.0; frame.right += 6.0; } return frame; } BRect BSlider::ThumbFrame() const { // TODO: The slider looks really ugly and broken when it is too little. // I would suggest using BarFrame() here to get the top and bottom coords // and spread them further apart for the thumb BRect frame = Bounds(); font_height fontHeight; GetFontHeight(&fontHeight); float textHeight = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); if (fStyle == B_BLOCK_THUMB) { if (Orientation() == B_HORIZONTAL) { frame.left = floorf(Position() * (_MaxPosition() - _MinPosition()) + _MinPosition()) - 8; frame.top = 2 + (Label() || fUpdateText ? textHeight + 4 : 0); frame.right = frame.left + 17; frame.bottom = frame.top + fBarThickness + 7; } else { frame.left = floor((frame.Width() - fBarThickness) / 2) - 4; frame.top = floorf(Position() * (_MaxPosition() - _MinPosition()) + _MinPosition()) - 8; frame.right = frame.left + fBarThickness + 7; frame.bottom = frame.top + 17; } } else { if (Orientation() == B_HORIZONTAL) { frame.left = floorf(Position() * (_MaxPosition() - _MinPosition()) + _MinPosition()) - 6; frame.right = frame.left + 12; frame.top = 3 + fBarThickness + (Label() ? textHeight + 4 : 0); frame.bottom = frame.top + 8; } else { frame.left = floorf((frame.Width() + fBarThickness) / 2) - 3; frame.top = floorf(Position() * (_MaxPosition() - _MinPosition())) + _MinPosition() - 6; frame.right = frame.left + 8; frame.bottom = frame.top + 12; } } return frame; } void BSlider::SetFlags(uint32 flags) { BControl::SetFlags(flags); } void BSlider::SetResizingMode(uint32 mode) { BControl::SetResizingMode(mode); } void BSlider::GetPreferredSize(float* _width, float* _height) { BSize preferredSize = PreferredSize(); if (Orientation() == B_HORIZONTAL) { if (_width != NULL) { // NOTE: For compatibility reasons, a horizontal BSlider // never shrinks horizontally. This only affects applications // which do not use the new layout system. *_width = std::max(Bounds().Width(), preferredSize.width); } if (_height != NULL) *_height = preferredSize.height; } else { if (_width != NULL) *_width = preferredSize.width; if (_height != NULL) { // NOTE: Similarly, a vertical BSlider never shrinks // vertically. This only affects applications which do not // use the new layout system. *_height = std::max(Bounds().Height(), preferredSize.height); } } } void BSlider::ResizeToPreferred() { BControl::ResizeToPreferred(); } status_t BSlider::Invoke(BMessage* message) { return BControl::Invoke(message); } BHandler* BSlider::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, int32 command, const char* property) { return BControl::ResolveSpecifier(message, index, specifier, command, property); } status_t BSlider::GetSupportedSuites(BMessage* message) { return BControl::GetSupportedSuites(message); } void BSlider::SetModificationMessage(BMessage* message) { delete fModificationMessage; fModificationMessage = message; } BMessage* BSlider::ModificationMessage() const { return fModificationMessage; } void BSlider::SetSnoozeAmount(int32 snoozeTime) { if (snoozeTime < 10000) snoozeTime = 10000; else if (snoozeTime > 1000000) snoozeTime = 1000000; fSnoozeAmount = snoozeTime; } int32 BSlider::SnoozeAmount() const { return fSnoozeAmount; } void BSlider::SetKeyIncrementValue(int32 incrementValue) { fKeyIncrementValue = incrementValue; } int32 BSlider::KeyIncrementValue() const { return fKeyIncrementValue; } void BSlider::SetHashMarkCount(int32 hashMarkCount) { fHashMarkCount = hashMarkCount; Invalidate(); } int32 BSlider::HashMarkCount() const { return fHashMarkCount; } void BSlider::SetHashMarks(hash_mark_location where) { fHashMarks = where; // TODO: enable if the hashmark look is influencing the control size! // InvalidateLayout(); Invalidate(); } hash_mark_location BSlider::HashMarks() const { return fHashMarks; } void BSlider::SetStyle(thumb_style style) { fStyle = style; InvalidateLayout(); Invalidate(); } thumb_style BSlider::Style() const { return fStyle; } void BSlider::SetBarColor(rgb_color barColor) { fBarColor = barColor; Invalidate(BarFrame()); } rgb_color BSlider::BarColor() const { return fBarColor; } void BSlider::UseFillColor(bool useFill, const rgb_color* barColor) { fUseFillColor = useFill; if (useFill && barColor) fFillColor = *barColor; Invalidate(BarFrame()); } bool BSlider::FillColor(rgb_color* barColor) const { if (barColor && fUseFillColor) *barColor = fFillColor; return fUseFillColor; } BView* BSlider::OffscreenView() const { #if USE_OFF_SCREEN_VIEW return fOffScreenView; #else return (BView*)this; #endif } orientation BSlider::Orientation() const { return fOrientation; } void BSlider::SetOrientation(orientation posture) { if (fOrientation == posture) return; fOrientation = posture; InvalidateLayout(); Invalidate(); } float BSlider::BarThickness() const { return fBarThickness; } void BSlider::SetBarThickness(float thickness) { if (thickness < 1.0) thickness = 1.0; else thickness = roundf(thickness); if (thickness != fBarThickness) { // calculate invalid barframe and extend by hashmark size float hInset = 0.0; float vInset = 0.0; if (fOrientation == B_HORIZONTAL) vInset = -6.0; else hInset = -6.0; BRect invalid = BarFrame().InsetByCopy(hInset, vInset) | ThumbFrame(); fBarThickness = thickness; invalid = invalid | BarFrame().InsetByCopy(hInset, vInset) | ThumbFrame(); Invalidate(invalid); InvalidateLayout(); } } void BSlider::SetFont(const BFont* font, uint32 properties) { BControl::SetFont(font, properties); #if USE_OFF_SCREEN_VIEW if (fOffScreenView && fOffScreenBits) { if (fOffScreenBits->Lock()) { fOffScreenView->SetFont(font, properties); fOffScreenBits->Unlock(); } } #endif InvalidateLayout(); } void BSlider::SetLimits(int32 minimum, int32 maximum) { if (minimum <= maximum) { fMinValue = minimum; fMaxValue = maximum; int32 value = Value(); value = std::max(minimum, value); value = std::min(maximum, value); if (value != Value()) SetValue(value); } } float BSlider::MaxUpdateTextWidth() { // very simplistic implementation that assumes the string will be widest // at the maximum value int32 value = Value(); SetValueNoUpdate(fMaxValue); float width = StringWidth(UpdateText()); SetValueNoUpdate(value); // in case the derived class uses a fixed buffer, the contents // should be reset for the old value UpdateText(); return width; } // #pragma mark - layout related BSize BSlider::MinSize() { return BLayoutUtils::ComposeSize(ExplicitMinSize(), _ValidateMinSize()); } BSize BSlider::MaxSize() { BSize maxSize = _ValidateMinSize(); if (fOrientation == B_HORIZONTAL) maxSize.width = B_SIZE_UNLIMITED; else maxSize.height = B_SIZE_UNLIMITED; return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize); } BSize BSlider::PreferredSize() { BSize preferredSize = _ValidateMinSize(); if (fOrientation == B_HORIZONTAL) preferredSize.width = std::max(100.0f, preferredSize.width); else preferredSize.height = std::max(100.0f, preferredSize.height); return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize); } status_t BSlider::SetIcon(const BBitmap* icon, uint32 flags) { return BControl::SetIcon(icon, flags); } void BSlider::LayoutInvalidated(bool descendants) { // invalidate cached preferred size fMinSize.Set(-1, -1); } // #pragma mark - private void BSlider::_DrawBlockThumb() { BRect frame = ThumbFrame(); BView* view = OffscreenView(); rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); uint32 flags = be_control_look->Flags(this); be_control_look->DrawSliderThumb(view, frame, frame, base, flags, fOrientation); } void BSlider::_DrawTriangleThumb() { BRect frame = ThumbFrame(); BView* view = OffscreenView(); rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); uint32 flags = be_control_look->Flags(this); be_control_look->DrawSliderTriangle(view, frame, frame, base, flags, fOrientation); } BPoint BSlider::_Location() const { return fLocation; } void BSlider::_SetLocationForValue(int32 value) { BPoint loc; float range = (float)(fMaxValue - fMinValue); if (range == 0) range = 1; float pos = (float)(value - fMinValue) / range * (_MaxPosition() - _MinPosition()); if (fOrientation == B_HORIZONTAL) { loc.x = ceil(_MinPosition() + pos); loc.y = 0; } else { loc.x = 0; loc.y = floor(_MaxPosition() - pos); } fLocation = loc; } float BSlider::_MinPosition() const { if (fOrientation == B_HORIZONTAL) return BarFrame().left + 1.0f; return BarFrame().bottom - 1.0f; } float BSlider::_MaxPosition() const { if (fOrientation == B_HORIZONTAL) return BarFrame().right - 1.0f; return BarFrame().top + 1.0f; } BSize BSlider::_ValidateMinSize() { if (fMinSize.width >= 0) { // the preferred size is up to date return fMinSize; } font_height fontHeight; GetFontHeight(&fontHeight); float width = 0.0f; float height = 0.0f; if (fMaxUpdateTextWidth < 0.0f) fMaxUpdateTextWidth = MaxUpdateTextWidth(); if (Orientation() == B_HORIZONTAL) { height = 12.0f + fBarThickness; int32 rows = 0; float labelWidth = 0; int32 labelRows = 0; float labelSpacing = StringWidth("M") * 2; if (Label() != NULL) { labelWidth = StringWidth(Label()); labelRows = 1; } if (fMaxUpdateTextWidth > 0.0f) { if (labelWidth > 0) labelWidth += labelSpacing; labelWidth += fMaxUpdateTextWidth; labelRows = 1; } rows += labelRows; if (MinLimitLabel() != NULL) width = StringWidth(MinLimitLabel()); if (MaxLimitLabel() != NULL) { // some space between the labels if (MinLimitLabel() != NULL) width += labelSpacing; width += StringWidth(MaxLimitLabel()); } if (labelWidth > width) width = labelWidth; if (width < 32.0f) width = 32.0f; if (MinLimitLabel() || MaxLimitLabel()) rows++; height += rows * (ceilf(fontHeight.ascent) + ceilf(fontHeight.descent) + 4.0); } else { // B_VERTICAL width = 12.0f + fBarThickness; height = 32.0f; float lineHeightNoLeading = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent); float lineHeight = lineHeightNoLeading + ceilf(fontHeight.leading); // find largest label float labelWidth = 0; if (Label() != NULL) { labelWidth = StringWidth(Label()); height += lineHeightNoLeading; } if (MaxLimitLabel() != NULL) { labelWidth = std::max(labelWidth, StringWidth(MaxLimitLabel())); height += Label() ? lineHeight : lineHeightNoLeading; } if (MinLimitLabel() != NULL) { labelWidth = std::max(labelWidth, StringWidth(MinLimitLabel())); height += lineHeightNoLeading; } if (fMaxUpdateTextWidth > 0.0f) { labelWidth = std::max(labelWidth, fMaxUpdateTextWidth); height += MinLimitLabel() ? lineHeight : lineHeightNoLeading; } width = std::max(labelWidth, width); } fMinSize.width = width; fMinSize.height = height; ResetLayoutInvalidation(); return fMinSize; } // #pragma mark - FBC padding void BSlider::_ReservedSlider6() {} void BSlider::_ReservedSlider7() {} void BSlider::_ReservedSlider8() {} void BSlider::_ReservedSlider9() {} void BSlider::_ReservedSlider10() {} void BSlider::_ReservedSlider11() {} void BSlider::_ReservedSlider12() {} BSlider& BSlider::operator=(const BSlider&) { return *this; } // #pragma mark - BeOS compatibility #if __GNUC__ < 3 extern "C" void GetLimits__7BSliderPlT1(BSlider* slider, int32* minimum, int32* maximum) { slider->GetLimits(minimum, maximum); } extern "C" void _ReservedSlider4__7BSlider(BSlider* slider, int32 minimum, int32 maximum) { slider->BSlider::SetLimits(minimum, maximum); } extern "C" float _ReservedSlider5__7BSlider(BSlider* slider) { return slider->BSlider::MaxUpdateTextWidth(); } extern "C" void _ReservedSlider1__7BSlider(BSlider* slider, orientation _orientation) { slider->BSlider::SetOrientation(_orientation); } extern "C" void _ReservedSlider2__7BSlider(BSlider* slider, float thickness) { slider->BSlider::SetBarThickness(thickness); } extern "C" void _ReservedSlider3__7BSlider(BSlider* slider, const BFont* font, uint32 properties) { slider->BSlider::SetFont(font, properties); } #endif // __GNUC__ < 3 extern "C" void B_IF_GCC_2(InvalidateLayout__7BSliderb, _ZN7BSlider16InvalidateLayoutEb)( BView* view, bool descendants) { perform_data_layout_invalidated data; data.descendants = descendants; view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); }