1//
2//  HFLineCountingView.m
3//  HexFiend_2
4//
5//  Copyright 2007 ridiculous_fish. All rights reserved.
6//
7
8#import <HexFiend/HFLineCountingView.h>
9#import <HexFiend/HFLineCountingRepresenter.h>
10#import <HexFiend/HFFunctions.h>
11
12#define TIME_LINE_NUMBERS 0
13
14#define HEX_LINE_NUMBERS_HAVE_0X_PREFIX 0
15
16#define INVALID_LINE_COUNT NSUIntegerMax
17
18#if TIME_LINE_NUMBERS
19@interface HFTimingTextView : NSTextView
20@end
21@implementation HFTimingTextView
22- (void)drawRect:(NSRect)rect {
23    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
24    [super drawRect:rect];
25    CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
26    NSLog(@"TextView line number time: %f", endTime - startTime);
27}
28@end
29#endif
30
31@implementation HFLineCountingView
32
33- (void)_sharedInitLineCountingView {
34    layoutManager = [[NSLayoutManager alloc] init];
35    textStorage = [[NSTextStorage alloc] init];
36    [textStorage addLayoutManager:layoutManager];
37    textContainer = [[NSTextContainer alloc] init];
38    [textContainer setLineFragmentPadding:(CGFloat)5];
39    [textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)];
40    [layoutManager addTextContainer:textContainer];
41}
42
43- (instancetype)initWithFrame:(NSRect)frame {
44    self = [super initWithFrame:frame];
45    if (self) {
46        [self _sharedInitLineCountingView];
47    }
48    return self;
49}
50
51- (void)dealloc {
52    HFUnregisterViewForWindowAppearanceChanges(self, registeredForAppNotifications);
53    [_font release];
54    [layoutManager release];
55    [textContainer release];
56    [textStorage release];
57    [textAttributes release];
58    [super dealloc];
59}
60
61- (void)encodeWithCoder:(NSCoder *)coder {
62    HFASSERT([coder allowsKeyedCoding]);
63    [super encodeWithCoder:coder];
64    [coder encodeObject:_font forKey:@"HFFont"];
65    [coder encodeDouble:_lineHeight forKey:@"HFLineHeight"];
66    [coder encodeObject:_representer forKey:@"HFRepresenter"];
67    [coder encodeInt64:_bytesPerLine forKey:@"HFBytesPerLine"];
68    [coder encodeInt64:_lineNumberFormat forKey:@"HFLineNumberFormat"];
69    [coder encodeBool:useStringDrawingPath forKey:@"HFUseStringDrawingPath"];
70}
71
72- (instancetype)initWithCoder:(NSCoder *)coder {
73    HFASSERT([coder allowsKeyedCoding]);
74    self = [super initWithCoder:coder];
75    [self _sharedInitLineCountingView];
76    _font = [[coder decodeObjectForKey:@"HFFont"] retain];
77    _lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"];
78    _representer = [coder decodeObjectForKey:@"HFRepresenter"];
79    _bytesPerLine = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerLine"];
80    _lineNumberFormat = (NSUInteger)[coder decodeInt64ForKey:@"HFLineNumberFormat"];
81    useStringDrawingPath = [coder decodeBoolForKey:@"HFUseStringDrawingPath"];
82    return self;
83}
84
85- (BOOL)isFlipped { return YES; }
86
87- (void)getLineNumberFormatString:(char *)outString length:(NSUInteger)length {
88    HFLineNumberFormat format = self.lineNumberFormat;
89    if (format == HFLineNumberFormatDecimal) {
90        strlcpy(outString, "%llu", length);
91    }
92    else if (format == HFLineNumberFormatHexadecimal) {
93#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX
94        // we want a format string like 0x%08llX
95        snprintf(outString, length, "0x%%0%lullX", (unsigned long)self.representer.digitCount - 2);
96#else
97        // we want a format string like %08llX
98        snprintf(outString, length, "%%0%lullX", (unsigned long)self.representer.digitCount);
99#endif
100    }
101    else {
102        strlcpy(outString, "", length);
103    }
104}
105
106- (void)windowDidChangeKeyStatus:(NSNotification *)note {
107    USE(note);
108    [self setNeedsDisplay:YES];
109}
110
111- (void)viewDidMoveToWindow {
112    HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications);
113    registeredForAppNotifications = YES;
114    [super viewDidMoveToWindow];
115}
116
117- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
118    HFUnregisterViewForWindowAppearanceChanges(self, NO);
119    [super viewWillMoveToWindow:newWindow];
120}
121
122- (void)drawDividerWithClip:(NSRect)clipRect {
123    USE(clipRect);
124
125
126#if 1
127    NSInteger edges = _representer.borderedEdges;
128    NSRect bounds = self.bounds;
129
130
131    // -1 means to draw no edges
132    if (edges == -1) {
133        edges = 0;
134    }
135
136    [_representer.borderColor set];
137
138    if ((edges & (1 << NSMinXEdge)) > 0) {
139        NSRect lineRect = bounds;
140        lineRect.size.width = 1;
141        lineRect.origin.x = 0;
142        if (NSIntersectsRect(lineRect, clipRect)) {
143            NSRectFill(lineRect);
144        }
145    }
146
147    if ((edges & (1 << NSMaxXEdge)) > 0) {
148        NSRect lineRect = bounds;
149        lineRect.size.width = 1;
150        lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width;
151        if (NSIntersectsRect(lineRect, clipRect)) {
152            NSRectFill(lineRect);
153        }
154    }
155
156    if ((edges & (1 << NSMinYEdge)) > 0) {
157        NSRect lineRect = bounds;
158        lineRect.size.height = 1;
159        lineRect.origin.y = 0;
160        if (NSIntersectsRect(lineRect, clipRect)) {
161            NSRectFill(lineRect);
162        }
163    }
164
165    if ((edges & (1 << NSMaxYEdge)) > 0) {
166        NSRect lineRect = bounds;
167        lineRect.size.height = 1;
168        lineRect.origin.y = NSMaxY(bounds) - lineRect.size.height;
169        if (NSIntersectsRect(lineRect, clipRect)) {
170            NSRectFill(lineRect);
171        }
172    }
173
174
175    // Backwards compatibility to always draw a border on the edge with the interior shadow
176
177    NSRect lineRect = bounds;
178    lineRect.size.width = 1;
179    NSInteger shadowEdge = _representer.interiorShadowEdge;
180    if (shadowEdge == NSMaxXEdge) {
181        lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width;
182    } else if (shadowEdge == NSMinXEdge) {
183        lineRect.origin.x = NSMinX(bounds);
184    } else {
185        lineRect = NSZeroRect;
186    }
187
188    if (NSIntersectsRect(lineRect, clipRect)) {
189        NSRectFill(lineRect);
190    }
191
192#else
193
194
195    if (NSIntersectsRect(lineRect, clipRect)) {
196        // this looks better when we have no shadow
197        [[NSColor lightGrayColor] set];
198        NSRect bounds = self.bounds;
199        NSRect lineRect = bounds;
200        lineRect.origin.x += lineRect.size.width - 2;
201        lineRect.size.width = 1;
202        NSRectFill(NSIntersectionRect(lineRect, clipRect));
203        [[NSColor whiteColor] set];
204        lineRect.origin.x += 1;
205        NSRectFill(NSIntersectionRect(lineRect, clipRect));
206    }
207#endif
208}
209
210static inline int common_prefix_length(const char *a, const char *b) {
211    int i;
212    for (i=0; ; i++) {
213        char ac = a[i];
214        char bc = b[i];
215        if (ac != bc || ac == 0 || bc == 0) break;
216    }
217    return i;
218}
219
220/* Drawing with NSLayoutManager is necessary because the 10_2 typesetting behavior used by the old string drawing does the wrong thing for fonts like Bitstream Vera Sans Mono.  Also it's an optimization for drawing the shadow. */
221- (void)drawLineNumbersWithClipLayoutManagerPerLine:(NSRect)clipRect {
222#if TIME_LINE_NUMBERS
223    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
224#endif
225    NSUInteger previousTextStorageCharacterCount = [textStorage length];
226
227    CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
228    NSRect textRect = self.bounds;
229    textRect.size.height = _lineHeight;
230    textRect.origin.y -= verticalOffset * _lineHeight;
231    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
232    unsigned long long lineValue = lineIndex * _bytesPerLine;
233    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
234    char previousBuff[256];
235    int previousStringLength = (int)previousTextStorageCharacterCount;
236    BOOL conversionResult = [[textStorage string] getCString:previousBuff maxLength:sizeof previousBuff encoding:NSASCIIStringEncoding];
237    HFASSERT(conversionResult);
238    while (linesRemaining--) {
239        char formatString[64];
240        [self getLineNumberFormatString:formatString length:sizeof formatString];
241
242        if (NSIntersectsRect(textRect, clipRect)) {
243            NSString *replacementCharacters = nil;
244            NSRange replacementRange;
245            char buff[256];
246            int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue);
247            HFASSERT(newStringLength > 0);
248            int prefixLength = common_prefix_length(previousBuff, buff);
249            HFASSERT(prefixLength <= newStringLength);
250            HFASSERT(prefixLength <= previousStringLength);
251            replacementRange = NSMakeRange(prefixLength, previousStringLength - prefixLength);
252            replacementCharacters = [[NSString alloc] initWithBytesNoCopy:buff + prefixLength length:newStringLength - prefixLength encoding:NSASCIIStringEncoding freeWhenDone:NO];
253            NSUInteger glyphCount;
254            [textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters];
255            if (previousTextStorageCharacterCount == 0) {
256                NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, nil];
257                [textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)];
258                [atts release];
259            }
260            glyphCount = [layoutManager numberOfGlyphs];
261            if (glyphCount > 0) {
262                CGFloat maxX = NSMaxX([layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphCount - 1 effectiveRange:NULL]);
263                [layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, glyphCount) atPoint:NSMakePoint(textRect.origin.x + textRect.size.width - maxX, textRect.origin.y)];
264            }
265            previousTextStorageCharacterCount = newStringLength;
266            [replacementCharacters release];
267            memcpy(previousBuff, buff, newStringLength + 1);
268            previousStringLength = newStringLength;
269        }
270        textRect.origin.y += _lineHeight;
271        lineIndex++;
272        lineValue = HFSum(lineValue, _bytesPerLine);
273    }
274#if TIME_LINE_NUMBERS
275    CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
276    NSLog(@"Line number time: %f", endTime - startTime);
277#endif
278}
279
280- (void)drawLineNumbersWithClipStringDrawing:(NSRect)clipRect {
281    CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
282    NSRect textRect = self.bounds;
283    textRect.size.height = _lineHeight;
284    textRect.size.width -= 5;
285    textRect.origin.y -= verticalOffset * _lineHeight + 1;
286    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
287    unsigned long long lineValue = lineIndex * _bytesPerLine;
288    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
289    if (! textAttributes) {
290        NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
291        [mutableStyle setAlignment:NSRightTextAlignment];
292        NSParagraphStyle *paragraphStyle = [mutableStyle copy];
293        [mutableStyle release];
294        textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
295        [paragraphStyle release];
296    }
297
298    char formatString[64];
299    [self getLineNumberFormatString:formatString length:sizeof formatString];
300
301    while (linesRemaining--) {
302        if (NSIntersectsRect(textRect, clipRect)) {
303            char buff[256];
304            int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue);
305            HFASSERT(newStringLength > 0);
306            NSString *string = [[NSString alloc] initWithBytesNoCopy:buff length:newStringLength encoding:NSASCIIStringEncoding freeWhenDone:NO];
307            [string drawInRect:textRect withAttributes:textAttributes];
308            [string release];
309        }
310        textRect.origin.y += _lineHeight;
311        lineIndex++;
312        if (linesRemaining > 0) lineValue = HFSum(lineValue, _bytesPerLine); //we could do this unconditionally, but then we risk overflow
313    }
314}
315
316- (NSUInteger)characterCountForLineRange:(HFRange)range {
317    HFASSERT(range.length <= NSUIntegerMax);
318    NSUInteger characterCount;
319
320    NSUInteger lineCount = ll2l(range.length);
321    const NSUInteger stride = _bytesPerLine;
322    HFLineCountingRepresenter *rep = self.representer;
323    HFLineNumberFormat format = self.lineNumberFormat;
324    if (format == HFLineNumberFormatDecimal) {
325        unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine);
326        characterCount = lineCount /* newlines */;
327        while (lineCount--) {
328            characterCount += HFCountDigitsBase10(lineValue);
329            lineValue += stride;
330        }
331    }
332    else if (format == HFLineNumberFormatHexadecimal) {
333        characterCount = ([rep digitCount] + 1) * lineCount; // +1 for newlines
334    }
335    else {
336        characterCount = -1;
337    }
338    return characterCount;
339}
340
341- (NSString *)newLineStringForRange:(HFRange)range {
342    HFASSERT(range.length <= NSUIntegerMax);
343    if(range.length == 0)
344        return [[NSString alloc] init]; // Placate the analyzer.
345
346    NSUInteger lineCount = ll2l(range.length);
347    const NSUInteger stride = _bytesPerLine;
348    unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine);
349    NSUInteger characterCount = [self characterCountForLineRange:range];
350    char *buffer = check_malloc(characterCount);
351    NSUInteger bufferIndex = 0;
352
353    char formatString[64];
354    [self getLineNumberFormatString:formatString length:sizeof formatString];
355
356    while (lineCount--) {
357        int charCount = sprintf(buffer + bufferIndex, formatString, lineValue + self.representer.valueOffset);
358        HFASSERT(charCount > 0);
359        bufferIndex += charCount;
360        buffer[bufferIndex++] = '\n';
361        lineValue += stride;
362    }
363    HFASSERT(bufferIndex == characterCount);
364
365    NSString *string = [[NSString alloc] initWithBytesNoCopy:(void *)buffer length:bufferIndex encoding:NSASCIIStringEncoding freeWhenDone:YES];
366    return string;
367}
368
369- (void)updateLayoutManagerWithLineIndex:(unsigned long long)startingLineIndex lineCount:(NSUInteger)linesRemaining {
370    const BOOL debug = NO;
371    [textStorage beginEditing];
372
373    if (storedLineCount == INVALID_LINE_COUNT) {
374        /* This usually indicates that our bytes per line or line number format changed, and we need to just recalculate everything */
375        NSString *string = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)];
376        [textStorage replaceCharactersInRange:NSMakeRange(0, [textStorage length]) withString:string];
377        [string release];
378
379    }
380    else {
381        HFRange leftRangeToReplace, rightRangeToReplace;
382        HFRange leftRangeToStore, rightRangeToStore;
383
384        HFRange oldRange = HFRangeMake(storedLineIndex, storedLineCount);
385        HFRange newRange = HFRangeMake(startingLineIndex, linesRemaining);
386        HFRange rangeToPreserve = HFIntersectionRange(oldRange, newRange);
387
388        if (rangeToPreserve.length == 0) {
389            leftRangeToReplace = oldRange;
390            leftRangeToStore = newRange;
391            rightRangeToReplace = HFZeroRange;
392            rightRangeToStore = HFZeroRange;
393        }
394        else {
395            if (debug) NSLog(@"Preserving %llu", rangeToPreserve.length);
396            HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, newRange));
397            HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, oldRange));
398            const unsigned long long maxPreserve = HFMaxRange(rangeToPreserve);
399            leftRangeToReplace = HFRangeMake(oldRange.location, rangeToPreserve.location - oldRange.location);
400            leftRangeToStore = HFRangeMake(newRange.location, rangeToPreserve.location - newRange.location);
401            rightRangeToReplace = HFRangeMake(maxPreserve, HFMaxRange(oldRange) - maxPreserve);
402            rightRangeToStore = HFRangeMake(maxPreserve, HFMaxRange(newRange) - maxPreserve);
403        }
404
405        if (debug) NSLog(@"Changing %@ -> %@", HFRangeToString(oldRange), HFRangeToString(newRange));
406        if (debug) NSLog(@"LEFT: %@ -> %@", HFRangeToString(leftRangeToReplace), HFRangeToString(leftRangeToStore));
407        if (debug) NSLog(@"RIGHT: %@ -> %@", HFRangeToString(rightRangeToReplace), HFRangeToString(rightRangeToStore));
408
409        HFASSERT(leftRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(leftRangeToReplace, oldRange));
410        HFASSERT(rightRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(rightRangeToReplace, oldRange));
411
412        if (leftRangeToReplace.length > 0 || leftRangeToStore.length > 0) {
413            NSUInteger charactersToDelete = [self characterCountForLineRange:leftRangeToReplace];
414            NSRange rangeToDelete = NSMakeRange(0, charactersToDelete);
415            if (leftRangeToStore.length == 0) {
416                [textStorage deleteCharactersInRange:rangeToDelete];
417                if (debug) NSLog(@"Left deleting text range %@", NSStringFromRange(rangeToDelete));
418            }
419            else {
420                NSString *leftRangeString = [self newLineStringForRange:leftRangeToStore];
421                [textStorage replaceCharactersInRange:rangeToDelete withString:leftRangeString];
422                if (debug) NSLog(@"Replacing text range %@ with %@", NSStringFromRange(rangeToDelete), leftRangeString);
423                [leftRangeString release];
424            }
425        }
426
427        if (rightRangeToReplace.length > 0 || rightRangeToStore.length > 0) {
428            NSUInteger charactersToDelete = [self characterCountForLineRange:rightRangeToReplace];
429            NSUInteger stringLength = [textStorage length];
430            HFASSERT(charactersToDelete <= stringLength);
431            NSRange rangeToDelete = NSMakeRange(stringLength - charactersToDelete, charactersToDelete);
432            if (rightRangeToStore.length == 0) {
433                [textStorage deleteCharactersInRange:rangeToDelete];
434                if (debug) NSLog(@"Right deleting text range %@", NSStringFromRange(rangeToDelete));
435            }
436            else {
437                NSString *rightRangeString = [self newLineStringForRange:rightRangeToStore];
438                [textStorage replaceCharactersInRange:rangeToDelete withString:rightRangeString];
439                if (debug) NSLog(@"Replacing text range %@ with %@ (for range %@)", NSStringFromRange(rangeToDelete), rightRangeString, HFRangeToString(rightRangeToStore));
440                [rightRangeString release];
441            }
442        }
443    }
444
445    if (!textAttributes) {
446        NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
447        [mutableStyle setAlignment:NSRightTextAlignment];
448        NSParagraphStyle *paragraphStyle = [mutableStyle copy];
449        [mutableStyle release];
450        textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
451        [paragraphStyle release];
452        [textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])];
453    }
454
455    [textStorage endEditing];
456
457#if ! NDEBUG
458    NSString *comparisonString = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)];
459    if (! [comparisonString isEqualToString:[textStorage string]]) {
460        NSLog(@"Not equal!");
461        NSLog(@"Expected:\n%@", comparisonString);
462        NSLog(@"Actual:\n%@", [textStorage string]);
463    }
464    HFASSERT([comparisonString isEqualToString:[textStorage string]]);
465    [comparisonString release];
466#endif
467
468    storedLineIndex = startingLineIndex;
469    storedLineCount = linesRemaining;
470}
471
472- (void)drawLineNumbersWithClipSingleStringDrawing:(NSRect)clipRect {
473    USE(clipRect);
474    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
475    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
476
477    CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
478    CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1.5;
479    NSRect textRect = self.bounds;
480    textRect.size.width -= 5;
481    textRect.origin.y -= verticalOffset;
482    textRect.size.height += verticalOffset + _lineHeight;
483
484    NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
485    [mutableStyle setAlignment:NSRightTextAlignment];
486    [mutableStyle setMinimumLineHeight:_lineHeight];
487    [mutableStyle setMaximumLineHeight:_lineHeight];
488    NSParagraphStyle *paragraphStyle = [mutableStyle copy];
489    [mutableStyle release];
490    NSColor *color = [[NSColor textColor] colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
491    color = [NSColor colorWithRed:color.redComponent green:color.greenComponent blue:color.blueComponent alpha:0.75];
492    NSDictionary *_textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont fontWithName:_font.fontName size:9], NSFontAttributeName, color, NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
493    [paragraphStyle release];
494
495
496    NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
497    [string drawInRect:textRect withAttributes:_textAttributes];
498    [string release];
499    [_textAttributes release];
500}
501
502- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect {
503    USE(clipRect);
504    const CGFloat cellTextContainerPadding = 2.f;
505    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
506    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
507
508    CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
509    CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
510    NSRect textRect = self.bounds;
511    textRect.size.width -= 5;
512    textRect.origin.y -= verticalOffset;
513    textRect.origin.x += cellTextContainerPadding;
514    textRect.size.height += verticalOffset;
515
516    if (! textAttributes) {
517        NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
518        [mutableStyle setAlignment:NSRightTextAlignment];
519        [mutableStyle setMinimumLineHeight:_lineHeight];
520        [mutableStyle setMaximumLineHeight:_lineHeight];
521        NSParagraphStyle *paragraphStyle = [mutableStyle copy];
522        [mutableStyle release];
523        textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor controlTextColor], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
524        [paragraphStyle release];
525    }
526
527    NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
528    NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:textAttributes];
529    [string release];
530    NSCell *cell = [[NSCell alloc] initTextCell:@""];
531    [cell setAttributedStringValue:attributedString];
532    [cell drawWithFrame:textRect inView:self];
533    [[NSColor purpleColor] set];
534    NSFrameRect(textRect);
535    [cell release];
536    [attributedString release];
537}
538
539- (void)drawLineNumbersWithClipFullLayoutManager:(NSRect)clipRect {
540    USE(clipRect);
541    unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
542    NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
543    if (lineIndex != storedLineIndex || linesRemaining != storedLineCount) {
544        [self updateLayoutManagerWithLineIndex:lineIndex lineCount:linesRemaining];
545    }
546
547    CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
548
549    NSPoint textPoint = self.bounds.origin;
550    textPoint.y -= verticalOffset * _lineHeight;
551    [layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) atPoint:textPoint];
552}
553
554- (void)drawLineNumbersWithClip:(NSRect)clipRect {
555#if TIME_LINE_NUMBERS
556    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
557#endif
558    NSInteger drawingMode = 3; // (useStringDrawingPath ? 1 : 3);
559    switch (drawingMode) {
560        // Drawing can't be done right if fonts are wider than expected, but all
561        // of these have rather nasty behavior in that case. I've commented what
562        // that behavior is; the comment is hypothetical 'could' if it shouldn't
563        // actually be a problem in practice.
564        // TODO: Make a drawing mode that is "Fonts could get clipped if too wide"
565        //       because that seems like better behavior than any of these.
566        case 0:
567            // Most fonts are too wide and every character gets piled on right (unreadable).
568            [self drawLineNumbersWithClipLayoutManagerPerLine:clipRect];
569            break;
570        case 1:
571            // Last characters could get omitted (*not* clipped) if too wide.
572            // Also, most fonts have bottoms clipped (very unsigntly).
573            [self drawLineNumbersWithClipStringDrawing:clipRect];
574            break;
575        case 2:
576            // Most fonts are too wide and wrap (breaks numbering).
577            [self drawLineNumbersWithClipFullLayoutManager:clipRect];
578            break;
579        case 3:
580            // Fonts could wrap if too wide (breaks numbering).
581            // *Note that that this is the only mode that generally works.*
582            [self drawLineNumbersWithClipSingleStringDrawing:clipRect];
583            break;
584        case 4:
585            // Most fonts are too wide and wrap (breaks numbering).
586            [self drawLineNumbersWithClipSingleStringCellDrawing:clipRect];
587            break;
588    }
589#if TIME_LINE_NUMBERS
590    CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
591    NSLog(@"Line number time: %f", endTime - startTime);
592#endif
593}
594
595- (void)drawRect:(NSRect)clipRect {
596    [self drawDividerWithClip:clipRect];
597    [self drawLineNumbersWithClip:clipRect];
598}
599
600- (void)setLineRangeToDraw:(HFFPRange)range {
601    if (! HFFPRangeEqualsRange(range, _lineRangeToDraw)) {
602        _lineRangeToDraw = range;
603        [self setNeedsDisplay:YES];
604    }
605}
606
607- (void)setBytesPerLine:(NSUInteger)val {
608    if (_bytesPerLine != val) {
609        _bytesPerLine = val;
610        storedLineCount = INVALID_LINE_COUNT;
611        [self setNeedsDisplay:YES];
612    }
613}
614
615- (void)setLineNumberFormat:(HFLineNumberFormat)format {
616    if (format != _lineNumberFormat) {
617        _lineNumberFormat = format;
618        storedLineCount = INVALID_LINE_COUNT;
619        [self setNeedsDisplay:YES];
620    }
621}
622
623- (BOOL)canUseStringDrawingPathForFont:(NSFont *)testFont {
624    NSString *name = [testFont fontName];
625    // No, Menlo does not work here.
626    return [name isEqualToString:@"Monaco"] || [name isEqualToString:@"Courier"] || [name isEqualToString:@"Consolas"];
627}
628
629- (void)setFont:(NSFont *)val {
630    if (val != _font) {
631        [_font release];
632        _font = [val copy];
633        [textStorage deleteCharactersInRange:NSMakeRange(0, [textStorage length])]; //delete the characters so we know to set the font next time we render
634        [textAttributes release];
635        textAttributes = nil;
636        storedLineCount = INVALID_LINE_COUNT;
637        useStringDrawingPath = [self canUseStringDrawingPathForFont:_font];
638        [self setNeedsDisplay:YES];
639    }
640}
641
642- (void)setLineHeight:(CGFloat)height {
643    if (_lineHeight != height) {
644        _lineHeight = height;
645        [self setNeedsDisplay:YES];
646    }
647}
648
649- (void)setFrameSize:(NSSize)size {
650    [super setFrameSize:size];
651    [textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)];
652}
653
654- (void)mouseDown:(NSEvent *)event {
655    USE(event);
656    // [_representer cycleLineNumberFormat];
657}
658
659- (void)scrollWheel:(NSEvent *)scrollEvent {
660    [_representer.controller scrollWithScrollEvent:scrollEvent];
661}
662
663+ (NSUInteger)digitsRequiredToDisplayLineNumber:(unsigned long long)lineNumber inFormat:(HFLineNumberFormat)format {
664    switch (format) {
665        case HFLineNumberFormatDecimal: return HFCountDigitsBase10(lineNumber);
666#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX
667        case HFLineNumberFormatHexadecimal: return 2 + HFCountDigitsBase16(lineNumber);
668#else
669        case HFLineNumberFormatHexadecimal: return HFCountDigitsBase16(lineNumber);
670#endif
671        default: return 0;
672    }
673}
674
675@end
676