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