Clean up |-[BSLineNumberRulerView drawBreakpointInRect:]| to use constants
[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 #import "Breakpoint.h"
22 #import "BSSourceView.h"
23
24 @interface BSLineNumberRulerView (Private)
25 - (void)computeLineIndex;
26 - (NSAttributedString*)attributedStringForLineNumber:(NSUInteger)line;
27 - (NSDictionary*)fontAttributes;
28 - (void)drawBreakpointInRect:(NSRect)rect;
29 - (void)drawProgramCounterInRect:(NSRect)rect;
30 @end
31
32 // Constants {{
33
34 // The default width of the ruler.
35 const CGFloat kDefaultWidth = 30.0;
36
37 // Padding between the right edge of the ruler and the line number string.
38 const CGFloat kRulerRightPadding = 2.5;
39
40 // }}
41
42
43 @implementation BSLineNumberRulerView
44
45 - (id)initWithSourceView:(BSSourceView*)sourceView
46 {
47 if (self = [super initWithScrollView:[sourceView scrollView]
48 orientation:NSVerticalRuler]) {
49 sourceView_ = sourceView;
50 [self setClientView:[[sourceView_ scrollView] documentView]];
51 [self setRuleThickness:kDefaultWidth];
52 }
53 return self;
54 }
55
56 - (void)awakeFromNib
57 {
58 [self setClientView:[[sourceView_ scrollView] documentView]];
59 [self setRuleThickness:kDefaultWidth];
60 }
61
62 - (void)drawHashMarksAndLabelsInRect:(NSRect)rect
63 {
64 // Draw the background color.
65 [[NSColor colorWithDeviceRed:0.871 green:0.871 blue:0.871 alpha:1] set];
66 [NSBezierPath fillRect:rect];
67
68 // Draw the right stroke.
69 [[NSColor grayColor] setStroke];
70 [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(rect), NSMinY(rect))
71 toPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
72
73 // Get some common elements of the source view.
74 NSTextView* textView = [sourceView_ textView];
75 NSLayoutManager* layoutManager = [textView layoutManager];
76 NSTextContainer* textContainer = [textView textContainer];
77 NSRect visibleRect = [[[self scrollView] contentView] bounds];
78
79 // Get the visible glyph range, as NSRulerView only draws in the visible rect.
80 NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:visibleRect
81 inTextContainer:textContainer];
82 NSRange characterRange = [layoutManager characterRangeForGlyphRange:visibleGlyphRange
83 actualGlyphRange:NULL];
84
85 // Load any markers. The superview takes care of filtering out for just the
86 // curently displayed file.
87 NSSet* markers = [sourceView_ markers];
88
89 // Go through the lines.
90 const NSRange kNullRange = NSMakeRange(NSNotFound, 0);
91 const CGFloat yOffset = [textView textContainerInset].height;
92
93 const size_t lineCount = lineIndex_.size();
94 std::vector<NSUInteger>::iterator element =
95 std::lower_bound(lineIndex_.begin(),
96 lineIndex_.end(),
97 characterRange.location);
98 for (NSUInteger line = std::distance(lineIndex_.begin(), element);
99 line < lineCount; ++line) {
100 NSUInteger firstCharacterIndex = lineIndex_[line];
101 // Stop after iterating past the end of the visible range.
102 if (firstCharacterIndex > NSMaxRange(characterRange))
103 break;
104
105 NSUInteger rectCount;
106 NSRectArray frameRects = [layoutManager rectArrayForCharacterRange:NSMakeRange(firstCharacterIndex, 0)
107 withinSelectedCharacterRange:kNullRange
108 inTextContainer:textContainer
109 rectCount:&rectCount];
110 if (frameRects) {
111 NSUInteger lineNumber = line + 1;
112 NSAttributedString* lineNumberString =
113 [self attributedStringForLineNumber:lineNumber];
114 NSSize stringSize = [lineNumberString size];
115
116 CGFloat yCoord = yOffset + NSMinY(frameRects[0]) - NSMinY(visibleRect);
117 NSRect drawRect = NSMakeRect(NSWidth(rect) - stringSize.width - kRulerRightPadding,
118 yCoord + (NSHeight(frameRects[0]) - stringSize.height) / 2.0,
119 NSWidth(rect) - kRulerRightPadding,
120 NSHeight(frameRects[0]));
121 [lineNumberString drawInRect:drawRect];
122
123 // Draw any markers. Adjust the drawRect to be the entire width of the
124 // ruler, rather than just the width of the string.
125 drawRect.origin.x = NSMinX(rect);
126
127 Breakpoint* test = [[[Breakpoint alloc] initWithLine:lineNumber
128 inFile:[sourceView_ file]] autorelease];
129 if ([markers containsObject:test]) {
130 [self drawBreakpointInRect:drawRect];
131 }
132 if (sourceView_.markedLine == lineNumber) {
133 [self drawProgramCounterInRect:drawRect];
134 }
135 }
136 }
137 }
138
139 - (void)performLayout
140 {
141 [self computeLineIndex];
142
143 // Determine the width of the ruler based on the line count.
144 NSUInteger lastElement = lineIndex_.back() + 1;
145 NSAttributedString* lastElementString = [self attributedStringForLineNumber:lastElement];
146 NSSize boundingSize = [lastElementString size];
147 [self setRuleThickness:std::max(kDefaultWidth, boundingSize.width)];
148
149 [self setNeedsDisplay:YES];
150 }
151
152 - (NSUInteger)lineNumberAtPoint:(NSPoint)point
153 {
154 // Get some common elements of the source view.
155 NSTextView* textView = [sourceView_ textView];
156 NSLayoutManager* layoutManager = [textView layoutManager];
157 NSTextContainer* textContainer = [textView textContainer];
158 NSRect visibleRect = [[[self scrollView] contentView] bounds];
159 point.y += NSMinY(visibleRect); // Adjust for scroll offset.
160
161 const CGFloat kWidth = NSWidth([self bounds]);
162 const NSRange kNullRange = NSMakeRange(NSNotFound, 0);
163 const size_t lineCount = lineIndex_.size();
164 for (NSUInteger line = 0; line < lineCount; ++line) {
165 NSUInteger firstCharacterIndex = lineIndex_[line];
166
167 NSUInteger rectCount;
168 NSRectArray frameRects =
169 [layoutManager rectArrayForCharacterRange:NSMakeRange(firstCharacterIndex, 0)
170 withinSelectedCharacterRange:kNullRange
171 inTextContainer:textContainer
172 rectCount:&rectCount];
173 for (NSUInteger i = 0; i < rectCount; ++i) {
174 frameRects[i].size.width = kWidth;
175 if (NSPointInRect(point, frameRects[i])) {
176 return line + 1;
177 }
178 }
179 }
180 return NSNotFound;
181 }
182
183 - (void)mouseDown:(NSEvent*)theEvent
184 {
185 NSPoint point = [theEvent locationInWindow];
186 point = [self convertPoint:point fromView:nil];
187 NSUInteger line = [self lineNumberAtPoint:point];
188 if (line != NSNotFound)
189 [sourceView_.delegate gutterClickedAtLine:line forFile:sourceView_.file];
190 }
191
192 // Private /////////////////////////////////////////////////////////////////////
193
194 /**
195 * Iterates over the text storage system and computes a map of line numbers to
196 * first character index for a line's frame rectangle.
197 */
198 - (void)computeLineIndex
199 {
200 lineIndex_.clear();
201
202 NSString* text = [[sourceView_ textView] string];
203 NSUInteger stringLength = [text length];
204 NSUInteger index = 0;
205
206 while (index < stringLength) {
207 lineIndex_.push_back(index);
208 index = NSMaxRange([text lineRangeForRange:NSMakeRange(index, 0)]);
209 }
210
211 NSUInteger lineEnd, contentEnd;
212 [text getLineStart:NULL
213 end:&lineEnd
214 contentsEnd:&contentEnd
215 forRange:NSMakeRange(lineIndex_.back(), 0)];
216 if (contentEnd < lineEnd)
217 lineIndex_.push_back(index);
218 }
219
220 /**
221 * Takes in a line number and returns a formatted attributed string, usable
222 * for drawing.
223 */
224 - (NSAttributedString*)attributedStringForLineNumber:(NSUInteger)line
225 {
226 NSString* format = [NSString stringWithFormat:@"%d", line];
227 return [[[NSAttributedString alloc] initWithString:format
228 attributes:[self fontAttributes]] autorelease];
229 }
230
231 /**
232 * Returns the dictionary for an NSAttributedString with which the line numbers
233 * will be drawn.
234 */
235 - (NSDictionary*)fontAttributes
236 {
237 NSFont* font = [NSFont fontWithName:@"Menlo" size:10.0];
238 if (!font)
239 font = [NSFont fontWithName:@"Monaco" size:10.0];
240 return [NSDictionary dictionaryWithObjectsAndKeys:
241 font, NSFontAttributeName,
242 [NSColor grayColor], NSForegroundColorAttributeName,
243 nil
244 ];
245 }
246
247 /**
248 * Draws a breakpoint (a blue arrow) in the specified rectangle.
249 */
250 - (void)drawBreakpointInRect:(NSRect)rect
251 {
252 [[NSGraphicsContext currentContext] saveGraphicsState];
253
254 NSBezierPath* path = [NSBezierPath bezierPath];
255
256 const CGFloat kPadding = 2.0;
257 const CGFloat kArrowWidth = 7.0;
258 const CGFloat minX = NSMinX(rect) + kPadding;
259 const CGFloat maxX = NSMaxX(rect);
260 const CGFloat minY = NSMinY(rect) + kPadding;
261
262 [path moveToPoint:NSMakePoint(minX, minY)]; // initial origin
263 [path lineToPoint:NSMakePoint(maxX - kArrowWidth, minY)]; // upper right
264 [path lineToPoint:NSMakePoint(maxX - kPadding, NSMidY(rect))]; // point
265 [path lineToPoint:NSMakePoint(maxX - kArrowWidth, NSMaxY(rect) - kPadding)]; // lower right
266 [path lineToPoint:NSMakePoint(minX, NSMaxY(rect) - kPadding)]; // lower left
267 [path lineToPoint:NSMakePoint(minX, minY - 1)]; // upper left
268
269 [[NSColor colorWithDeviceRed:0.004 green:0.557 blue:0.851 alpha:1.0] set];
270 [path fill];
271
272 [[NSColor colorWithDeviceRed:0.0 green:0.404 blue:0.804 alpha:1.0] set];
273 [path setLineWidth:2];
274 [path stroke];
275
276 [[NSGraphicsContext currentContext] restoreGraphicsState];
277 }
278
279 /**
280 * Draws the program counter (a red arrow) in the specified rectangle.
281 */
282 - (void)drawProgramCounterInRect:(NSRect)rect
283 {
284 [[NSGraphicsContext currentContext] saveGraphicsState];
285
286 const CGFloat kArrowWidth = 10.0;
287 const CGFloat kMaxX = NSMaxX(rect);
288 const CGFloat kMidY = NSMidY(rect);
289
290 NSBezierPath* path = [NSBezierPath bezierPath];
291 [path moveToPoint:NSMakePoint(kMaxX, kMidY)];
292 [path lineToPoint:NSMakePoint(kMaxX - kArrowWidth, NSMaxY(rect))];
293 [path lineToPoint:NSMakePoint(kMaxX - kArrowWidth, NSMinY(rect))];
294 [path lineToPoint:NSMakePoint(kMaxX, kMidY)];
295
296 [[[NSColor redColor] colorWithAlphaComponent:0.5] set];
297 [path fill];
298
299 [[NSColor redColor] setStroke];
300 [path stroke];
301
302 [[NSGraphicsContext currentContext] restoreGraphicsState];
303 }
304
305 @end