690 lines
29 KiB
Objective-C
690 lines
29 KiB
Objective-C
//
|
|
// HFLineCountingView.m
|
|
// HexFiend_2
|
|
//
|
|
// Copyright 2007 ridiculous_fish. All rights reserved.
|
|
//
|
|
|
|
#import <HexFiend/HFLineCountingView.h>
|
|
#import <HexFiend/HFLineCountingRepresenter.h>
|
|
#import <HexFiend/HFFunctions.h>
|
|
|
|
#define TIME_LINE_NUMBERS 0
|
|
|
|
#define HEX_LINE_NUMBERS_HAVE_0X_PREFIX 0
|
|
|
|
#define INVALID_LINE_COUNT NSUIntegerMax
|
|
|
|
#if TIME_LINE_NUMBERS
|
|
@interface HFTimingTextView : NSTextView
|
|
@end
|
|
@implementation HFTimingTextView
|
|
- (void)drawRect:(NSRect)rect {
|
|
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
|
[super drawRect:rect];
|
|
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
|
|
NSLog(@"TextView line number time: %f", endTime - startTime);
|
|
}
|
|
@end
|
|
#endif
|
|
|
|
@implementation HFLineCountingView
|
|
|
|
- (void)_sharedInitLineCountingView {
|
|
layoutManager = [[NSLayoutManager alloc] init];
|
|
textStorage = [[NSTextStorage alloc] init];
|
|
[textStorage addLayoutManager:layoutManager];
|
|
textContainer = [[NSTextContainer alloc] init];
|
|
[textContainer setLineFragmentPadding:(CGFloat)5];
|
|
[textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)];
|
|
[layoutManager addTextContainer:textContainer];
|
|
}
|
|
|
|
- (instancetype)initWithFrame:(NSRect)frame {
|
|
self = [super initWithFrame:frame];
|
|
if (self) {
|
|
[self _sharedInitLineCountingView];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
HFUnregisterViewForWindowAppearanceChanges(self, registeredForAppNotifications);
|
|
[_font release];
|
|
[layoutManager release];
|
|
[textContainer release];
|
|
[textStorage release];
|
|
[textAttributes release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
|
HFASSERT([coder allowsKeyedCoding]);
|
|
[super encodeWithCoder:coder];
|
|
[coder encodeObject:_font forKey:@"HFFont"];
|
|
[coder encodeDouble:_lineHeight forKey:@"HFLineHeight"];
|
|
[coder encodeObject:_representer forKey:@"HFRepresenter"];
|
|
[coder encodeInt64:_bytesPerLine forKey:@"HFBytesPerLine"];
|
|
[coder encodeInt64:_lineNumberFormat forKey:@"HFLineNumberFormat"];
|
|
[coder encodeBool:useStringDrawingPath forKey:@"HFUseStringDrawingPath"];
|
|
}
|
|
|
|
- (instancetype)initWithCoder:(NSCoder *)coder {
|
|
HFASSERT([coder allowsKeyedCoding]);
|
|
self = [super initWithCoder:coder];
|
|
[self _sharedInitLineCountingView];
|
|
_font = [[coder decodeObjectForKey:@"HFFont"] retain];
|
|
_lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"];
|
|
_representer = [coder decodeObjectForKey:@"HFRepresenter"];
|
|
_bytesPerLine = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesPerLine"];
|
|
_lineNumberFormat = (NSUInteger)[coder decodeInt64ForKey:@"HFLineNumberFormat"];
|
|
useStringDrawingPath = [coder decodeBoolForKey:@"HFUseStringDrawingPath"];
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isFlipped { return YES; }
|
|
|
|
- (void)getLineNumberFormatString:(char *)outString length:(NSUInteger)length {
|
|
HFLineNumberFormat format = self.lineNumberFormat;
|
|
if (format == HFLineNumberFormatDecimal) {
|
|
strlcpy(outString, "%llu", length);
|
|
}
|
|
else if (format == HFLineNumberFormatHexadecimal) {
|
|
#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX
|
|
// we want a format string like 0x%08llX
|
|
snprintf(outString, length, "0x%%0%lullX", (unsigned long)self.representer.digitCount - 2);
|
|
#else
|
|
// we want a format string like %08llX
|
|
snprintf(outString, length, "%%0%lullX", (unsigned long)self.representer.digitCount);
|
|
#endif
|
|
}
|
|
else {
|
|
strlcpy(outString, "", length);
|
|
}
|
|
}
|
|
|
|
- (void)windowDidChangeKeyStatus:(NSNotification *)note {
|
|
USE(note);
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
- (void)viewDidMoveToWindow {
|
|
HFRegisterViewForWindowAppearanceChanges(self, @selector(windowDidChangeKeyStatus:), !registeredForAppNotifications);
|
|
registeredForAppNotifications = YES;
|
|
[super viewDidMoveToWindow];
|
|
}
|
|
|
|
- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
|
|
HFUnregisterViewForWindowAppearanceChanges(self, NO);
|
|
[super viewWillMoveToWindow:newWindow];
|
|
}
|
|
|
|
- (void)drawGradientWithClip:(NSRect)clip {
|
|
[_representer.backgroundColor set];
|
|
NSRectFill(clip);
|
|
|
|
NSInteger shadowEdge = _representer.interiorShadowEdge;
|
|
|
|
if (shadowEdge >= 0) {
|
|
const CGFloat shadowWidth = 6;
|
|
NSWindow *window = self.window;
|
|
BOOL drawActive = (window == nil || [window isKeyWindow] || [window isMainWindow]);
|
|
HFDrawShadow([[NSGraphicsContext currentContext] graphicsPort], self.bounds, shadowWidth, shadowEdge, drawActive, clip);
|
|
}
|
|
}
|
|
|
|
- (void)drawDividerWithClip:(NSRect)clipRect {
|
|
USE(clipRect);
|
|
|
|
|
|
#if 1
|
|
NSInteger edges = _representer.borderedEdges;
|
|
NSRect bounds = self.bounds;
|
|
|
|
|
|
// -1 means to draw no edges
|
|
if (edges == -1) {
|
|
edges = 0;
|
|
}
|
|
|
|
[_representer.borderColor set];
|
|
|
|
if ((edges & (1 << NSMinXEdge)) > 0) {
|
|
NSRect lineRect = bounds;
|
|
lineRect.size.width = 1;
|
|
lineRect.origin.x = 0;
|
|
if (NSIntersectsRect(lineRect, clipRect)) {
|
|
NSRectFill(lineRect);
|
|
}
|
|
}
|
|
|
|
if ((edges & (1 << NSMaxXEdge)) > 0) {
|
|
NSRect lineRect = bounds;
|
|
lineRect.size.width = 1;
|
|
lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width;
|
|
if (NSIntersectsRect(lineRect, clipRect)) {
|
|
NSRectFill(lineRect);
|
|
}
|
|
}
|
|
|
|
if ((edges & (1 << NSMinYEdge)) > 0) {
|
|
NSRect lineRect = bounds;
|
|
lineRect.size.height = 1;
|
|
lineRect.origin.y = 0;
|
|
if (NSIntersectsRect(lineRect, clipRect)) {
|
|
NSRectFill(lineRect);
|
|
}
|
|
}
|
|
|
|
if ((edges & (1 << NSMaxYEdge)) > 0) {
|
|
NSRect lineRect = bounds;
|
|
lineRect.size.height = 1;
|
|
lineRect.origin.y = NSMaxY(bounds) - lineRect.size.height;
|
|
if (NSIntersectsRect(lineRect, clipRect)) {
|
|
NSRectFill(lineRect);
|
|
}
|
|
}
|
|
|
|
|
|
// Backwards compatibility to always draw a border on the edge with the interior shadow
|
|
|
|
NSRect lineRect = bounds;
|
|
lineRect.size.width = 1;
|
|
NSInteger shadowEdge = _representer.interiorShadowEdge;
|
|
if (shadowEdge == NSMaxXEdge) {
|
|
lineRect.origin.x = NSMaxX(bounds) - lineRect.size.width;
|
|
} else if (shadowEdge == NSMinXEdge) {
|
|
lineRect.origin.x = NSMinX(bounds);
|
|
} else {
|
|
lineRect = NSZeroRect;
|
|
}
|
|
|
|
if (NSIntersectsRect(lineRect, clipRect)) {
|
|
NSRectFill(lineRect);
|
|
}
|
|
|
|
#else
|
|
|
|
|
|
if (NSIntersectsRect(lineRect, clipRect)) {
|
|
// this looks better when we have no shadow
|
|
[[NSColor lightGrayColor] set];
|
|
NSRect bounds = self.bounds;
|
|
NSRect lineRect = bounds;
|
|
lineRect.origin.x += lineRect.size.width - 2;
|
|
lineRect.size.width = 1;
|
|
NSRectFill(NSIntersectionRect(lineRect, clipRect));
|
|
[[NSColor whiteColor] set];
|
|
lineRect.origin.x += 1;
|
|
NSRectFill(NSIntersectionRect(lineRect, clipRect));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static inline int common_prefix_length(const char *a, const char *b) {
|
|
int i;
|
|
for (i=0; ; i++) {
|
|
char ac = a[i];
|
|
char bc = b[i];
|
|
if (ac != bc || ac == 0 || bc == 0) break;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/* 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. */
|
|
- (void)drawLineNumbersWithClipLayoutManagerPerLine:(NSRect)clipRect {
|
|
#if TIME_LINE_NUMBERS
|
|
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
|
#endif
|
|
NSUInteger previousTextStorageCharacterCount = [textStorage length];
|
|
|
|
CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
|
|
NSRect textRect = self.bounds;
|
|
textRect.size.height = _lineHeight;
|
|
textRect.origin.y -= verticalOffset * _lineHeight;
|
|
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
|
|
unsigned long long lineValue = lineIndex * _bytesPerLine;
|
|
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
|
|
char previousBuff[256];
|
|
int previousStringLength = (int)previousTextStorageCharacterCount;
|
|
BOOL conversionResult = [[textStorage string] getCString:previousBuff maxLength:sizeof previousBuff encoding:NSASCIIStringEncoding];
|
|
HFASSERT(conversionResult);
|
|
while (linesRemaining--) {
|
|
char formatString[64];
|
|
[self getLineNumberFormatString:formatString length:sizeof formatString];
|
|
|
|
if (NSIntersectsRect(textRect, clipRect)) {
|
|
NSString *replacementCharacters = nil;
|
|
NSRange replacementRange;
|
|
char buff[256];
|
|
int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue);
|
|
HFASSERT(newStringLength > 0);
|
|
int prefixLength = common_prefix_length(previousBuff, buff);
|
|
HFASSERT(prefixLength <= newStringLength);
|
|
HFASSERT(prefixLength <= previousStringLength);
|
|
replacementRange = NSMakeRange(prefixLength, previousStringLength - prefixLength);
|
|
replacementCharacters = [[NSString alloc] initWithBytesNoCopy:buff + prefixLength length:newStringLength - prefixLength encoding:NSASCIIStringEncoding freeWhenDone:NO];
|
|
NSUInteger glyphCount;
|
|
[textStorage replaceCharactersInRange:replacementRange withString:replacementCharacters];
|
|
if (previousTextStorageCharacterCount == 0) {
|
|
NSDictionary *atts = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, nil];
|
|
[textStorage setAttributes:atts range:NSMakeRange(0, newStringLength)];
|
|
[atts release];
|
|
}
|
|
glyphCount = [layoutManager numberOfGlyphs];
|
|
if (glyphCount > 0) {
|
|
CGFloat maxX = NSMaxX([layoutManager lineFragmentUsedRectForGlyphAtIndex:glyphCount - 1 effectiveRange:NULL]);
|
|
[layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, glyphCount) atPoint:NSMakePoint(textRect.origin.x + textRect.size.width - maxX, textRect.origin.y)];
|
|
}
|
|
previousTextStorageCharacterCount = newStringLength;
|
|
[replacementCharacters release];
|
|
memcpy(previousBuff, buff, newStringLength + 1);
|
|
previousStringLength = newStringLength;
|
|
}
|
|
textRect.origin.y += _lineHeight;
|
|
lineIndex++;
|
|
lineValue = HFSum(lineValue, _bytesPerLine);
|
|
}
|
|
#if TIME_LINE_NUMBERS
|
|
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
|
|
NSLog(@"Line number time: %f", endTime - startTime);
|
|
#endif
|
|
}
|
|
|
|
- (void)drawLineNumbersWithClipStringDrawing:(NSRect)clipRect {
|
|
CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
|
|
NSRect textRect = self.bounds;
|
|
textRect.size.height = _lineHeight;
|
|
textRect.size.width -= 5;
|
|
textRect.origin.y -= verticalOffset * _lineHeight + 1;
|
|
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
|
|
unsigned long long lineValue = lineIndex * _bytesPerLine;
|
|
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
|
|
if (! textAttributes) {
|
|
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
|
[mutableStyle setAlignment:NSRightTextAlignment];
|
|
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
|
|
[mutableStyle release];
|
|
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
|
[paragraphStyle release];
|
|
}
|
|
|
|
char formatString[64];
|
|
[self getLineNumberFormatString:formatString length:sizeof formatString];
|
|
|
|
while (linesRemaining--) {
|
|
if (NSIntersectsRect(textRect, clipRect)) {
|
|
char buff[256];
|
|
int newStringLength = snprintf(buff, sizeof buff, formatString, lineValue);
|
|
HFASSERT(newStringLength > 0);
|
|
NSString *string = [[NSString alloc] initWithBytesNoCopy:buff length:newStringLength encoding:NSASCIIStringEncoding freeWhenDone:NO];
|
|
[string drawInRect:textRect withAttributes:textAttributes];
|
|
[string release];
|
|
}
|
|
textRect.origin.y += _lineHeight;
|
|
lineIndex++;
|
|
if (linesRemaining > 0) lineValue = HFSum(lineValue, _bytesPerLine); //we could do this unconditionally, but then we risk overflow
|
|
}
|
|
}
|
|
|
|
- (NSUInteger)characterCountForLineRange:(HFRange)range {
|
|
HFASSERT(range.length <= NSUIntegerMax);
|
|
NSUInteger characterCount;
|
|
|
|
NSUInteger lineCount = ll2l(range.length);
|
|
const NSUInteger stride = _bytesPerLine;
|
|
HFLineCountingRepresenter *rep = self.representer;
|
|
HFLineNumberFormat format = self.lineNumberFormat;
|
|
if (format == HFLineNumberFormatDecimal) {
|
|
unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine);
|
|
characterCount = lineCount /* newlines */;
|
|
while (lineCount--) {
|
|
characterCount += HFCountDigitsBase10(lineValue);
|
|
lineValue += stride;
|
|
}
|
|
}
|
|
else if (format == HFLineNumberFormatHexadecimal) {
|
|
characterCount = ([rep digitCount] + 1) * lineCount; // +1 for newlines
|
|
}
|
|
else {
|
|
characterCount = -1;
|
|
}
|
|
return characterCount;
|
|
}
|
|
|
|
- (NSString *)newLineStringForRange:(HFRange)range {
|
|
HFASSERT(range.length <= NSUIntegerMax);
|
|
if(range.length == 0)
|
|
return [[NSString alloc] init]; // Placate the analyzer.
|
|
|
|
NSUInteger lineCount = ll2l(range.length);
|
|
const NSUInteger stride = _bytesPerLine;
|
|
unsigned long long lineValue = HFProductULL(range.location, _bytesPerLine);
|
|
NSUInteger characterCount = [self characterCountForLineRange:range];
|
|
char *buffer = check_malloc(characterCount);
|
|
NSUInteger bufferIndex = 0;
|
|
|
|
char formatString[64];
|
|
[self getLineNumberFormatString:formatString length:sizeof formatString];
|
|
|
|
while (lineCount--) {
|
|
int charCount = sprintf(buffer + bufferIndex, formatString, lineValue + self.representer.valueOffset);
|
|
HFASSERT(charCount > 0);
|
|
bufferIndex += charCount;
|
|
buffer[bufferIndex++] = '\n';
|
|
lineValue += stride;
|
|
}
|
|
HFASSERT(bufferIndex == characterCount);
|
|
|
|
NSString *string = [[NSString alloc] initWithBytesNoCopy:(void *)buffer length:bufferIndex encoding:NSASCIIStringEncoding freeWhenDone:YES];
|
|
return string;
|
|
}
|
|
|
|
- (void)updateLayoutManagerWithLineIndex:(unsigned long long)startingLineIndex lineCount:(NSUInteger)linesRemaining {
|
|
const BOOL debug = NO;
|
|
[textStorage beginEditing];
|
|
|
|
if (storedLineCount == INVALID_LINE_COUNT) {
|
|
/* This usually indicates that our bytes per line or line number format changed, and we need to just recalculate everything */
|
|
NSString *string = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)];
|
|
[textStorage replaceCharactersInRange:NSMakeRange(0, [textStorage length]) withString:string];
|
|
[string release];
|
|
|
|
}
|
|
else {
|
|
HFRange leftRangeToReplace, rightRangeToReplace;
|
|
HFRange leftRangeToStore, rightRangeToStore;
|
|
|
|
HFRange oldRange = HFRangeMake(storedLineIndex, storedLineCount);
|
|
HFRange newRange = HFRangeMake(startingLineIndex, linesRemaining);
|
|
HFRange rangeToPreserve = HFIntersectionRange(oldRange, newRange);
|
|
|
|
if (rangeToPreserve.length == 0) {
|
|
leftRangeToReplace = oldRange;
|
|
leftRangeToStore = newRange;
|
|
rightRangeToReplace = HFZeroRange;
|
|
rightRangeToStore = HFZeroRange;
|
|
}
|
|
else {
|
|
if (debug) NSLog(@"Preserving %llu", rangeToPreserve.length);
|
|
HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, newRange));
|
|
HFASSERT(HFRangeIsSubrangeOfRange(rangeToPreserve, oldRange));
|
|
const unsigned long long maxPreserve = HFMaxRange(rangeToPreserve);
|
|
leftRangeToReplace = HFRangeMake(oldRange.location, rangeToPreserve.location - oldRange.location);
|
|
leftRangeToStore = HFRangeMake(newRange.location, rangeToPreserve.location - newRange.location);
|
|
rightRangeToReplace = HFRangeMake(maxPreserve, HFMaxRange(oldRange) - maxPreserve);
|
|
rightRangeToStore = HFRangeMake(maxPreserve, HFMaxRange(newRange) - maxPreserve);
|
|
}
|
|
|
|
if (debug) NSLog(@"Changing %@ -> %@", HFRangeToString(oldRange), HFRangeToString(newRange));
|
|
if (debug) NSLog(@"LEFT: %@ -> %@", HFRangeToString(leftRangeToReplace), HFRangeToString(leftRangeToStore));
|
|
if (debug) NSLog(@"RIGHT: %@ -> %@", HFRangeToString(rightRangeToReplace), HFRangeToString(rightRangeToStore));
|
|
|
|
HFASSERT(leftRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(leftRangeToReplace, oldRange));
|
|
HFASSERT(rightRangeToReplace.length == 0 || HFRangeIsSubrangeOfRange(rightRangeToReplace, oldRange));
|
|
|
|
if (leftRangeToReplace.length > 0 || leftRangeToStore.length > 0) {
|
|
NSUInteger charactersToDelete = [self characterCountForLineRange:leftRangeToReplace];
|
|
NSRange rangeToDelete = NSMakeRange(0, charactersToDelete);
|
|
if (leftRangeToStore.length == 0) {
|
|
[textStorage deleteCharactersInRange:rangeToDelete];
|
|
if (debug) NSLog(@"Left deleting text range %@", NSStringFromRange(rangeToDelete));
|
|
}
|
|
else {
|
|
NSString *leftRangeString = [self newLineStringForRange:leftRangeToStore];
|
|
[textStorage replaceCharactersInRange:rangeToDelete withString:leftRangeString];
|
|
if (debug) NSLog(@"Replacing text range %@ with %@", NSStringFromRange(rangeToDelete), leftRangeString);
|
|
[leftRangeString release];
|
|
}
|
|
}
|
|
|
|
if (rightRangeToReplace.length > 0 || rightRangeToStore.length > 0) {
|
|
NSUInteger charactersToDelete = [self characterCountForLineRange:rightRangeToReplace];
|
|
NSUInteger stringLength = [textStorage length];
|
|
HFASSERT(charactersToDelete <= stringLength);
|
|
NSRange rangeToDelete = NSMakeRange(stringLength - charactersToDelete, charactersToDelete);
|
|
if (rightRangeToStore.length == 0) {
|
|
[textStorage deleteCharactersInRange:rangeToDelete];
|
|
if (debug) NSLog(@"Right deleting text range %@", NSStringFromRange(rangeToDelete));
|
|
}
|
|
else {
|
|
NSString *rightRangeString = [self newLineStringForRange:rightRangeToStore];
|
|
[textStorage replaceCharactersInRange:rangeToDelete withString:rightRangeString];
|
|
if (debug) NSLog(@"Replacing text range %@ with %@ (for range %@)", NSStringFromRange(rangeToDelete), rightRangeString, HFRangeToString(rightRangeToStore));
|
|
[rightRangeString release];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! textAttributes) {
|
|
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
|
[mutableStyle setAlignment:NSRightTextAlignment];
|
|
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
|
|
[mutableStyle release];
|
|
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
|
[paragraphStyle release];
|
|
[textStorage setAttributes:textAttributes range:NSMakeRange(0, [textStorage length])];
|
|
}
|
|
|
|
[textStorage endEditing];
|
|
|
|
#if ! NDEBUG
|
|
NSString *comparisonString = [self newLineStringForRange:HFRangeMake(startingLineIndex, linesRemaining)];
|
|
if (! [comparisonString isEqualToString:[textStorage string]]) {
|
|
NSLog(@"Not equal!");
|
|
NSLog(@"Expected:\n%@", comparisonString);
|
|
NSLog(@"Actual:\n%@", [textStorage string]);
|
|
}
|
|
HFASSERT([comparisonString isEqualToString:[textStorage string]]);
|
|
[comparisonString release];
|
|
#endif
|
|
|
|
storedLineIndex = startingLineIndex;
|
|
storedLineCount = linesRemaining;
|
|
}
|
|
|
|
- (void)drawLineNumbersWithClipSingleStringDrawing:(NSRect)clipRect {
|
|
USE(clipRect);
|
|
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
|
|
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
|
|
|
|
CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
|
|
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
|
|
NSRect textRect = self.bounds;
|
|
textRect.size.width -= 5;
|
|
textRect.origin.y -= verticalOffset;
|
|
textRect.size.height += verticalOffset;
|
|
|
|
if (! textAttributes) {
|
|
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
|
[mutableStyle setAlignment:NSRightTextAlignment];
|
|
[mutableStyle setMinimumLineHeight:_lineHeight];
|
|
[mutableStyle setMaximumLineHeight:_lineHeight];
|
|
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
|
|
[mutableStyle release];
|
|
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
|
[paragraphStyle release];
|
|
}
|
|
|
|
|
|
NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
|
|
[string drawInRect:textRect withAttributes:textAttributes];
|
|
[string release];
|
|
}
|
|
|
|
- (void)drawLineNumbersWithClipSingleStringCellDrawing:(NSRect)clipRect {
|
|
USE(clipRect);
|
|
const CGFloat cellTextContainerPadding = 2.f;
|
|
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
|
|
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
|
|
|
|
CGFloat linesToVerticallyOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
|
|
CGFloat verticalOffset = linesToVerticallyOffset * _lineHeight + 1;
|
|
NSRect textRect = self.bounds;
|
|
textRect.size.width -= 5;
|
|
textRect.origin.y -= verticalOffset;
|
|
textRect.origin.x += cellTextContainerPadding;
|
|
textRect.size.height += verticalOffset;
|
|
|
|
if (! textAttributes) {
|
|
NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
|
[mutableStyle setAlignment:NSRightTextAlignment];
|
|
[mutableStyle setMinimumLineHeight:_lineHeight];
|
|
[mutableStyle setMaximumLineHeight:_lineHeight];
|
|
NSParagraphStyle *paragraphStyle = [mutableStyle copy];
|
|
[mutableStyle release];
|
|
textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:_font, NSFontAttributeName, [NSColor colorWithCalibratedWhite:(CGFloat).1 alpha:(CGFloat).8], NSForegroundColorAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
|
|
[paragraphStyle release];
|
|
}
|
|
|
|
NSString *string = [self newLineStringForRange:HFRangeMake(lineIndex, linesRemaining)];
|
|
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:textAttributes];
|
|
[string release];
|
|
NSCell *cell = [[NSCell alloc] initTextCell:@""];
|
|
[cell setAttributedStringValue:attributedString];
|
|
[cell drawWithFrame:textRect inView:self];
|
|
[[NSColor purpleColor] set];
|
|
NSFrameRect(textRect);
|
|
[cell release];
|
|
[attributedString release];
|
|
}
|
|
|
|
- (void)drawLineNumbersWithClipFullLayoutManager:(NSRect)clipRect {
|
|
USE(clipRect);
|
|
unsigned long long lineIndex = HFFPToUL(floorl(_lineRangeToDraw.location));
|
|
NSUInteger linesRemaining = ll2l(HFFPToUL(ceill(_lineRangeToDraw.length + _lineRangeToDraw.location) - floorl(_lineRangeToDraw.location)));
|
|
if (lineIndex != storedLineIndex || linesRemaining != storedLineCount) {
|
|
[self updateLayoutManagerWithLineIndex:lineIndex lineCount:linesRemaining];
|
|
}
|
|
|
|
CGFloat verticalOffset = ld2f(_lineRangeToDraw.location - floorl(_lineRangeToDraw.location));
|
|
|
|
NSPoint textPoint = self.bounds.origin;
|
|
textPoint.y -= verticalOffset * _lineHeight;
|
|
[layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) atPoint:textPoint];
|
|
}
|
|
|
|
- (void)drawLineNumbersWithClip:(NSRect)clipRect {
|
|
#if TIME_LINE_NUMBERS
|
|
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
|
#endif
|
|
NSInteger drawingMode = (useStringDrawingPath ? 1 : 3);
|
|
switch (drawingMode) {
|
|
// Drawing can't be done right if fonts are wider than expected, but all
|
|
// of these have rather nasty behavior in that case. I've commented what
|
|
// that behavior is; the comment is hypothetical 'could' if it shouldn't
|
|
// actually be a problem in practice.
|
|
// TODO: Make a drawing mode that is "Fonts could get clipped if too wide"
|
|
// because that seems like better behavior than any of these.
|
|
case 0:
|
|
// Most fonts are too wide and every character gets piled on right (unreadable).
|
|
[self drawLineNumbersWithClipLayoutManagerPerLine:clipRect];
|
|
break;
|
|
case 1:
|
|
// Last characters could get omitted (*not* clipped) if too wide.
|
|
// Also, most fonts have bottoms clipped (very unsigntly).
|
|
[self drawLineNumbersWithClipStringDrawing:clipRect];
|
|
break;
|
|
case 2:
|
|
// Most fonts are too wide and wrap (breaks numbering).
|
|
[self drawLineNumbersWithClipFullLayoutManager:clipRect];
|
|
break;
|
|
case 3:
|
|
// Fonts could wrap if too wide (breaks numbering).
|
|
// *Note that that this is the only mode that generally works.*
|
|
[self drawLineNumbersWithClipSingleStringDrawing:clipRect];
|
|
break;
|
|
case 4:
|
|
// Most fonts are too wide and wrap (breaks numbering).
|
|
[self drawLineNumbersWithClipSingleStringCellDrawing:clipRect];
|
|
break;
|
|
}
|
|
#if TIME_LINE_NUMBERS
|
|
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
|
|
NSLog(@"Line number time: %f", endTime - startTime);
|
|
#endif
|
|
}
|
|
|
|
- (void)drawRect:(NSRect)clipRect {
|
|
[self drawGradientWithClip:clipRect];
|
|
[self drawDividerWithClip:clipRect];
|
|
[self drawLineNumbersWithClip:clipRect];
|
|
}
|
|
|
|
- (void)setLineRangeToDraw:(HFFPRange)range {
|
|
if (! HFFPRangeEqualsRange(range, _lineRangeToDraw)) {
|
|
_lineRangeToDraw = range;
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
}
|
|
|
|
- (void)setBytesPerLine:(NSUInteger)val {
|
|
if (_bytesPerLine != val) {
|
|
_bytesPerLine = val;
|
|
storedLineCount = INVALID_LINE_COUNT;
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
}
|
|
|
|
- (void)setLineNumberFormat:(HFLineNumberFormat)format {
|
|
if (format != _lineNumberFormat) {
|
|
_lineNumberFormat = format;
|
|
storedLineCount = INVALID_LINE_COUNT;
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
}
|
|
|
|
- (BOOL)canUseStringDrawingPathForFont:(NSFont *)testFont {
|
|
NSString *name = [testFont fontName];
|
|
// No, Menlo does not work here.
|
|
return [name isEqualToString:@"Monaco"] || [name isEqualToString:@"Courier"] || [name isEqualToString:@"Consolas"];
|
|
}
|
|
|
|
- (void)setFont:(NSFont *)val {
|
|
if (val != _font) {
|
|
[_font release];
|
|
_font = [val copy];
|
|
[textStorage deleteCharactersInRange:NSMakeRange(0, [textStorage length])]; //delete the characters so we know to set the font next time we render
|
|
[textAttributes release];
|
|
textAttributes = nil;
|
|
storedLineCount = INVALID_LINE_COUNT;
|
|
useStringDrawingPath = [self canUseStringDrawingPathForFont:_font];
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
}
|
|
|
|
- (void)setLineHeight:(CGFloat)height {
|
|
if (_lineHeight != height) {
|
|
_lineHeight = height;
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
}
|
|
|
|
- (void)setFrameSize:(NSSize)size {
|
|
[super setFrameSize:size];
|
|
[textContainer setContainerSize:NSMakeSize(self.bounds.size.width, [textContainer containerSize].height)];
|
|
}
|
|
|
|
- (void)mouseDown:(NSEvent *)event {
|
|
USE(event);
|
|
// [_representer cycleLineNumberFormat];
|
|
}
|
|
|
|
- (void)scrollWheel:(NSEvent *)scrollEvent {
|
|
[_representer.controller scrollWithScrollEvent:scrollEvent];
|
|
}
|
|
|
|
+ (NSUInteger)digitsRequiredToDisplayLineNumber:(unsigned long long)lineNumber inFormat:(HFLineNumberFormat)format {
|
|
switch (format) {
|
|
case HFLineNumberFormatDecimal: return HFCountDigitsBase10(lineNumber);
|
|
#if HEX_LINE_NUMBERS_HAVE_0X_PREFIX
|
|
case HFLineNumberFormatHexadecimal: return 2 + HFCountDigitsBase16(lineNumber);
|
|
#else
|
|
case HFLineNumberFormatHexadecimal: return HFCountDigitsBase16(lineNumber);
|
|
#endif
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
@end
|