Bump project version to 212.1.
[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 #include <vector>
21
22 #import "Breakpoint.h"
23 #import "BSSourceView.h"
24
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;
34 @end
35
36 // Constants {{
37
38 // The default width of the ruler.
39 const CGFloat kDefaultWidth = 30.0;
40
41 // Padding between the right edge of the ruler and the line number string.
42 const CGFloat kRulerRightPadding = 2.5;
43
44 // }}
45
46 @implementation BSLineNumberRulerView {
47 BSSourceView* _sourceView; // Weak, owns this.
48
49 // A vector (thus 0-based) map of line numbers (indices) to character indices
50 // in the text storage.
51 std::vector<NSUInteger> _lineIndex;
52 }
53
54 - (instancetype)initWithSourceView:(BSSourceView*)sourceView
55 {
56 if (self = [super initWithScrollView:[sourceView scrollView]
57 orientation:NSVerticalRuler]) {
58 _sourceView = sourceView;
59 [self setClientView:[[_sourceView scrollView] documentView]];
60 [self setRuleThickness:kDefaultWidth];
61 }
62 return self;
63 }
64
65 - (void)awakeFromNib
66 {
67 [self setClientView:[[_sourceView scrollView] documentView]];
68 [self setRuleThickness:kDefaultWidth];
69 }
70
71 - (void)drawHashMarksAndLabelsInRect:(NSRect)rect
72 {
73 // Draw the background color.
74 [[NSColor windowBackgroundColor] set];
75 [NSBezierPath fillRect:rect];
76
77 // Draw the right stroke.
78 [[NSColor grayColor] setStroke];
79 [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(rect), NSMinY(rect))
80 toPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
81
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];
87
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];
93
94 // Load any markers. The superview takes care of filtering out for just the
95 // curently displayed file.
96 NSSet<NSNumber*>* markers = [_sourceView markers];
97
98 // Go through the lines.
99 const NSRange kNullRange = NSMakeRange(NSNotFound, 0);
100 const CGFloat yOffset = [textView textContainerInset].height;
101
102 const size_t lineCount = _lineIndex.size();
103 std::vector<NSUInteger>::iterator element =
104 std::lower_bound(_lineIndex.begin(),
105 _lineIndex.end(),
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))
112 break;
113
114 NSUInteger rectCount;
115 NSRectArray frameRects = [layoutManager rectArrayForCharacterRange:NSMakeRange(firstCharacterIndex, 0)
116 withinSelectedCharacterRange:kNullRange
117 inTextContainer:textContainer
118 rectCount:&rectCount];
119 if (frameRects) {
120 NSUInteger lineNumber = line + 1;
121 NSAttributedString* lineNumberString =
122 [self attributedStringForLineNumber:lineNumber];
123 NSSize stringSize = [lineNumberString size];
124
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];
131
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);
135
136 if ([markers containsObject:@(lineNumber)]) {
137 [self drawBreakpointInRect:drawRect];
138 }
139 if (_sourceView.markedLine == lineNumber) {
140 [self drawProgramCounterInRect:drawRect];
141 }
142 }
143 }
144 }
145
146 - (void)performLayout
147 {
148 [self computeLineIndex];
149
150 // Determine the width of the ruler based on the line count.
151 if (_lineIndex.empty()) {
152 [self setRuleThickness:kDefaultWidth];
153 } else {
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)];
158 }
159
160 [self setNeedsDisplay:YES];
161 }
162
163 - (NSUInteger)lineNumberAtPoint:(NSPoint)point
164 {
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.
171
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];
177
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])) {
187 return line + 1;
188 }
189 }
190 }
191 return NSNotFound;
192 }
193
194 - (void)mouseDown:(NSEvent*)theEvent
195 {
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];
201 }
202
203 // Private /////////////////////////////////////////////////////////////////////
204
205 /**
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.
208 */
209 - (void)computeLineIndex
210 {
211 _lineIndex.clear();
212
213 NSString* text = [[_sourceView textView] string];
214 NSUInteger stringLength = [text length];
215 NSUInteger index = 0;
216
217 while (index < stringLength) {
218 _lineIndex.push_back(index);
219 index = NSMaxRange([text lineRangeForRange:NSMakeRange(index, 0)]);
220 }
221
222 if (_lineIndex.empty())
223 return;
224
225 NSUInteger lineEnd, contentEnd;
226 [text getLineStart:NULL
227 end:&lineEnd
228 contentsEnd:&contentEnd
229 forRange:NSMakeRange(_lineIndex.back(), 0)];
230 if (contentEnd < lineEnd)
231 _lineIndex.push_back(index);
232 }
233
234 /**
235 * Takes in a line number and returns a formatted attributed string, usable
236 * for drawing.
237 */
238 - (NSAttributedString*)attributedStringForLineNumber:(unsigned long)line
239 {
240 NSString* format = [NSString stringWithFormat:@"%lu", line];
241 return [[NSAttributedString alloc] initWithString:format
242 attributes:[self fontAttributes]];
243 }
244
245 /**
246 * Returns the dictionary for an NSAttributedString with which the line numbers
247 * will be drawn.
248 */
249 - (NSDictionary*)fontAttributes
250 {
251 NSFont* font = [NSFont fontWithDescriptor:[[BSSourceView sourceFont] fontDescriptor] size:11.0];
252 return @{
253 NSFontAttributeName : font,
254 NSForegroundColorAttributeName : [NSColor grayColor],
255 };
256 }
257
258 /**
259 * Draws a breakpoint (a blue arrow) in the specified rectangle.
260 */
261 - (void)drawBreakpointInRect:(NSRect)rect
262 {
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]];
266 }
267
268 /**
269 * Draws the program counter (a red arrow) in the specified rectangle.
270 */
271 - (void)drawProgramCounterInRect:(NSRect)rect
272 {
273 [self drawMarkerInRect:rect
274 fillColor:[[NSColor redColor] colorWithAlphaComponent:0.5]
275 strokeColor:[NSColor colorWithDeviceRed:0.788 green:0 blue:0 alpha:1.0]];
276 }
277
278 /**
279 * Draws the arrow shape in a given color.
280 */
281 - (void)drawMarkerInRect:(NSRect)rect
282 fillColor:(NSColor*)fill
283 strokeColor:(NSColor*)stroke
284 {
285 [[NSGraphicsContext currentContext] saveGraphicsState];
286
287 NSBezierPath* path = [NSBezierPath bezierPath];
288
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;
294
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
301
302 [fill set];
303 [path fill];
304
305 [stroke set];
306 [path setLineWidth:2];
307 [path stroke];
308
309 [[NSGraphicsContext currentContext] restoreGraphicsState];
310 }
311
312 @end