/**
An NSSliderCell controls the behaviour and appearance of an associated NSSlider, or a single slider in an NSMatrix. Tick marks are defined in the official standard, but are not implemented in GNUstep.
An NSSliderCell can be customized through its
set...
methods. If these do not provide enough
customization, a subclass can be created, which overrides any of the
follwing methods: knobRectFlipped:
,
drawBarInside:flipped:
, drawKnob:
, or
prefersTrackingUntilMouseUp
.
Draws the slider's track, not including the bezel, in aRect flipped indicates whether the control view has a flipped coordinate system.
Do not call this method directly, it is provided for subclassing only.
*/ - (void) drawBarInside: (NSRect)rect flipped: (BOOL)flipped { [[GSTheme theme] drawBarInside: rect inCell: self flipped: flipped]; } /**Returns the rect in which to draw the knob, based on the
coordinate system of the NSSlider or NSMatrix this NSSliderCell is
associated with. flipped indicates whether or not that
coordinate system is flipped, which can be determined by sending the
isFlipped
message to the associated NSSlider or
NSMatrix.
Do not call this method directly. It is included for subclassing only.
*/ - (NSRect) knobRectFlipped: (BOOL)flipped { NSImage *image = [_knobCell image]; NSSize size; NSPoint origin; double doubleValue = _value; // FIXME: this method needs to be refactored out to GSTheme if (_isVertical && flipped) { doubleValue = _maxValue + _minValue - doubleValue; } doubleValue = (doubleValue - _minValue) / (_maxValue - _minValue); if (image != nil) { size = [image size]; } else { size = NSZeroSize; } if (_isVertical == YES) { origin = _trackRect.origin; origin.x += (_trackRect.size.width - size.width) / 2.0; // center horizontally origin.y += (_trackRect.size.height - size.height) * doubleValue; } else { origin = _trackRect.origin; origin.x += (_trackRect.size.width - size.width) * doubleValue; origin.y += (_trackRect.size.height - size.height) / 2.0; // center vertically } { NSRect result = NSMakeRect (origin.x, origin.y, size.width, size.height); if ([self controlView]) { result = [[self controlView] centerScanRect: result]; } return result; } } /**Calculates the rect in which to draw the knob, then calls
drawKnob:
Before calling this method, a
lockFocus
message must be sent to the cell's control
view.
When subclassing NSSliderCell, do not override this method.
Override drawKnob:
instead.
See Also: -drawKnob:
*/ - (void) drawKnob { [[GSTheme theme] drawKnobInCell: self]; } /**Draws the knob in knobRect. Before calling this
method, a lockFocus
message must be sent to the cell's
control view.
Do not call this method directly. It is included for subclassing only.
See Also: -drawKnob
*/ - (void) drawKnob: (NSRect)knobRect { [_knobCell drawInteriorWithFrame: knobRect inView: _control_view]; } - (void) drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView*)controlView { cellFrame = [self drawingRectForBounds: cellFrame]; _trackRect = cellFrame; if (_type == NSCircularSlider) { NSBezierPath *circle; NSPoint knobCenter; NSPoint point; NSRect knobRect; double fraction, angle, radius; NSImage *image; if (cellFrame.size.width > cellFrame.size.height) { knobRect = NSMakeRect(cellFrame.origin.x + ((cellFrame.size.width - cellFrame.size.height) / 2.0), cellFrame.origin.y, cellFrame.size.height, cellFrame.size.height); } else { knobRect = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y + ((cellFrame.size.height - cellFrame.size.width) / 2.0), cellFrame.size.width, cellFrame.size.width); } if ([self controlView]) knobRect = [[self controlView] centerScanRect: knobRect]; image = [NSImage imageNamed: @"common_CircularSlider"]; if (image != nil) { [image drawInRect: knobRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil]; } else { knobRect = NSInsetRect(knobRect, 1, 1); circle = [NSBezierPath bezierPathWithOvalInRect: knobRect]; [[NSColor controlBackgroundColor] set]; [circle fill]; [[NSColor blackColor] set]; [circle stroke]; } knobCenter = NSMakePoint(NSMidX(knobRect), NSMidY(knobRect)); fraction = ([self doubleValue] - [self minValue]) / ([self maxValue] - [self minValue]); angle = (fraction * (2.0 * M_PI)) - (M_PI / 2.0); radius = (knobRect.size.height / 2) - 6; point = NSMakePoint((radius * cos(angle)) + knobCenter.x, (radius * sin(angle)) + knobCenter.y); image = [NSImage imageNamed: @"common_Dimple"]; { NSSize size = [image size]; NSRect dimpleRect = NSMakeRect(point.x - (size.width / 2.0), point.y - (size.height / 2.0), size.width, size.height); if ([self controlView]) dimpleRect = [[self controlView] centerScanRect: dimpleRect]; [image drawInRect: dimpleRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil]; } } else if (_type == NSLinearSlider) { BOOL vertical = (cellFrame.size.height > cellFrame.size.width); if (vertical != _isVertical) { NSImage *image; if (vertical == YES) { image = [NSImage imageNamed: @"common_SliderVert"]; } else { image = [NSImage imageNamed: @"common_SliderHoriz"]; } [_knobCell setImage: image]; } _isVertical = vertical; [self drawBarInside: cellFrame flipped: [controlView isFlipped]]; /* Draw title - Uhmmm - shouldn't this better go into drawBarInside:flipped: ? */ if (_isVertical == NO) { [_titleCell drawInteriorWithFrame: cellFrame inView: controlView]; } [self drawKnob]; } } - (BOOL) isOpaque { return NO; } /**Returns the thickness of the slider's knob. This value is in pixels, and is the size of the knob along the slider's track.
See Also: -setKnobThickness:
*/ - (CGFloat) knobThickness { NSImage *image = [_knobCell image]; NSSize size; if (image != nil) { size = [image size]; } else { return 0; } return _isVertical ? size.height : size.width; } /**Sets the thickness of the knob to thickness, in pixels. This value sets the amount of space which the knob takes up in the slider's track.
See Also: -knobThickness
*/ - (void) setKnobThickness: (CGFloat)thickness { NSImage *image = [_knobCell image]; NSSize size; if (image != nil) { size = [image size]; } else { return; } if (_isVertical == YES) size.height = thickness; else size.width = thickness; [image setSize: size]; if ((_control_view != nil) && ([_control_view isKindOfClass: [NSControl class]])) { [(NSControl*)_control_view updateCell: self]; } } /**Sets the value by which the slider will be be incremented when with the ALT key down to increment.
See Also: -altIncrementValue
*/ - (void) setAltIncrementValue: (double)increment { _altIncrementValue = increment; } /**Returns the minimum value that the slider represents.
See Also: -setMinValue:
*/ - (double) minValue { return _minValue; } /**Returns the maximum value that the slider represents.
See Also: -setMaxValue:
*/ - (double) maxValue { return _maxValue; } /**Sets the minimum value that the sliders represents to maxValue.
See Also: -minValue
*/ - (void) setMinValue: (double)aDouble { _minValue = aDouble; if (_minValue > _maxValue) { _value = _minValue; } else if (_value < _minValue) { _value = _minValue; } } /**Sets the maximum value that the sliders represents to maxValue.
See Also: -maxValue
*/ - (void) setMaxValue: (double)aDouble { _maxValue = aDouble; if (_minValue > _maxValue) { _value = _minValue; } else if (_value > _maxValue) { _value = _maxValue; } } - (double) doubleValue { return _value; } - (void) setDoubleValue: (double)aDouble { if (_minValue > _maxValue) { return; } if (aDouble < _minValue) { _value = _minValue; } else if (aDouble > _maxValue) { _value = _maxValue; } else { _value = aDouble; } if ((_control_view != nil) && ([_control_view isKindOfClass: [NSControl class]])) { [(NSControl*)_control_view updateCell: self]; } } - (float) floatValue { return _value; } - (void) setFloatValue: (float)aFloat { if (_minValue > _maxValue) { return; } if (aFloat < _minValue) { _value = _minValue; } else if (aFloat > _maxValue) { _value = _maxValue; } else { _value = aFloat; } if ((_control_view != nil) && ([_control_view isKindOfClass: [NSControl class]])) { [(NSControl*)_control_view updateCell: self]; } } - (int) intValue { return _value; } - (void) setIntValue: (int)anInt { if (_minValue > _maxValue) { return; } if (anInt < _minValue) { _value = _minValue; } else if (anInt > _maxValue) { _value = _maxValue; } else { _value = anInt; } if ((_control_view != nil) && ([_control_view isKindOfClass: [NSControl class]])) { [(NSControl*)_control_view updateCell: self]; } } - (NSInteger) integerValue { return (NSInteger)_value; } - (id) objectValue { return [NSNumber numberWithDouble: _value]; } - (void) setObjectValue: (id)anObject { // If the provided object doesn't respond to doubeValue, or our minValue // is greater than our maxValue, we set our value to our minValue // (this arbitrary choice matches OS X) if ([anObject respondsToSelector: @selector(doubleValue)] == NO || _minValue > _maxValue) { _value = _minValue; } else { double aDouble = [anObject doubleValue]; if (aDouble < _minValue) { _value = _minValue; } else if (aDouble > _maxValue) { _value = _maxValue; } else { _value = aDouble; } } if ((_control_view != nil) && ([_control_view isKindOfClass: [NSControl class]])) { [(NSControl*)_control_view updateCell: self]; } } /**Returns the cell used to draw the title.
See Also: -setTitleCell:
*/ - (id) titleCell { return _titleCell; } /**Returns the colour used to draw the title.
See Also: -setTitleColor:
*/ - (NSColor*) titleColor { return [_titleCell textColor]; } /**Returns the font used to draw the title.
See Also: -setTitleFont:
*/ - (NSFont*) titleFont { return [_titleCell font]; } /**Sets the title of the slider to barTitle. This title is displayed on the slider's track, behind the knob.
See Also: -title
*/ - (void) setTitle: (NSString*)title { [_titleCell setStringValue: title]; } /**Returns the title of the slider. This title is displayed on the slider's track, behind the knob.
See Also: -setTitle:
*/ - (NSString*) title { return [_titleCell stringValue]; } /**Sets the cell used to draw the title to titleCell.
See Also: -titleCell
*/ - (void) setTitleCell: (NSCell*)aCell { ASSIGN(_titleCell, aCell); } /**Sets the colour with which the title will be drawn to color.
See Also: -titleColor
*/ - (void) setTitleColor: (NSColor*)color { [_titleCell setTextColor: color]; } /**Sets the font with which the title will be drawm to font.
See Also: -titleFont
*/ - (void) setTitleFont: (NSFont*)font { [_titleCell setFont: font]; } /**Returns the slider type: linear or circular.
See Also: -setSliderType:
*/ - (NSSliderType)sliderType { return _type; } /**Sets the type of the slider: linear or circular.
See Also: -sliderType
*/ - (void) setSliderType: (NSSliderType)type { _type = type; if (_type == NSLinearSlider) { [self setBordered: YES]; [self setBezeled: NO]; } else if (_type == NSCircularSlider) { [self setBordered: NO]; [self setBezeled: NO]; } } /**Returns whether or not the slider is vertical. If, for some reason, this cannot be determined, for such reasons as the slider is not yet displayed, this method returns -1. Generally, a slider is considered vertical if its height is greater than its width. */ - (NSInteger) isVertical { return _isVertical; } /**Returns the value by which the slider is incremented when the user holds down the ALT key.
See Also: -setAltIncrementValue:
*/ - (double) altIncrementValue { return _altIncrementValue; } /**The default implementation returns YES
, so that the
slider continues to track the user's movement even if the cursor
leaves the slider's track.
Do not call this method directly. Override it in subclasses where the tracking behaviour needs to be different.
*/ + (BOOL) prefersTrackingUntilMouseUp { return YES; } /** Returns the rect of the track, minus the bezel. */ - (NSRect) trackRect { return _trackRect; } // ticks - (BOOL) allowsTickMarkValuesOnly { return _allowsTickMarkValuesOnly; } /* verified on Cocoa that a circular slider with one tick has two values: 0, 50 */ - (double) closestTickMarkValueToValue: (double)aValue { double d, f; int effectiveTicks; if (_numberOfTickMarks == 0) return aValue; effectiveTicks = _numberOfTickMarks; if (_type == NSCircularSlider) effectiveTicks++; if (effectiveTicks == 1) return (_maxValue + _minValue) / 2; if (aValue < _minValue) { aValue = _minValue; } else if (aValue > _maxValue) { aValue = _maxValue; } d = _maxValue - _minValue; f = ((aValue - _minValue) * (effectiveTicks - 1)) / d; f = ((GSRoundTowardsInfinity(f) * d) / (effectiveTicks - 1)) + _minValue; /* never return the maximum value, tested on Apple */ if (_type == NSCircularSlider && (f >= _maxValue)) f = _minValue; return f; } - (NSInteger) indexOfTickMarkAtPoint: (NSPoint)point { NSInteger i; for (i = 0; i < _numberOfTickMarks; i++) { if (NSPointInRect(point, [self rectOfTickMarkAtIndex: i])) { return i; } } return NSNotFound; } - (NSInteger) numberOfTickMarks { return _numberOfTickMarks; } - (NSRect) rectOfTickMarkAtIndex: (NSInteger)index { NSRect rect = _trackRect; CGFloat d; if ((index < 0) || (index >= _numberOfTickMarks)) { [NSException raise: NSRangeException format: @"Index of tick mark out of bounds."]; } if (_numberOfTickMarks > 1) { if (_isVertical) { d = NSHeight(rect) / (_numberOfTickMarks - 1); rect.size.height = d; rect.origin.y += d * index; } else { d = NSWidth(rect) / (_numberOfTickMarks - 1); rect.size.width = d; rect.origin.x += d * index; } } return rect; } - (void) setAllowsTickMarkValuesOnly: (BOOL)flag { _allowsTickMarkValuesOnly = flag; } - (void) setNumberOfTickMarks: (NSInteger)numberOfTickMarks { _numberOfTickMarks = numberOfTickMarks; if ((_control_view != nil) && ([_control_view isKindOfClass: [NSControl class]])) { [(NSControl*)_control_view updateCell: self]; } } - (void) setTickMarkPosition: (NSTickMarkPosition)position { _tickMarkPosition = position; if ((_control_view != nil) && ([_control_view isKindOfClass: [NSControl class]])) { [(NSControl*)_control_view updateCell: self]; } } - (NSTickMarkPosition) tickMarkPosition { return _tickMarkPosition; } - (double) tickMarkValueAtIndex: (NSInteger)index { if ((index < 0) || (index >= _numberOfTickMarks)) { [NSException raise: NSRangeException format: @"Index of tick mark out of bounds."]; } if (_numberOfTickMarks == 1) return (_maxValue + _minValue) / 2; if (index >= _numberOfTickMarks) return _maxValue; if (index <= 0) return _minValue; if (_type == NSCircularSlider) return _minValue + index * (_maxValue - _minValue) / _numberOfTickMarks; if (_type == NSLinearSlider) return _minValue + index * (_maxValue - _minValue) / (_numberOfTickMarks - 1); return 0.0; } - (BOOL) startTrackingAt: (NSPoint)startPoint inView: (NSView*)controlView { // If the point is in the view then yes start tracking if ([controlView mouse: startPoint inRect: [controlView bounds]]) { BOOL isFlipped = [controlView isFlipped]; NSRect knobRect = [self knobRectFlipped: isFlipped]; if (![controlView mouse: startPoint inRect: knobRect]) { // Mouse is not on the knob, move the knob to the mouse position double doubleValue; NSRect slotRect = [self trackRect]; BOOL isVertical = [self isVertical]; double minValue = [self minValue]; double maxValue = [self maxValue]; doubleValue = _doubleValueForMousePoint(startPoint, knobRect, slotRect, isVertical, minValue, maxValue, self, isFlipped, (_type == NSCircularSlider)); if (_allowsTickMarkValuesOnly) { doubleValue = [self closestTickMarkValueToValue: doubleValue]; } [self setDoubleValue: doubleValue]; if ([self isContinuous]) { [(NSControl*)controlView sendAction: [self action] to: [self target]]; } } return YES; } else { return NO; } } - (BOOL) continueTracking: (NSPoint)lastPoint at: (NSPoint)currentPoint inView: (NSView*)controlView { if (currentPoint.x != lastPoint.x || currentPoint.y != lastPoint.y) { double doubleValue; BOOL isFlipped = [controlView isFlipped]; NSRect knobRect = [self knobRectFlipped: isFlipped]; double oldDoubleValue = [self doubleValue]; NSRect slotRect = [self trackRect]; BOOL isVertical = [self isVertical]; double minValue = [self minValue]; double maxValue = [self maxValue]; doubleValue = _doubleValueForMousePoint(currentPoint, knobRect, slotRect, isVertical, minValue, maxValue, self, isFlipped, (_type == NSCircularSlider)); if (_allowsTickMarkValuesOnly) { doubleValue = [self closestTickMarkValueToValue: doubleValue]; } if (doubleValue != oldDoubleValue) { [self setDoubleValue: doubleValue]; // The action gets triggered in trackMouse:...untilMouseUp: } } return YES; } - (id) initWithCoder: (NSCoder*)decoder { self = [super initWithCoder: decoder]; if (self == nil) return nil; if ([decoder allowsKeyedCoding]) { _allowsTickMarkValuesOnly = [decoder decodeBoolForKey: @"NSAllowsTickMarkValuesOnly"]; _numberOfTickMarks = [decoder decodeIntForKey: @"NSNumberOfTickMarks"]; _tickMarkPosition = [decoder decodeIntForKey: @"NSTickMarkPosition"]; [self setMinValue: [decoder decodeDoubleForKey: @"NSMinValue"]]; [self setMaxValue: [decoder decodeDoubleForKey: @"NSMaxValue"]]; [self setDoubleValue: [decoder decodeDoubleForKey: @"NSValue"]]; _altIncrementValue = [decoder decodeDoubleForKey: @"NSAltIncValue"]; [self setSliderType: [decoder decodeIntForKey: @"NSSliderType"]]; // do these here, since the Cocoa version of the class does not save these values... _knobCell = [NSCell new]; _titleCell = [NSTextFieldCell new]; [_titleCell setTextColor: [NSColor controlTextColor]]; [_titleCell setStringValue: @""]; [_titleCell setAlignment: NSCenterTextAlignment]; _isVertical = -1; } else { float tmp_float; NSInteger tmp_int; [self setDoubleValue: 0]; [decoder decodeValueOfObjCType: @encode(float) at: &tmp_float]; [self setMinValue: tmp_float]; [decoder decodeValueOfObjCType: @encode(float) at: &tmp_float]; [self setMaxValue: tmp_float]; [decoder decodeValueOfObjCType: @encode(float) at: &tmp_float]; [self setAltIncrementValue: tmp_float]; decode_NSInteger(decoder, &tmp_int); _isVertical = tmp_int; [decoder decodeValueOfObjCType: @encode(id) at: &_titleCell]; [decoder decodeValueOfObjCType: @encode(id) at: &_knobCell]; if ([decoder versionForClassName: @"NSSliderCell"] >= 2) { [decoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsTickMarkValuesOnly]; decode_NSInteger(decoder, &_numberOfTickMarks); decode_NSInteger(decoder, &tmp_int); _tickMarkPosition = tmp_int; } } return self; } - (void) encodeWithCoder: (NSCoder*)coder { [super encodeWithCoder: coder]; if ([coder allowsKeyedCoding]) { [coder encodeBool: _allowsTickMarkValuesOnly forKey: @"NSAllowsTickMarkValuesOnly"]; [coder encodeInt: _numberOfTickMarks forKey: @"NSNumberOfTickMarks"]; [coder encodeInt: _tickMarkPosition forKey: @"NSTickMarkPosition"]; [coder encodeDouble: _minValue forKey: @"NSMinValue"]; [coder encodeDouble: _maxValue forKey: @"NSMaxValue"]; [coder encodeDouble: _value forKey: @"NSValue"]; [coder encodeDouble: _altIncrementValue forKey: @"NSAltIncValue"]; [coder encodeInt: _type forKey: @"NSSliderType"]; } else { float tmp_float; NSInteger tmp_int; tmp_float = _minValue; [coder encodeValueOfObjCType: @encode(float) at: &tmp_float]; tmp_float = _maxValue; [coder encodeValueOfObjCType: @encode(float) at: &tmp_float]; tmp_float = _altIncrementValue; [coder encodeValueOfObjCType: @encode(float) at: &tmp_float]; tmp_int = _isVertical; encode_NSInteger(coder, &tmp_int); [coder encodeValueOfObjCType: @encode(id) at: &_titleCell]; [coder encodeValueOfObjCType: @encode(id) at: &_knobCell]; // New for version 2 [coder encodeValueOfObjCType: @encode(BOOL) at: &_allowsTickMarkValuesOnly]; encode_NSInteger(coder, &_numberOfTickMarks); tmp_int = _tickMarkPosition; encode_NSInteger(coder, &tmp_int); } } @end