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