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