1/*
2   NSRulerView.m
3
4   Copyright (C) 2002 Free Software Foundation, Inc.
5
6   Author: Diego Kreutz (kreutz@inf.ufsm.br)
7   Date: January 2002
8
9   This file is part of the GNUstep GUI Library.
10
11   This library is free software; you can redistribute it and/or
12   modify it under the terms of the GNU Lesser General Public
13   License as published by the Free Software Foundation; either
14   version 2 of the License, or (at your option) any later version.
15
16   This library is distributed in the hope that it will be useful,
17   but WITHOUT ANY WARRANTY; without even the implied warranty of
18   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
19   Lesser General Public License for more details.
20
21   You should have received a copy of the GNU Lesser General Public
22   License along with this library; see the file COPYING.LIB.
23   If not, see <http://www.gnu.org/licenses/> or write to the
24   Free Software Foundation, 51 Franklin Street, Fifth Floor,
25   Boston, MA 02110-1301, USA.
26*/
27
28#include <math.h>
29#import "config.h"
30
31#import <Foundation/NSArray.h>
32#import <Foundation/NSDebug.h>
33#import <Foundation/NSException.h>
34#import <Foundation/NSValue.h>
35#import "AppKit/NSAttributedString.h"
36#import "AppKit/NSBezierPath.h"
37#import "AppKit/NSColor.h"
38#import "AppKit/NSEvent.h"
39#import "AppKit/NSFont.h"
40#import "AppKit/NSGraphics.h"
41#import "AppKit/NSRulerMarker.h"
42#import "AppKit/NSRulerView.h"
43#import "AppKit/NSScrollView.h"
44#import "AppKit/NSStringDrawing.h"
45#import "GSGuiPrivate.h"
46
47#define MIN_LABEL_DISTANCE 40
48#define MIN_MARK_DISTANCE 5
49
50#define MARK_SIZE 2
51#define MID_MARK_SIZE 4
52#define BIG_MARK_SIZE 6
53#define LABEL_MARK_SIZE 11
54
55#define RULER_THICKNESS 16
56#define MARKER_THICKNESS 15
57
58#define DRAW_HASH_MARK(path, size)			\
59		do {					\
60		  if (_orientation == NSHorizontalRuler)\
61		    {					\
62		      [path relativeLineToPoint: NSMakePoint(0, size)];	\
63		    }					\
64		  else					\
65		    {					\
66		      [path relativeLineToPoint: NSMakePoint(size, 0)];	\
67		    }					\
68		} while (0)
69
70@interface GSRulerUnit : NSObject
71{
72  NSString *_unitName;
73  NSString *_abbreviation;
74  CGFloat   _conversionFactor;
75  NSArray  *_stepUpCycle;
76  NSArray  *_stepDownCycle;
77}
78
79+ (GSRulerUnit *) unitWithName: (NSString *)uName
80                  abbreviation: (NSString *)abbrev
81  unitToPointsConversionFactor: (CGFloat)factor
82                   stepUpCycle: (NSArray *)upCycle
83                 stepDownCycle: (NSArray *)downCycle;
84- (id) initWithUnitName: (NSString *)uName
85                abbreviation: (NSString *)abbrev
86unitToPointsConversionFactor: (CGFloat)factor
87                 stepUpCycle: (NSArray *)upCycle
88               stepDownCycle: (NSArray *)downCycle;
89- (NSString *) unitName;
90- (NSString *) abbreviation;
91- (CGFloat) conversionFactor;
92- (NSArray *) stepUpCycle;
93- (NSArray *) stepDownCycle;
94
95@end
96
97@implementation GSRulerUnit
98
99+ (GSRulerUnit *) unitWithName: (NSString *)uName
100                  abbreviation: (NSString *)abbrev
101  unitToPointsConversionFactor: (CGFloat)factor
102                   stepUpCycle: (NSArray *)upCycle
103                 stepDownCycle: (NSArray *)downCycle
104{
105  return [[[self alloc] initWithUnitName: uName
106                            abbreviation: abbrev
107            unitToPointsConversionFactor: factor
108                             stepUpCycle: upCycle
109                           stepDownCycle: downCycle] autorelease];
110}
111
112- (id) initWithUnitName: (NSString *)uName
113	   abbreviation: (NSString *)abbrev
114unitToPointsConversionFactor: (CGFloat)factor
115	    stepUpCycle: (NSArray *)upCycle
116	  stepDownCycle: (NSArray *)downCycle
117{
118  self = [super init];
119  if (self != nil)
120    {
121      ASSIGN(_unitName, uName);
122      ASSIGN(_abbreviation, abbrev);
123      _conversionFactor = factor;
124      ASSIGN(_stepUpCycle, upCycle);
125      ASSIGN(_stepDownCycle, downCycle);
126    }
127
128  return self;
129}
130
131- (NSString *) unitName
132{
133  return _unitName;
134}
135
136- (NSString *) abbreviation
137{
138  return _abbreviation;
139}
140
141- (CGFloat) conversionFactor
142{
143  return _conversionFactor;
144}
145
146- (NSArray *) stepUpCycle
147{
148  return _stepUpCycle;
149}
150
151- (NSArray *) stepDownCycle
152{
153  return _stepDownCycle;
154}
155
156- (void) dealloc
157{
158  [_unitName release];
159  [_abbreviation release];
160  [_stepUpCycle release];
161  [_stepDownCycle release];
162  [super dealloc];
163}
164
165@end
166
167
168@implementation NSRulerView
169
170/*
171 * Class variables
172 */
173static NSMutableDictionary *units = nil;
174
175/*
176 * Class methods
177 */
178+ (void) initialize
179{
180  if (self == [NSRulerView class])
181    {
182      NSArray *array05;
183      NSArray *array052;
184      NSArray *array2;
185      NSArray *array10;
186
187      [self setVersion: 1];
188
189      units = [[NSMutableDictionary alloc] init];
190      array05 = [NSArray arrayWithObject: [NSNumber numberWithFloat: 0.5]];
191      array052 = [NSArray arrayWithObjects: [NSNumber numberWithFloat: 0.5],
192                 [NSNumber numberWithFloat: 0.2], nil];
193      array2 = [NSArray arrayWithObject: [NSNumber numberWithFloat: 2.0]];
194      array10 = [NSArray arrayWithObject: [NSNumber numberWithFloat: 10.0]];
195      [self registerUnitWithName: @"Inches"
196                    abbreviation: @"in"
197    unitToPointsConversionFactor: 72.0
198                     stepUpCycle: array2
199                   stepDownCycle: array05];
200      [self registerUnitWithName: @"Centimeters"
201                    abbreviation: @"cm"
202    unitToPointsConversionFactor: 28.35
203                     stepUpCycle: array2
204                   stepDownCycle: array052];
205      [self registerUnitWithName: @"Points"
206                    abbreviation: @"pt"
207    unitToPointsConversionFactor: 1.0
208                     stepUpCycle: array10
209                   stepDownCycle: array05];
210      [self registerUnitWithName: @"Picas"
211                    abbreviation: @"pc"
212    unitToPointsConversionFactor: 12.0
213                     stepUpCycle: array2
214                   stepDownCycle: array05];
215    }
216}
217
218- (id) initWithScrollView: (NSScrollView *)aScrollView
219              orientation: (NSRulerOrientation)o
220{
221  self = [super initWithFrame: NSZeroRect];
222  if (self != nil)
223    {
224      [self setScrollView: aScrollView];
225      [self setOrientation: o];
226      [self setMeasurementUnits: @"Points"]; /* FIXME: should be user's pref */
227      [self setRuleThickness: RULER_THICKNESS];
228      [self setOriginOffset: 0.0];
229      [self setReservedThicknessForAccessoryView: 0.0];
230      if (o == NSHorizontalRuler)
231        {
232          [self setReservedThicknessForMarkers: MARKER_THICKNESS];
233	}
234      else
235        {
236          [self setReservedThicknessForMarkers: 0.0];
237	}
238      [self invalidateHashMarks];
239    }
240  return self;
241}
242
243+ (void) registerUnitWithName: (NSString *)uName
244                 abbreviation: (NSString *)abbreviation
245 unitToPointsConversionFactor: (CGFloat)conversionFactor
246                  stepUpCycle: (NSArray *)stepUpCycle
247                stepDownCycle: (NSArray *)stepDownCycle
248{
249  GSRulerUnit *u = [GSRulerUnit unitWithName: uName
250				abbreviation: abbreviation
251				unitToPointsConversionFactor: conversionFactor
252				stepUpCycle: stepUpCycle
253				stepDownCycle: stepDownCycle];
254  [units setObject: u forKey: uName];
255}
256
257- (void) setMeasurementUnits: (NSString *)uName
258{
259  GSRulerUnit *newUnit;
260
261  newUnit = [units objectForKey: uName];
262  if (newUnit == nil)
263    {
264      [NSException raise: NSInvalidArgumentException
265		   format: @"Unknown measurement unit %@", uName];
266    }
267  ASSIGN(_unit, newUnit);
268  [self invalidateHashMarks];
269}
270
271- (NSString *) measurementUnits
272{
273  return [_unit unitName];
274}
275
276- (void) setClientView: (NSView *)aView
277{
278  if (_clientView == aView)
279    return;
280
281  if (_clientView != nil
282      && [_clientView respondsToSelector:
283			@selector(rulerView:willSetClientView:)])
284    {
285      [_clientView rulerView: self willSetClientView: aView];
286    }
287  /* NB: We should not RETAIN the clientView.  */
288  _clientView = aView;
289  [self setMarkers: nil];
290  [self invalidateHashMarks];
291}
292
293- (BOOL) isOpaque
294{
295  return YES;
296}
297
298- (NSView *) clientView
299{
300  return _clientView;
301}
302
303- (void) setAccessoryView: (NSView *)aView
304{
305  /* FIXME/TODO: support for accessory views is not implemented */
306  ASSIGN(_accessoryView, aView);
307  [self setNeedsDisplay: YES];
308}
309
310- (NSView *) accessoryView
311{
312  return _accessoryView;
313}
314
315- (void) setOriginOffset: (CGFloat)offset
316{
317  _originOffset = offset;
318  [self invalidateHashMarks];
319}
320
321- (CGFloat) originOffset
322{
323  return _originOffset;
324}
325
326- (void) _verifyReservedThicknessForMarkers
327{
328  NSEnumerator *en;
329  NSRulerMarker *marker;
330  CGFloat maxThickness = _reservedThicknessForMarkers;
331
332  if (_markers == nil)
333    {
334      return;
335    }
336  en = [_markers objectEnumerator];
337  while ((marker = [en nextObject]) != nil)
338    {
339      CGFloat markerThickness;
340      markerThickness = [marker thicknessRequiredInRuler];
341      if (markerThickness > maxThickness)
342        {
343	  maxThickness = markerThickness;
344	}
345    }
346  if (maxThickness > _reservedThicknessForMarkers)
347    {
348      [self setReservedThicknessForMarkers: maxThickness];
349    }
350}
351
352- (void) setMarkers: (NSArray *)newMarkers
353{
354  if (newMarkers != nil && _clientView == nil)
355    {
356      [NSException raise: NSInternalInconsistencyException
357                  format: @"Cannot set markers without a client view"];
358    }
359  if (newMarkers != nil)
360    {
361      ASSIGN(_markers, [NSMutableArray arrayWithArray: newMarkers]);
362      [self _verifyReservedThicknessForMarkers];
363    }
364  else
365    {
366      ASSIGN(_markers, nil);
367    }
368  [self setNeedsDisplay: YES];
369}
370
371- (NSArray *) markers
372{
373  return _markers;
374}
375
376- (void) addMarker: (NSRulerMarker *)aMarker
377{
378  CGFloat markerThickness = [aMarker thicknessRequiredInRuler];
379
380  if (_clientView == nil)
381    {
382      [NSException raise: NSInternalInconsistencyException
383                 format: @"Cannot add a marker without a client view"];
384    }
385
386  if (markerThickness > [self reservedThicknessForMarkers])
387    {
388      [self setReservedThicknessForMarkers: markerThickness];
389    }
390  if (_markers == nil)
391    {
392      _markers = [[NSMutableArray alloc] initWithObjects: aMarker, nil];
393    }
394  else
395    {
396      [_markers addObject: aMarker];
397    }
398
399  [self setNeedsDisplay: YES];
400}
401
402- (void) removeMarker: (NSRulerMarker *)aMarker
403{
404  [_markers removeObject: aMarker];
405  [self setNeedsDisplay: YES];
406}
407
408- (BOOL) trackMarker: (NSRulerMarker *)aMarker
409      withMouseEvent: (NSEvent *)theEvent
410{
411  NSParameterAssert(aMarker != nil);
412
413  return [aMarker trackMouse: theEvent adding: YES];
414}
415
416- (NSRect) _rulerRect
417{
418  NSRect rect = [self bounds];
419  if (_orientation == NSHorizontalRuler)
420    {
421      rect.size.height = _ruleThickness;
422      if ([self isFlipped])
423        {
424	  rect.origin.y = [self baselineLocation];
425	}
426      else
427        {
428	  rect.origin.y = [self baselineLocation] - _ruleThickness;
429	}
430    }
431  else
432    {
433      rect.size.width = _ruleThickness;
434      rect.origin.x = [self baselineLocation];
435    }
436  return rect;
437}
438
439- (NSRect) _markersRect
440{
441  NSRect rect = [self bounds];
442  if (_orientation == NSHorizontalRuler)
443    {
444      rect.size.height = _reservedThicknessForMarkers;
445      if ([self isFlipped])
446        {
447	  rect.origin.y = _reservedThicknessForAccessoryView;
448	}
449      else
450        {
451	  rect.origin.y = _ruleThickness;
452	}
453    }
454  else
455    {
456      rect.size.width = _reservedThicknessForMarkers;
457      rect.origin.x = _reservedThicknessForAccessoryView;
458    }
459  return rect;
460}
461
462- (NSRulerMarker *) _markerAtPoint: (NSPoint)point
463{
464  NSEnumerator *markerEnum;
465  NSRulerMarker *marker;
466  BOOL flipped = [self isFlipped];
467
468  /* test markers in reverse order so that markers drawn on top
469     are tested before those underneath */
470  markerEnum = [_markers reverseObjectEnumerator];
471  while ((marker = [markerEnum nextObject]) != nil)
472    {
473      if (NSMouseInRect (point, [marker imageRectInRuler], flipped))
474        {
475	  return marker;
476	}
477    }
478  return nil;
479}
480
481- (void) mouseDown: (NSEvent*)theEvent
482{
483  NSPoint clickPoint;
484  BOOL flipped = [self isFlipped];
485
486  clickPoint = [self convertPoint: [theEvent locationInWindow]
487                         fromView: nil];
488  if (NSMouseInRect (clickPoint, [self _rulerRect], flipped))
489    {
490      if (_clientView != nil
491          && [_clientView respondsToSelector:
492			    @selector(rulerView:handleMouseDown:)])
493        {
494          [_clientView rulerView: self handleMouseDown: theEvent];
495        }
496    }
497  else if (NSMouseInRect (clickPoint, [self _markersRect], flipped))
498    {
499      NSRulerMarker *clickedMarker;
500
501      clickedMarker = [self _markerAtPoint: clickPoint];
502      if (clickedMarker != nil)
503        {
504	  [clickedMarker trackMouse: theEvent adding: NO];
505	}
506    }
507}
508
509- (void) moveRulerlineFromLocation: (CGFloat)oldLoc
510                        toLocation: (CGFloat)newLoc
511{
512  /* FIXME/TODO: not implemented */
513}
514
515- (void)drawRect: (NSRect)aRect
516{
517  [[NSColor controlColor] set];
518  NSRectFill(aRect);
519  [self drawHashMarksAndLabelsInRect: aRect];
520  [self drawMarkersInRect: aRect];
521}
522
523- (float) _stepForIndex: (int)index
524{
525  int newindex;
526  NSArray *stepCycle;
527
528  if (index > 0)
529    {
530      stepCycle = [_unit stepUpCycle];
531      newindex = (index - 1) % [stepCycle count];
532      return [[stepCycle objectAtIndex: newindex] floatValue];
533    }
534  else
535    {
536      stepCycle = [_unit stepDownCycle];
537      newindex = (-index) % [stepCycle count];
538      return 1 / [[stepCycle objectAtIndex: newindex] floatValue];
539    }
540}
541
542- (void) _verifyCachedValues
543{
544  if (! _cacheIsValid)
545    {
546      NSSize unitSize;
547      CGFloat cf;
548      int convIndex;
549
550      /* calculate the size one unit in document view has in the ruler */
551      cf = [_unit conversionFactor];
552      unitSize = [self convertSize: NSMakeSize(cf, cf)
553		       fromView: [_scrollView documentView]];
554
555      if (_orientation == NSHorizontalRuler)
556        {
557          _unitToRuler = unitSize.width;
558        }
559      else
560        {
561          _unitToRuler = unitSize.height;
562        }
563
564      /* Calculate distance between marks.  */
565      /* It must not be less than MIN_MARK_DISTANCE in ruler units
566       * and must obey the current unit's step cycles.  */
567      _markDistance = _unitToRuler;
568      convIndex = 0;
569      /* make sure it's smaller than MIN_MARK_DISTANCE */
570      while ((_markDistance) > MIN_MARK_DISTANCE)
571        {
572          _markDistance /= [self _stepForIndex: convIndex];
573          convIndex--;
574        }
575      /* find the first size that's not < MIN_MARK_DISTANCE */
576      while ((_markDistance) < MIN_MARK_DISTANCE)
577        {
578          convIndex++;
579          _markDistance *= [self _stepForIndex: convIndex];
580        }
581
582      /* calculate number of small marks in each bigger mark */
583      _marksToMidMark = GSRoundTowardsInfinity([self _stepForIndex: convIndex + 1]);
584      _marksToBigMark = _marksToMidMark
585                      * GSRoundTowardsInfinity([self _stepForIndex: convIndex + 2]);
586
587      /* Calculate distance between labels.
588         It must not be less than MIN_LABEL_DISTANCE. */
589      _labelDistance = _markDistance;
590      while (_labelDistance < MIN_LABEL_DISTANCE)
591        {
592          convIndex++;
593          _labelDistance *= [self _stepForIndex: convIndex];
594        }
595
596      /* number of small marks between two labels */
597      _marksToLabel = GSRoundTowardsInfinity(_labelDistance / _markDistance);
598
599      /* format of labels */
600      if (_labelDistance / _unitToRuler >= 1)
601        {
602          ASSIGN(_labelFormat, @"%1.f");
603        }
604      else
605        {
606          /* smallest integral value not less than log10(1/labelDistInUnits) */
607          int log = ceil(log10(1 / (_labelDistance / _unitToRuler)));
608          NSString *string = [NSString stringWithFormat: @"%%.%df", (int)log];
609          ASSIGN(_labelFormat, string);
610        }
611
612      _cacheIsValid = YES;
613    }
614}
615
616- (void) drawHashMarksAndLabelsInRect: (NSRect)aRect
617{
618  NSView *docView;
619  NSRect docBounds;
620  NSRect baselineRect;
621  NSRect visibleBaselineRect;
622  CGFloat firstBaselineLocation;
623  CGFloat firstVisibleLocation;
624  CGFloat lastVisibleLocation;
625  int firstVisibleMark;
626  int lastVisibleMark;
627  int mark;
628  int firstVisibleLabel;
629  int lastVisibleLabel;
630  int label;
631  CGFloat baselineLocation = [self baselineLocation];
632  NSPoint zeroPoint;
633  CGFloat zeroLocation;
634  NSBezierPath *path;
635  NSFont *font = [NSFont systemFontOfSize: [NSFont smallSystemFontSize]];
636  NSDictionary *attr = [[NSDictionary alloc]
637			   initWithObjectsAndKeys:
638			       font, NSFontAttributeName,
639			       [NSColor blackColor], NSForegroundColorAttributeName,
640			       nil];
641
642  docView = [_scrollView documentView];
643  docBounds = [docView bounds];
644
645  /* Calculate the location of 'zero' hash mark */
646  // _originOffset is an offset from document bounds origin, in doc coords
647  zeroPoint.x = docBounds.origin.x + _originOffset;
648  zeroPoint.y = docBounds.origin.y + _originOffset;
649  zeroPoint = [self convertPoint: zeroPoint fromView: docView];
650  if (_orientation == NSHorizontalRuler)
651    {
652      zeroLocation = zeroPoint.x;
653    }
654  else
655    {
656      zeroLocation = zeroPoint.y;
657    }
658
659  [self _verifyCachedValues];
660
661  /* Calculate the base line (corresponds to the document bounds) */
662  baselineRect = [self convertRect: docBounds  fromView: docView];
663  if (_orientation == NSHorizontalRuler)
664    {
665      baselineRect.origin.y = baselineLocation;
666      baselineRect.size.height = 1;
667      firstBaselineLocation = NSMinX(baselineRect);
668      visibleBaselineRect = NSIntersectionRect(baselineRect, aRect);
669      firstVisibleLocation = NSMinX(visibleBaselineRect);
670      lastVisibleLocation = NSMaxX(visibleBaselineRect);
671    }
672  else
673    {
674      baselineRect.origin.x = baselineLocation;
675      baselineRect.size.width = 1;
676      firstBaselineLocation = NSMinY(baselineRect);
677      visibleBaselineRect = NSIntersectionRect(baselineRect, aRect);
678      firstVisibleLocation = NSMinY(visibleBaselineRect);
679      lastVisibleLocation = NSMaxY(visibleBaselineRect);
680    }
681
682  /* draw the base line */
683  [[NSColor blackColor] set];
684  NSRectFill(visibleBaselineRect);
685
686  /* draw hash marks */
687  firstVisibleMark = ceil((firstVisibleLocation - zeroLocation)
688                          / _markDistance);
689  lastVisibleMark = floor((lastVisibleLocation - zeroLocation)
690                          / _markDistance);
691  path = [NSBezierPath new];
692
693  for (mark = firstVisibleMark; mark <= lastVisibleMark; mark++)
694    {
695      CGFloat markLocation;
696
697      markLocation = zeroLocation + mark * _markDistance;
698      if (_orientation == NSHorizontalRuler)
699        {
700	  [path moveToPoint: NSMakePoint(markLocation, baselineLocation)];
701        }
702      else
703        {
704	  [path moveToPoint: NSMakePoint(baselineLocation, markLocation)];
705        }
706
707      if ((mark % _marksToLabel) == 0)
708        {
709          DRAW_HASH_MARK(path, LABEL_MARK_SIZE);
710        }
711      else if ((mark % _marksToBigMark) == 0)
712        {
713          DRAW_HASH_MARK(path, BIG_MARK_SIZE);
714        }
715      else if ((mark % _marksToMidMark) == 0)
716        {
717          DRAW_HASH_MARK(path, MID_MARK_SIZE);
718        }
719      else
720        {
721          DRAW_HASH_MARK(path, MARK_SIZE);
722        }
723    }
724  [path stroke];
725  RELEASE(path);
726
727  /* draw labels */
728  /* FIXME: shouldn't be using NSCell to draw labels? */
729  firstVisibleLabel = floor((firstVisibleLocation - zeroLocation)
730                          / (_marksToLabel * _markDistance));
731  lastVisibleLabel = floor((lastVisibleLocation - zeroLocation)
732                          / (_marksToLabel * _markDistance));
733  /* firstVisibleLabel can be to the left of the visible ruler area.
734     This is OK because just part of the label can be visible to the left
735     when scrolling. However, it should not be drawn if outside of the
736     baseline. */
737  if (zeroLocation + firstVisibleLabel * _marksToLabel * _markDistance
738      < firstBaselineLocation)
739    {
740      firstVisibleLabel++;
741    }
742
743  for (label = firstVisibleLabel; label <= lastVisibleLabel; label++)
744    {
745      CGFloat labelLocation = zeroLocation + label * _marksToLabel * _markDistance;
746      // This has to be a float or we need to change the label format
747      float labelValue = (labelLocation - zeroLocation) / _unitToRuler;
748      NSString *labelString = [NSString stringWithFormat: _labelFormat, labelValue];
749      NSSize size = [labelString sizeWithAttributes: attr];
750      NSPoint labelPosition;
751
752      if (_orientation == NSHorizontalRuler)
753        {
754	  labelPosition.x = labelLocation + 1;
755	  labelPosition.y = baselineLocation + LABEL_MARK_SIZE + 4 - size.height;
756        }
757      else
758        {
759	  labelPosition.x = baselineLocation + _ruleThickness - size.width;
760	  labelPosition.y = labelLocation + 1;
761        }
762      [labelString drawAtPoint: labelPosition withAttributes: attr];
763    }
764
765  RELEASE(attr);
766}
767
768- (void) drawMarkersInRect: (NSRect)aRect
769{
770  NSRulerMarker *marker;
771  NSEnumerator *en;
772
773  en = [_markers objectEnumerator];
774  while ((marker = [en nextObject]) != nil)
775    {
776      [marker drawRect: aRect];
777    }
778}
779
780- (void) invalidateHashMarks
781{
782  _cacheIsValid = NO;
783  [self setNeedsDisplay:YES];
784}
785
786- (void) setScrollView: (NSScrollView *)scrollView
787{
788  /* We do NOT retain the scrollView; the scrollView is retaining us.  */
789  _scrollView = scrollView;
790}
791
792- (NSScrollView *) scrollView
793{
794  return _scrollView;
795}
796
797- (void) setOrientation: (NSRulerOrientation)o
798{
799  _orientation = o;
800}
801
802- (NSRulerOrientation)orientation
803{
804  return _orientation;
805}
806
807- (void) setReservedThicknessForAccessoryView: (CGFloat)thickness
808{
809  _reservedThicknessForAccessoryView = thickness;
810  [_scrollView tile];
811}
812
813- (CGFloat) reservedThicknessForAccessoryView
814{
815  return _reservedThicknessForAccessoryView;
816}
817
818- (void) setReservedThicknessForMarkers: (CGFloat)thickness
819{
820  _reservedThicknessForMarkers = thickness;
821  [_scrollView tile];
822}
823
824- (CGFloat) reservedThicknessForMarkers
825{
826  return _reservedThicknessForMarkers;
827}
828
829- (void) setRuleThickness: (CGFloat)thickness
830{
831  _ruleThickness = thickness;
832  [_scrollView tile];
833}
834
835- (CGFloat) ruleThickness
836{
837  return _ruleThickness;
838}
839
840- (CGFloat) requiredThickness
841{
842  return [self ruleThickness]
843    + [self reservedThicknessForAccessoryView]
844    + [self reservedThicknessForMarkers];
845}
846
847- (CGFloat) baselineLocation
848{
849  return [self reservedThicknessForAccessoryView]
850    + [self reservedThicknessForMarkers];
851}
852
853- (BOOL) isFlipped
854{
855  if (_orientation == NSVerticalRuler)
856    {
857      return [[_scrollView documentView] isFlipped];
858    }
859  return YES;
860}
861
862- (void) encodeWithCoder: (NSCoder *)encoder
863{
864  [super encodeWithCoder: encoder];
865  /* FIXME/TODO: not implemented */
866  return;
867}
868
869- (id) initWithCoder: (NSCoder *)decoder
870{
871  self = [super initWithCoder: decoder];
872  if (self == nil)
873    return nil;
874
875  /* FIXME/TODO: not implemented */
876  return self;
877}
878
879- (void) dealloc
880{
881  RELEASE(_unit);
882  RELEASE(_accessoryView);
883  RELEASE(_markers);
884  RELEASE(_labelFormat);
885  [super dealloc];
886}
887
888@end
889
890