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