/* NSRulerView.m Copyright (C) 2002 Free Software Foundation, Inc. Author: Diego Kreutz (kreutz@inf.ufsm.br) Date: January 2002 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DEFINE_RINT_IF_MISSING #define MIN_LABEL_DISTANCE 40 #define MIN_MARK_DISTANCE 5 #define MARK_SIZE 2 #define MID_MARK_SIZE 4 #define BIG_MARK_SIZE 6 #define LABEL_MARK_SIZE 11 #define BASE_LINE_LOCATION 0 #define RULER_THICKNESS 16 #define MARKER_THICKNESS 15 #define DRAW_HASH_MARK(path, size) \ do { \ if (_orientation == NSHorizontalRuler)\ { \ [path relativeLineToPoint: NSMakePoint(0, size)]; \ } \ else \ { \ [path relativeLineToPoint: NSMakePoint(size, 0)]; \ } \ } while (0) @interface GSRulerUnit : NSObject { NSString *_unitName; NSString *_abbreviation; float _conversionFactor; NSArray *_stepUpCycle; NSArray *_stepDownCycle; } + (GSRulerUnit *) unitWithName: (NSString *)uName abbreviation: (NSString *)abbrev unitToPointsConversionFactor: (float)factor stepUpCycle: (NSArray *)upCycle stepDownCycle: (NSArray *)downCycle; - (id) initWithUnitName: (NSString *)uName abbreviation: (NSString *)abbrev unitToPointsConversionFactor: (float)factor stepUpCycle: (NSArray *)upCycle stepDownCycle: (NSArray *)downCycle; - (NSString *) unitName; - (NSString *) abbreviation; - (float) conversionFactor; - (NSArray *) stepUpCycle; - (NSArray *) stepDownCycle; @end @implementation GSRulerUnit + (GSRulerUnit *) unitWithName: (NSString *)uName abbreviation: (NSString *)abbrev unitToPointsConversionFactor: (float)factor stepUpCycle: (NSArray *)upCycle stepDownCycle: (NSArray *)downCycle { return [[[self alloc] initWithUnitName: uName abbreviation: abbrev unitToPointsConversionFactor: factor stepUpCycle: upCycle stepDownCycle: downCycle] autorelease]; } - (id) initWithUnitName: (NSString *)uName abbreviation: (NSString *)abbrev unitToPointsConversionFactor: (float)factor stepUpCycle: (NSArray *)upCycle stepDownCycle: (NSArray *)downCycle { self = [super init]; if (self != nil) { ASSIGN(_unitName, uName); ASSIGN(_abbreviation, abbrev); _conversionFactor = factor; ASSIGN(_stepUpCycle, upCycle); ASSIGN(_stepDownCycle, downCycle); } return self; } - (NSString *) unitName { return _unitName; } - (NSString *) abbreviation { return _abbreviation; } - (float) conversionFactor { return _conversionFactor; } - (NSArray *) stepUpCycle { return _stepUpCycle; } - (NSArray *) stepDownCycle { return _stepDownCycle; } - (void) dealloc { [_unitName release]; [_abbreviation release]; [_stepUpCycle release]; [_stepDownCycle release]; [super dealloc]; } @end @implementation NSRulerView /* * Class variables */ static NSMutableDictionary *units = nil; /* * Class methods */ + (void) initialize { if (self == [NSRulerView class]) { NSArray *array05; NSArray *array052; NSArray *array2; NSArray *array10; [self setVersion: 0.01]; units = [[NSMutableDictionary alloc] init]; array05 = [NSArray arrayWithObject: [NSNumber numberWithFloat: 0.5]]; array052 = [NSArray arrayWithObjects: [NSNumber numberWithFloat: 0.5], [NSNumber numberWithFloat: 0.2], nil]; array2 = [NSArray arrayWithObject: [NSNumber numberWithFloat: 2.0]]; array10 = [NSArray arrayWithObject: [NSNumber numberWithFloat: 10.0]]; [self registerUnitWithName: @"Inches" abbreviation: @"in" unitToPointsConversionFactor: 72.0 stepUpCycle: array2 stepDownCycle: array05]; [self registerUnitWithName: @"Centimeters" abbreviation: @"cm" unitToPointsConversionFactor: 28.35 stepUpCycle: array2 stepDownCycle: array052]; [self registerUnitWithName: @"Points" abbreviation: @"pt" unitToPointsConversionFactor: 1.0 stepUpCycle: array10 stepDownCycle: array05]; [self registerUnitWithName: @"Picas" abbreviation: @"pc" unitToPointsConversionFactor: 12.0 stepUpCycle: array2 stepDownCycle: array05]; } } - (id) initWithScrollView: (NSScrollView *)aScrollView orientation: (NSRulerOrientation)o { self = [super initWithFrame: NSZeroRect]; if (self != nil) { [self setScrollView: aScrollView]; [self setOrientation: o]; [self setMeasurementUnits: @"Points"]; /* FIXME: should be user's pref */ [self setRuleThickness: RULER_THICKNESS]; [self setOriginOffset: 0.0]; [self setReservedThicknessForAccessoryView: 0.0]; if (o == NSHorizontalRuler) { [self setReservedThicknessForMarkers: MARKER_THICKNESS]; } else { [self setReservedThicknessForMarkers: 0.0]; } [self invalidateHashMarks]; } return self; } + (void) registerUnitWithName: (NSString *)uName abbreviation: (NSString *)abbreviation unitToPointsConversionFactor: (float)conversionFactor stepUpCycle: (NSArray *)stepUpCycle stepDownCycle: (NSArray *)stepDownCycle { GSRulerUnit *u = [GSRulerUnit unitWithName: uName abbreviation: abbreviation unitToPointsConversionFactor: conversionFactor stepUpCycle: stepUpCycle stepDownCycle: stepDownCycle]; [units setObject: u forKey: uName]; } - (void) setMeasurementUnits: (NSString *)uName { GSRulerUnit *newUnit; newUnit = [units objectForKey: uName]; if (newUnit == nil) { [NSException raise: NSInvalidArgumentException format: @"Unknown measurement unit %@", uName]; } ASSIGN(_unit, newUnit); [self setNeedsDisplay: YES]; } - (NSString *) measurementUnits { return [_unit unitName]; } - (void) setClientView: (NSView *)aView { if (_clientView == aView) return; if (_clientView != nil && [_clientView respondsToSelector: @selector(rulerView:willSetClientView:)]) { [_clientView rulerView: self willSetClientView: aView]; } /* NB: We should not RETAIN the clientView. */ _clientView = aView; [self setMarkers: nil]; [self setNeedsDisplay: YES]; } - (BOOL) isOpaque { return YES; } - (NSView *) clientView { return _clientView; } - (void) setAccessoryView: (NSView *)aView { /* FIXME/TODO: support for accessory views is not implemented */ ASSIGN(_accessoryView, aView); [self setNeedsDisplay: YES]; } - (NSView *) accessoryView { return _accessoryView; } - (void) setOriginOffset: (float)offset { _originOffset = offset; [self setNeedsDisplay: YES]; } - (float) originOffset { return _originOffset; } - (void) _verifyReservedThicknessForMarkers { NSEnumerator *en; NSRulerMarker *marker; float maxThickness = _reservedThicknessForMarkers; if (_markers == nil) { return; } en = [_markers objectEnumerator]; while ((marker = [en nextObject]) != nil) { float markerThickness; markerThickness = [marker thicknessRequiredInRuler]; if (markerThickness > maxThickness) { maxThickness = markerThickness; } } if (maxThickness > _reservedThicknessForMarkers) { [self setReservedThicknessForMarkers: maxThickness]; } } - (void) setMarkers: (NSArray *)newMarkers { if (newMarkers != nil && _clientView == nil) { [NSException raise: NSInternalInconsistencyException format: @"Cannot set markers without a client view"]; } if (newMarkers != nil) { ASSIGN(_markers, [NSMutableArray arrayWithArray: newMarkers]); [self _verifyReservedThicknessForMarkers]; } else { ASSIGN(_markers, nil); } [self setNeedsDisplay: YES]; } - (NSArray *) markers { return _markers; } - (void) addMarker: (NSRulerMarker *)aMarker { float markerThickness = [aMarker thicknessRequiredInRuler]; if (_clientView == nil) { [NSException raise: NSInternalInconsistencyException format: @"Cannot add a marker without a client view"]; } if (markerThickness > [self reservedThicknessForMarkers]) { [self setReservedThicknessForMarkers: markerThickness]; } if (_markers == nil) { _markers = [[NSMutableArray alloc] initWithObjects: aMarker, nil]; } else { [_markers addObject: aMarker]; } [self setNeedsDisplay: YES]; } - (void) removeMarker: (NSRulerMarker *)aMarker { [_markers removeObject: aMarker]; [self setNeedsDisplay: YES]; } - (BOOL) trackMarker: (NSRulerMarker *)aMarker withMouseEvent: (NSEvent *)theEvent { NSParameterAssert(aMarker != nil); return [aMarker trackMouse: theEvent adding: YES]; } - (NSRect) _rulerRect { NSRect rect = [self bounds]; if (_orientation == NSHorizontalRuler) { rect.size.height = _ruleThickness; if ([self isFlipped]) { rect.origin.y = [self baselineLocation]; } else { rect.origin.y = [self baselineLocation] - _ruleThickness; } } else { rect.size.width = _ruleThickness; rect.origin.x = [self baselineLocation]; } return rect; } - (NSRect) _markersRect { NSRect rect = [self bounds]; if (_orientation == NSHorizontalRuler) { rect.size.height = _reservedThicknessForMarkers; if ([self isFlipped]) { rect.origin.y = _reservedThicknessForAccessoryView; } else { rect.origin.y = _ruleThickness; } } else { rect.size.width = _reservedThicknessForMarkers; rect.origin.x = _reservedThicknessForAccessoryView; } return rect; } - (NSRulerMarker *) _markerAtPoint: (NSPoint)point { NSEnumerator *markerEnum; NSRulerMarker *marker; BOOL flipped = [self isFlipped]; /* test markers in reverse order so that markers drawn on top are tested before those underneath */ markerEnum = [_markers reverseObjectEnumerator]; while ( (marker = [markerEnum nextObject]) != nil) { if (NSMouseInRect (point, [marker imageRectInRuler], flipped)) { return marker; } } return nil; } - (void) mouseDown: (NSEvent*)theEvent { NSPoint clickPoint; BOOL flipped = [self isFlipped]; clickPoint = [self convertPoint: [theEvent locationInWindow] fromView: nil]; if (NSMouseInRect (clickPoint, [self _rulerRect], flipped)) { if (_clientView != nil && [_clientView respondsToSelector: @selector(rulerView:handleMouseDown:)]) { [_clientView rulerView: self handleMouseDown: theEvent]; } } else if (NSMouseInRect (clickPoint, [self _markersRect], flipped)) { NSRulerMarker *clickedMarker; clickedMarker = [self _markerAtPoint: clickPoint]; if (clickedMarker != nil) { [clickedMarker trackMouse: theEvent adding: NO]; } } } - (void) moveRulerlineFromLocation: (float)oldLoc toLocation: (float)newLoc { /* FIXME/TODO: not implemented */ } - (void)drawRect: (NSRect)aRect { [[NSColor controlColor] set]; NSRectFill(aRect); [self drawHashMarksAndLabelsInRect: aRect]; [self drawMarkersInRect: aRect]; } - (float) _stepForIndex: (int)index { int newindex; NSArray *stepCycle; if (index > 0) { stepCycle = [_unit stepUpCycle]; newindex = (index - 1) % [stepCycle count]; return [[stepCycle objectAtIndex: newindex] floatValue]; } else { stepCycle = [_unit stepDownCycle]; newindex = (-index) % [stepCycle count]; return 1 / [[stepCycle objectAtIndex: newindex] floatValue]; } } - (void) _verifyCachedValues { if (! _cacheIsValid) { NSSize unitSize; float cf; int convIndex; /* calculate the size one unit in document view has in the ruler */ cf = [_unit conversionFactor]; unitSize = [self convertSize: NSMakeSize(cf, cf) fromView: [_scrollView documentView]]; if (_orientation == NSHorizontalRuler) { _unitToRuler = unitSize.width; } else { _unitToRuler = unitSize.height; } /* Calculate distance between marks. */ /* It must not be less than MIN_MARK_DISTANCE in ruler units * and must obey the current unit's step cycles. */ _markDistance = _unitToRuler; convIndex = 0; /* make sure it's smaller than MIN_MARK_DISTANCE */ while ((_markDistance) > MIN_MARK_DISTANCE) { _markDistance /= [self _stepForIndex: convIndex]; convIndex--; } /* find the first size that's not < MIN_MARK_DISTANCE */ while ((_markDistance) < MIN_MARK_DISTANCE) { convIndex++; _markDistance *= [self _stepForIndex: convIndex]; } /* calculate number of small marks in each bigger mark */ _marksToMidMark = rint([self _stepForIndex: convIndex + 1]); _marksToBigMark = _marksToMidMark * rint([self _stepForIndex: convIndex + 2]); /* Calculate distance between labels. It must not be less than MIN_LABEL_DISTANCE. */ _labelDistance = _markDistance; while (_labelDistance < MIN_LABEL_DISTANCE) { convIndex++; _labelDistance *= [self _stepForIndex: convIndex]; } /* number of small marks between two labels */ _marksToLabel = rint(_labelDistance / _markDistance); /* format of labels */ if (_labelDistance / _unitToRuler >= 1) { ASSIGN(_labelFormat, @"%1.f"); } else { /* smallest integral value not less than log10(1/labelDistInUnits) */ int log = ceil(log10(1 / (_labelDistance / _unitToRuler))); NSString *string = [NSString stringWithFormat: @"%%.%df", (int)log]; ASSIGN(_labelFormat, string); } _cacheIsValid = YES; } } - (void) drawHashMarksAndLabelsInRect: (NSRect)aRect { NSView *docView; float firstVisibleLocation; float visibleLength; int firstVisibleMark; int lastVisibleMark; int mark; int firstVisibleLabel; int lastVisibleLabel; int label; NSRect baseLineRect; float baselineLocation = [self baselineLocation]; NSPoint zeroPoint; NSBezierPath *path; NSFont *font = [NSFont systemFontOfSize: [NSFont smallSystemFontSize]]; NSDictionary *attr = [[NSDictionary alloc] initWithObjectsAndKeys: font, NSFontAttributeName, [NSColor blackColor], NSForegroundColorAttributeName, nil]; docView = [_scrollView documentView]; zeroPoint = [self convertPoint: NSMakePoint(_originOffset, _originOffset) fromView: docView]; if (_orientation == NSHorizontalRuler) { _zeroLocation = zeroPoint.x; } else { _zeroLocation = zeroPoint.y; } [self _verifyCachedValues]; baseLineRect = [self convertRect: [docView bounds] fromView: docView]; if (_orientation == NSHorizontalRuler) { baseLineRect.origin.y = baselineLocation; baseLineRect.size.height = 1; baseLineRect = NSIntersectionRect(baseLineRect, aRect); firstVisibleLocation = NSMinX(baseLineRect); visibleLength = NSWidth(baseLineRect); } else { baseLineRect.origin.x = baselineLocation; baseLineRect.size.width = 1; baseLineRect = NSIntersectionRect(baseLineRect, aRect); firstVisibleLocation = NSMinY(baseLineRect); visibleLength = NSHeight(baseLineRect); } /* draw the base line */ [[NSColor blackColor] set]; NSRectFill(baseLineRect); /* draw hash marks */ firstVisibleMark = floor((firstVisibleLocation - _zeroLocation) / _markDistance); lastVisibleMark = floor((firstVisibleLocation + visibleLength - _zeroLocation) / _markDistance); path = [NSBezierPath new]; for (mark = firstVisibleMark; mark <= lastVisibleMark; mark++) { float markLocation; markLocation = _zeroLocation + mark * _markDistance; if (_orientation == NSHorizontalRuler) { [path moveToPoint: NSMakePoint(markLocation, baselineLocation)]; } else { [path moveToPoint: NSMakePoint(baselineLocation, markLocation)]; } if ((mark % _marksToLabel) == 0) { DRAW_HASH_MARK(path, LABEL_MARK_SIZE); } else if ((mark % _marksToBigMark) == 0) { DRAW_HASH_MARK(path, BIG_MARK_SIZE); } else if ((mark % _marksToMidMark) == 0) { DRAW_HASH_MARK(path, MID_MARK_SIZE); } else { DRAW_HASH_MARK(path, MARK_SIZE); } } [path stroke]; RELEASE(path); /* draw labels */ /* FIXME: shouldn't be using NSCell to draw labels? */ firstVisibleLabel = floor((firstVisibleLocation - _zeroLocation) / (_marksToLabel * _markDistance)); lastVisibleLabel = floor((firstVisibleLocation + visibleLength - _zeroLocation) / (_marksToLabel * _markDistance)); for (label = firstVisibleLabel; label <= lastVisibleLabel; label++) { float labelLocation = _zeroLocation + label * _marksToLabel * _markDistance; float labelValue = (labelLocation - _zeroLocation) / _unitToRuler; NSString *labelString = [NSString stringWithFormat: _labelFormat, labelValue]; NSSize size = [labelString sizeWithAttributes: attr]; NSPoint labelPosition; if (_orientation == NSHorizontalRuler) { labelPosition.x = labelLocation + 1; labelPosition.y = baselineLocation + LABEL_MARK_SIZE + 4 - size.height; } else { labelPosition.x = baselineLocation + LABEL_MARK_SIZE + 4 - size.width; labelPosition.y = labelLocation + 1; } [labelString drawAtPoint: labelPosition withAttributes: attr]; } RELEASE(attr); } - (void) drawMarkersInRect: (NSRect)aRect { NSRulerMarker *marker; NSEnumerator *en; en = [_markers objectEnumerator]; while ((marker = [en nextObject]) != nil) { [marker drawRect: aRect]; } } - (void) invalidateHashMarks { _cacheIsValid = NO; } - (void) setScrollView: (NSScrollView *)scrollView { /* We do NOT retain the scrollView; the scrollView is retaining us. */ _scrollView = scrollView; } - (NSScrollView *) scrollView { return _scrollView; } - (void) setOrientation: (NSRulerOrientation)o { _orientation = o; } - (NSRulerOrientation)orientation { return _orientation; } - (void) setReservedThicknessForAccessoryView: (float)thickness { _reservedThicknessForAccessoryView = thickness; [_scrollView tile]; } - (float) reservedThicknessForAccessoryView { return _reservedThicknessForAccessoryView; } - (void) setReservedThicknessForMarkers: (float)thickness { _reservedThicknessForMarkers = thickness; [_scrollView tile]; } - (float) reservedThicknessForMarkers { return _reservedThicknessForMarkers; } - (void) setRuleThickness: (float)thickness { _ruleThickness = thickness; [_scrollView tile]; } - (float) ruleThickness { return _ruleThickness; } - (float) requiredThickness { return [self ruleThickness] + [self reservedThicknessForAccessoryView] + [self reservedThicknessForMarkers]; } - (float) baselineLocation { return [self reservedThicknessForAccessoryView] + [self reservedThicknessForMarkers]; } /* FIXME ... we cache isFlipped in NSView. */ - (BOOL) isFlipped { if (_orientation == NSVerticalRuler) { return [[_scrollView documentView] isFlipped]; } return YES; } - (void)encodeWithCoder:(NSCoder *)encoder { /* FIXME/TODO: not implemented */ return; } - (id)initWithCoder:(NSCoder *)decoder { /* FIXME/TODO: not implemented */ return nil; } - (void) dealloc { RELEASE(_unit); RELEASE(_clientView); RELEASE(_accessoryView); RELEASE(_markers); RELEASE(_labelFormat); [super dealloc]; } @end