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