Pad and vertically center the line numbers
[macgdbp.git] / Source / BSLineNumberRulerView.mm
1 /*
2 * MacGDBp
3 * Copyright (c) 2007 - 2011, Blue Static <http://www.bluestatic.org>
4 *
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.
8 *
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.
12 *
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
15 */
16
17 #import "BSLineNumberRulerView.h"
18
19 #include <algorithm>
20
21 @interface BSLineNumberRulerView (Private)
22 - (void)computeLineIndex;
23 - (NSAttributedString*)attributedStringForLineNumber:(NSUInteger)line;
24 - (NSDictionary*)fontAttributes;
25 @end
26
27 // Constants {{
28
29 // The default width of the ruler.
30 const CGFloat kDefaultWidth = 30.0;
31
32 // Padding between the right edge of the ruler and the line number string.
33 const CGFloat kRulerRightPadding = 2.5;
34
35 // }}
36
37
38 @implementation BSLineNumberRulerView
39
40 - (id)initWithScrollView:(NSScrollView*)scrollView
41 {
42 if (self = [super initWithScrollView:scrollView orientation:NSVerticalRuler]) {
43 [self setClientView:[scrollView documentView]];
44 [self setRuleThickness:kDefaultWidth];
45 }
46 return self;
47 }
48
49 - (void)awakeFromNib
50 {
51 [self setClientView:[[self scrollView] documentView]];
52 [self setRuleThickness:kDefaultWidth];
53 }
54
55 - (void)drawHashMarksAndLabelsInRect:(NSRect)rect
56 {
57 // Draw the background color.
58 [[NSColor colorWithDeviceRed:0.871 green:0.871 blue:0.871 alpha:1] set];
59 [NSBezierPath fillRect:rect];
60
61 // Draw the right stroke.
62 [[NSColor grayColor] setStroke];
63 [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(rect), NSMinY(rect))
64 toPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
65
66 // Get some common elements of the source view.
67 NSView* view = [self clientView];
68 if (![view isKindOfClass:[NSTextView class]])
69 return;
70 NSTextView* textView = (NSTextView*)view;
71 NSLayoutManager* layoutManager = [textView layoutManager];
72 NSTextContainer* textContainer = [textView textContainer];
73 NSRect visibleRect = [[[self scrollView] contentView] bounds];
74
75 // Get the visible glyph range, as NSRulerView only draws in the visible rect.
76 NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:visibleRect
77 inTextContainer:textContainer];
78 NSRange characterRange = [layoutManager characterRangeForGlyphRange:visibleGlyphRange
79 actualGlyphRange:NULL];
80
81 // Go through the lines.
82 const NSRange kNullRange = NSMakeRange(NSNotFound, 0);
83 const CGFloat yOffset = [textView textContainerInset].height;
84
85 size_t lineCount = lineIndex_.size();
86 std::vector<NSUInteger>::iterator element =
87 std::lower_bound(lineIndex_.begin(),
88 lineIndex_.end(),
89 characterRange.location);
90 for (NSUInteger line = std::distance(lineIndex_.begin(), element);
91 line < lineCount; ++line) {
92 NSUInteger firstCharacterIndex = lineIndex_[line];
93 NSLog(@"line = %d @ %d / %d", line, firstCharacterIndex, lineCount);
94 // Stop after iterating past the end of the visible range.
95 if (firstCharacterIndex > NSMaxRange(characterRange))
96 break;
97
98 NSUInteger rectCount;
99 NSRectArray frameRects = [layoutManager rectArrayForCharacterRange:NSMakeRange(firstCharacterIndex, 0)
100 withinSelectedCharacterRange:kNullRange
101 inTextContainer:textContainer
102 rectCount:&rectCount];
103 if (frameRects) {
104 NSUInteger lineNumber = line + 1;
105 NSAttributedString* lineNumberString =
106 [self attributedStringForLineNumber:lineNumber];
107 NSSize stringSize = [lineNumberString size];
108
109 CGFloat yCoord = yOffset + NSMinY(frameRects[0]) - NSMinY(visibleRect);
110 NSRect drawRect = NSMakeRect(NSWidth(rect) - stringSize.width - kRulerRightPadding,
111 yCoord + (NSHeight(frameRects[0]) - stringSize.height) / 2.0,
112 NSWidth(rect) - kRulerRightPadding,
113 NSHeight(frameRects[0]));
114 [lineNumberString drawInRect:drawRect];
115 }
116 }
117 }
118
119 - (void)performLayout
120 {
121 [self computeLineIndex];
122
123 // Determine the width of the ruler based on the line count.
124 NSUInteger lastElement = lineIndex_.back() + 1;
125 NSAttributedString* lastElementString = [self attributedStringForLineNumber:lastElement];
126 NSSize boundingSize = [lastElementString size];
127 [self setRuleThickness:std::max(kDefaultWidth, boundingSize.width)];
128 }
129
130 // Private /////////////////////////////////////////////////////////////////////
131
132 /**
133 * Iterates over the text storage system and computes a map of line numbers to
134 * first character index for a line's frame rectangle.
135 */
136 - (void)computeLineIndex
137 {
138 lineIndex_.clear();
139
140 NSView* view = [self clientView];
141 if (![view isKindOfClass:[NSTextView class]])
142 return;
143
144 NSString* text = [(NSTextView*)view string];
145 NSUInteger stringLength = [text length];
146 NSUInteger index = 0;
147
148 while (index < stringLength) {
149 lineIndex_.push_back(index);
150 index = NSMaxRange([text lineRangeForRange:NSMakeRange(index, 0)]);
151 }
152
153 NSUInteger lineEnd, contentEnd;
154 [text getLineStart:NULL
155 end:&lineEnd
156 contentsEnd:&contentEnd
157 forRange:NSMakeRange(lineIndex_.back(), 0)];
158 if (contentEnd < lineEnd)
159 lineIndex_.push_back(index);
160
161 NSLog(@"line count = %d", lineIndex_.size());
162 }
163
164 /**
165 * Takes in a line number and returns a formatted attributed string, usable
166 * for drawing.
167 */
168 - (NSAttributedString*)attributedStringForLineNumber:(NSUInteger)line
169 {
170 NSString* format = [NSString stringWithFormat:@"%d", line];
171 return [[[NSAttributedString alloc] initWithString:format
172 attributes:[self fontAttributes]] autorelease];
173 }
174
175 /**
176 * Returns the dictionary for an NSAttributedString with which the line numbers
177 * will be drawn.
178 */
179 - (NSDictionary*)fontAttributes
180 {
181 return [NSDictionary dictionaryWithObjectsAndKeys:
182 [NSFont fontWithName:@"Monaco" size:9.0], NSFontAttributeName,
183 [NSColor grayColor], NSForegroundColorAttributeName,
184 nil
185 ];
186 }
187
188 @end