3 * Copyright (c) 2007 - 2011, Blue Static <http://www.bluestatic.org>
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 #import "BSLineNumberRulerView.h"
22 #import "Breakpoint.h"
23 #import "BSSourceView.h"
25 @interface BSLineNumberRulerView (Private)
26 - (void)computeLineIndex;
27 - (NSAttributedString*)attributedStringForLineNumber:(NSUInteger)line;
28 - (NSDictionary*)fontAttributes;
29 - (void)drawBreakpointInRect:(NSRect)rect;
30 - (void)drawProgramCounterInRect:(NSRect)rect;
31 - (void)drawMarkerInRect:(NSRect)rect
32 fillColor:(NSColor*)fill
33 strokeColor:(NSColor*)stroke;
38 // The default width of the ruler.
39 const CGFloat kDefaultWidth = 30.0;
41 // Padding between the right edge of the ruler and the line number string.
42 const CGFloat kRulerRightPadding = 2.5;
46 @implementation BSLineNumberRulerView {
47 BSSourceView* _sourceView; // Weak, owns this.
49 // A vector (thus 0-based) map of line numbers (indices) to character indices
50 // in the text storage.
51 std::vector<NSUInteger> _lineIndex;
54 - (instancetype)initWithSourceView:(BSSourceView*)sourceView
56 if (self = [super initWithScrollView:[sourceView scrollView]
57 orientation:NSVerticalRuler]) {
58 _sourceView = sourceView;
59 [self setClientView:[[_sourceView scrollView] documentView]];
60 [self setRuleThickness:kDefaultWidth];
67 [self setClientView:[[_sourceView scrollView] documentView]];
68 [self setRuleThickness:kDefaultWidth];
71 - (void)drawHashMarksAndLabelsInRect:(NSRect)rect
73 // Draw the background color.
74 [[NSColor windowBackgroundColor] set];
75 [NSBezierPath fillRect:rect];
77 // Draw the right stroke.
78 [[NSColor grayColor] setStroke];
79 [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(rect), NSMinY(rect))
80 toPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
82 // Get some common elements of the source view.
83 NSTextView* textView = [_sourceView textView];
84 NSLayoutManager* layoutManager = [textView layoutManager];
85 NSTextContainer* textContainer = [textView textContainer];
86 NSRect visibleRect = [[[self scrollView] contentView] bounds];
88 // Get the visible glyph range, as NSRulerView only draws in the visible rect.
89 NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:visibleRect
90 inTextContainer:textContainer];
91 NSRange characterRange = [layoutManager characterRangeForGlyphRange:visibleGlyphRange
92 actualGlyphRange:NULL];
94 // Load any markers. The superview takes care of filtering out for just the
95 // curently displayed file.
96 NSSet<NSNumber*>* markers = [_sourceView markers];
98 // Go through the lines.
99 const NSRange kNullRange = NSMakeRange(NSNotFound, 0);
100 const CGFloat yOffset = [textView textContainerInset].height;
102 const size_t lineCount = _lineIndex.size();
103 std::vector<NSUInteger>::iterator element =
104 std::lower_bound(_lineIndex.begin(),
106 characterRange.location);
107 for (NSUInteger line = std::distance(_lineIndex.begin(), element);
108 line < lineCount; ++line) {
109 NSUInteger firstCharacterIndex = _lineIndex[line];
110 // Stop after iterating past the end of the visible range.
111 if (firstCharacterIndex > NSMaxRange(characterRange))
114 NSUInteger rectCount;
115 NSRectArray frameRects = [layoutManager rectArrayForCharacterRange:NSMakeRange(firstCharacterIndex, 0)
116 withinSelectedCharacterRange:kNullRange
117 inTextContainer:textContainer
118 rectCount:&rectCount];
120 NSUInteger lineNumber = line + 1;
121 NSAttributedString* lineNumberString =
122 [self attributedStringForLineNumber:lineNumber];
123 NSSize stringSize = [lineNumberString size];
125 CGFloat yCoord = yOffset + NSMinY(frameRects[0]) - NSMinY(visibleRect);
126 NSRect drawRect = NSMakeRect(NSWidth(rect) - stringSize.width - kRulerRightPadding,
127 yCoord + (NSHeight(frameRects[0]) - stringSize.height) / 2.0,
128 NSWidth(rect) - kRulerRightPadding,
129 NSHeight(frameRects[0]));
130 [lineNumberString drawInRect:drawRect];
132 // Draw any markers. Adjust the drawRect to be the entire width of the
133 // ruler, rather than just the width of the string.
134 drawRect.origin.x = NSMinX(rect);
136 if ([markers containsObject:@(lineNumber)]) {
137 [self drawBreakpointInRect:drawRect];
139 if (_sourceView.markedLine == lineNumber) {
140 [self drawProgramCounterInRect:drawRect];
146 - (void)performLayout
148 [self computeLineIndex];
150 // Determine the width of the ruler based on the line count.
151 if (_lineIndex.empty()) {
152 [self setRuleThickness:kDefaultWidth];
154 NSUInteger lastElement = _lineIndex.back() + 1;
155 NSAttributedString* lastElementString = [self attributedStringForLineNumber:lastElement];
156 NSSize boundingSize = [lastElementString size];
157 [self setRuleThickness:std::max(kDefaultWidth, boundingSize.width)];
160 [self setNeedsDisplay:YES];
163 - (NSUInteger)lineNumberAtPoint:(NSPoint)point
165 // Get some common elements of the source view.
166 NSTextView* textView = [_sourceView textView];
167 NSLayoutManager* layoutManager = [textView layoutManager];
168 NSTextContainer* textContainer = [textView textContainer];
169 NSRect visibleRect = [[[self scrollView] contentView] bounds];
170 point.y += NSMinY(visibleRect); // Adjust for scroll offset.
172 const CGFloat kWidth = NSWidth([self bounds]);
173 const NSRange kNullRange = NSMakeRange(NSNotFound, 0);
174 const size_t lineCount = _lineIndex.size();
175 for (NSUInteger line = 0; line < lineCount; ++line) {
176 NSUInteger firstCharacterIndex = _lineIndex[line];
178 NSUInteger rectCount;
179 NSRectArray frameRects =
180 [layoutManager rectArrayForCharacterRange:NSMakeRange(firstCharacterIndex, 0)
181 withinSelectedCharacterRange:kNullRange
182 inTextContainer:textContainer
183 rectCount:&rectCount];
184 for (NSUInteger i = 0; i < rectCount; ++i) {
185 frameRects[i].size.width = kWidth;
186 if (NSPointInRect(point, frameRects[i])) {
194 - (void)mouseDown:(NSEvent*)theEvent
196 NSPoint point = [theEvent locationInWindow];
197 point = [self convertPoint:point fromView:nil];
198 NSUInteger line = [self lineNumberAtPoint:point];
199 if (line != NSNotFound)
200 [_sourceView.delegate gutterClickedAtLine:line forFile:_sourceView.file];
203 // Private /////////////////////////////////////////////////////////////////////
206 * Iterates over the text storage system and computes a map of line numbers to
207 * first character index for a line's frame rectangle.
209 - (void)computeLineIndex
213 NSString* text = [[_sourceView textView] string];
214 NSUInteger stringLength = [text length];
215 NSUInteger index = 0;
217 while (index < stringLength) {
218 _lineIndex.push_back(index);
219 index = NSMaxRange([text lineRangeForRange:NSMakeRange(index, 0)]);
222 if (_lineIndex.empty())
225 NSUInteger lineEnd, contentEnd;
226 [text getLineStart:NULL
228 contentsEnd:&contentEnd
229 forRange:NSMakeRange(_lineIndex.back(), 0)];
230 if (contentEnd < lineEnd)
231 _lineIndex.push_back(index);
235 * Takes in a line number and returns a formatted attributed string, usable
238 - (NSAttributedString*)attributedStringForLineNumber:(unsigned long)line
240 NSString* format = [NSString stringWithFormat:@"%lu", line];
241 return [[NSAttributedString alloc] initWithString:format
242 attributes:[self fontAttributes]];
246 * Returns the dictionary for an NSAttributedString with which the line numbers
249 - (NSDictionary*)fontAttributes
251 NSFont* font = [NSFont fontWithDescriptor:[[BSSourceView sourceFont] fontDescriptor] size:11.0];
253 NSFontAttributeName : font,
254 NSForegroundColorAttributeName : [NSColor grayColor],
259 * Draws a breakpoint (a blue arrow) in the specified rectangle.
261 - (void)drawBreakpointInRect:(NSRect)rect
263 [self drawMarkerInRect:rect
264 fillColor:[NSColor colorWithDeviceRed:0.004 green:0.557 blue:0.851 alpha:1.0]
265 strokeColor:[NSColor colorWithDeviceRed:0.0 green:0.404 blue:0.804 alpha:1.0]];
269 * Draws the program counter (a red arrow) in the specified rectangle.
271 - (void)drawProgramCounterInRect:(NSRect)rect
273 [self drawMarkerInRect:rect
274 fillColor:[[NSColor redColor] colorWithAlphaComponent:0.5]
275 strokeColor:[NSColor colorWithDeviceRed:0.788 green:0 blue:0 alpha:1.0]];
279 * Draws the arrow shape in a given color.
281 - (void)drawMarkerInRect:(NSRect)rect
282 fillColor:(NSColor*)fill
283 strokeColor:(NSColor*)stroke
285 [[NSGraphicsContext currentContext] saveGraphicsState];
287 NSBezierPath* path = [NSBezierPath bezierPath];
289 const CGFloat kPadding = 2.0;
290 const CGFloat kArrowWidth = 7.0;
291 const CGFloat minX = NSMinX(rect) + kPadding;
292 const CGFloat maxX = NSMaxX(rect);
293 const CGFloat minY = NSMinY(rect) + kPadding;
295 [path moveToPoint:NSMakePoint(minX, minY)]; // initial origin
296 [path lineToPoint:NSMakePoint(maxX - kArrowWidth, minY)]; // upper right
297 [path lineToPoint:NSMakePoint(maxX - kPadding, NSMidY(rect))]; // point
298 [path lineToPoint:NSMakePoint(maxX - kArrowWidth, NSMaxY(rect) - kPadding)]; // lower right
299 [path lineToPoint:NSMakePoint(minX, NSMaxY(rect) - kPadding)]; // lower left
300 [path lineToPoint:NSMakePoint(minX, minY - 1)]; // upper left
306 [path setLineWidth:2];
309 [[NSGraphicsContext currentContext] restoreGraphicsState];