SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
HexFiend/HFRepresenterTextView.m
1 //
2 // HFRepresenterTextView.m
3 // HexFiend_2
4 //
5 // Copyright 2007 ridiculous_fish. All rights reserved.
6 //
7
8 #import <HexFiend/HFRepresenterTextView_Internal.h>
9 #import <HexFiend/HFTextRepresenter_Internal.h>
10 #import <HexFiend/HFTextVisualStyleRun.h>
11 #import <HexFiend/HFFunctions.h>
12 #import <HexFiend/HFRepresenterTextViewCallout.h>
13 #import <objc/message.h>
14
15 static const NSTimeInterval HFCaretBlinkFrequency = 0.56;
16
17 @implementation HFRepresenterTextView
18
19 - (NSUInteger)_getGlyphs:(CGGlyph *)glyphs forString:(NSString *)string font:(NSFont *)inputFont {
20 NSUInteger length = [string length];
21 UniChar chars[256];
22 HFASSERT(length <= sizeof chars / sizeof *chars);
23 HFASSERT(inputFont != nil);
24 [string getCharacters:chars range:NSMakeRange(0, length)];
25 if (! CTFontGetGlyphsForCharacters((CTFontRef)inputFont, chars, glyphs, length)) {
26 /* Some or all characters were not mapped. This is OK. We'll use the replacement glyph. */
27 }
28 return length;
29 }
30
31 - (NSUInteger)_glyphsForString:(NSString *)string withGeneratingLayoutManager:(NSLayoutManager *)layoutManager glyphs:(CGGlyph *)glyphs {
32 HFASSERT(layoutManager != NULL);
33 HFASSERT(string != NULL);
34 NSGlyph nsglyphs[GLYPH_BUFFER_SIZE];
35 [[[layoutManager textStorage] mutableString] setString:string];
36 NSUInteger glyphIndex, glyphCount = [layoutManager getGlyphs:nsglyphs range:NSMakeRange(0, MIN(GLYPH_BUFFER_SIZE, [layoutManager numberOfGlyphs]))];
37 if (glyphs != NULL) {
38 /* Convert from unsigned int NSGlyphs to unsigned short CGGlyphs */
39 for (glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) {
40 /* Get rid of NSControlGlyph */
41 NSGlyph modifiedGlyph = nsglyphs[glyphIndex] == NSControlGlyph ? NSNullGlyph : nsglyphs[glyphIndex];
42 HFASSERT(modifiedGlyph <= USHRT_MAX);
43 glyphs[glyphIndex] = (CGGlyph)modifiedGlyph;
44 }
45 }
46 return glyphCount;
47 }
48
49 /* Returns the number of glyphs for the given string, using the given text view, and generating the glyphs if the glyphs parameter is not NULL */
50 - (NSUInteger)_glyphsForString:(NSString *)string withGeneratingTextView:(NSTextView *)textView glyphs:(CGGlyph *)glyphs {
51 HFASSERT(string != NULL);
52 HFASSERT(textView != NULL);
53 [textView setString:string];
54 [textView setNeedsDisplay:YES]; //ligature generation doesn't seem to happen without this, for some reason. This seems very fragile! We should find a better way to get this ligature information!!
55 return [self _glyphsForString:string withGeneratingLayoutManager:[textView layoutManager] glyphs:glyphs];
56 }
57
58 - (NSArray *)displayedSelectedContentsRanges {
59 if (! cachedSelectedRanges) {
60 cachedSelectedRanges = [[[self representer] displayedSelectedContentsRanges] copy];
61 }
62 return cachedSelectedRanges;
63 }
64
65 - (BOOL)_shouldHaveCaretTimer {
66 NSWindow *window = [self window];
67 if (window == NULL) return NO;
68 if (! [window isKeyWindow]) return NO;
69 if (self != [window firstResponder]) return NO;
70 if (! _hftvflags.editable) return NO;
71 NSArray *ranges = [self displayedSelectedContentsRanges];
72 if ([ranges count] != 1) return NO;
73 NSRange range = [ranges[0] rangeValue];
74 if (range.length != 0) return NO;
75 return YES;
76 }
77
78 - (NSUInteger)_effectiveBytesPerColumn {
79 /* returns the bytesPerColumn, unless it's larger than the bytes per character, in which case it returns 0 */
80 NSUInteger bytesPerColumn = [self bytesPerColumn], bytesPerCharacter = [self bytesPerCharacter];
81 return bytesPerColumn >= bytesPerCharacter ? bytesPerColumn : 0;
82 }
83
84 // note: index may be negative
85 - (NSPoint)originForCharacterAtByteIndex:(NSInteger)index {
86 NSPoint result;
87 NSInteger bytesPerLine = (NSInteger)[self bytesPerLine];
88
89 // We want a nonnegative remainder
90 NSInteger lineIndex = index / bytesPerLine;
91 NSInteger byteIndexIntoLine = index % bytesPerLine;
92 while (byteIndexIntoLine < 0) {
93 byteIndexIntoLine += bytesPerLine;
94 lineIndex--;
95 }
96
97 NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
98 NSUInteger numConsumedColumns = (bytesPerColumn ? byteIndexIntoLine / bytesPerColumn : 0);
99 NSUInteger characterIndexIntoLine = byteIndexIntoLine / [self bytesPerCharacter];
100
101 result.x = [self horizontalContainerInset] + characterIndexIntoLine * [self advancePerCharacter] + numConsumedColumns * [self advanceBetweenColumns];
102 result.y = (lineIndex - [self verticalOffset]) * [self lineHeight];
103
104 return result;
105 }
106
107 - (NSUInteger)indexOfCharacterAtPoint:(NSPoint)point {
108 NSUInteger bytesPerLine = [self bytesPerLine];
109 NSUInteger bytesPerCharacter = [self bytesPerCharacter];
110 HFASSERT(bytesPerLine % bytesPerCharacter == 0);
111 CGFloat advancePerCharacter = [self advancePerCharacter];
112 NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
113 CGFloat floatRow = (CGFloat)floor([self verticalOffset] + point.y / [self lineHeight]);
114 NSUInteger byteIndexWithinRow;
115
116 // to compute the column, we need to solve for byteIndexIntoLine in something like this: point.x = [self advancePerCharacter] * charIndexIntoLine + [self spaceBetweenColumns] * floor(byteIndexIntoLine / [self bytesPerColumn]). Start by computing the column (or if bytesPerColumn is 0, we don't have columns)
117 CGFloat insetX = point.x - [self horizontalContainerInset];
118 if (insetX < 0) {
119 //handle the case of dragging within the container inset
120 byteIndexWithinRow = 0;
121 }
122 else if (bytesPerColumn == 0) {
123 /* We don't have columns */
124 byteIndexWithinRow = bytesPerCharacter * (NSUInteger)(insetX / advancePerCharacter);
125 }
126 else {
127 CGFloat advancePerColumn = [self advancePerColumn];
128 HFASSERT(advancePerColumn > 0);
129 CGFloat floatColumn = insetX / advancePerColumn;
130 HFASSERT(floatColumn >= 0 && floatColumn <= NSUIntegerMax);
131 CGFloat startOfColumn = advancePerColumn * HFFloor(floatColumn);
132 HFASSERT(startOfColumn <= insetX);
133 CGFloat xOffsetWithinColumn = insetX - startOfColumn;
134 CGFloat charIndexWithinColumn = xOffsetWithinColumn / advancePerCharacter; //charIndexWithinColumn may be larger than bytesPerColumn if the user clicked on the space between columns
135 HFASSERT(charIndexWithinColumn >= 0 && charIndexWithinColumn <= NSUIntegerMax / bytesPerCharacter);
136 NSUInteger byteIndexWithinColumn = bytesPerCharacter * (NSUInteger)charIndexWithinColumn;
137 byteIndexWithinRow = bytesPerColumn * (NSUInteger)floatColumn + byteIndexWithinColumn; //this may trigger overflow to the next column, but that's OK
138 byteIndexWithinRow = MIN(byteIndexWithinRow, bytesPerLine); //don't let clicking to the right of the line overflow to the next line
139 }
140 HFASSERT(floatRow >= 0 && floatRow <= NSUIntegerMax);
141 NSUInteger row = (NSUInteger)floatRow;
142 return (row * bytesPerLine + byteIndexWithinRow) / bytesPerCharacter;
143 }
144
145 - (NSRect)caretRect {
146 NSArray *ranges = [self displayedSelectedContentsRanges];
147 HFASSERT([ranges count] == 1);
148 NSRange range = [ranges[0] rangeValue];
149 HFASSERT(range.length == 0);
150
151 NSPoint caretBaseline = [self originForCharacterAtByteIndex:range.location];
152 return NSMakeRect(caretBaseline.x - 1, caretBaseline.y, 1, [self lineHeight]);
153 }
154
155 - (void)_blinkCaret:(NSTimer *)timer {
156 HFASSERT(timer == caretTimer);
157 if (_hftvflags.caretVisible) {
158 _hftvflags.caretVisible = NO;
159 [self setNeedsDisplayInRect:lastDrawnCaretRect];
160 caretRectToDraw = NSZeroRect;
161 }
162 else {
163 _hftvflags.caretVisible = YES;
164 caretRectToDraw = [self caretRect];
165 [self setNeedsDisplayInRect:caretRectToDraw];
166 }
167 }
168
169 - (void)_updateCaretTimerWithFirstResponderStatus:(BOOL)treatAsHavingFirstResponder {
170 BOOL hasCaretTimer = !! caretTimer;
171 BOOL shouldHaveCaretTimer = treatAsHavingFirstResponder && [self _shouldHaveCaretTimer];
172 if (shouldHaveCaretTimer == YES && hasCaretTimer == NO) {
173 caretTimer = [[NSTimer timerWithTimeInterval:HFCaretBlinkFrequency target:self selector:@selector(_blinkCaret:) userInfo:nil repeats:YES] retain];
174 NSRunLoop *loop = [NSRunLoop currentRunLoop];
175 [loop addTimer:caretTimer forMode:NSDefaultRunLoopMode];
176 [loop addTimer:caretTimer forMode:NSModalPanelRunLoopMode];
177 if ([self enclosingMenuItem] != NULL) {
178 [loop addTimer:caretTimer forMode:NSEventTrackingRunLoopMode];
179 }
180 }
181 else if (shouldHaveCaretTimer == NO && hasCaretTimer == YES) {
182 [caretTimer invalidate];
183 [caretTimer release];
184 caretTimer = nil;
185 caretRectToDraw = NSZeroRect;
186 if (! NSIsEmptyRect(lastDrawnCaretRect)) {
187 [self setNeedsDisplayInRect:lastDrawnCaretRect];
188 }
189 }
190 HFASSERT(shouldHaveCaretTimer == !! caretTimer);
191 }
192
193 - (void)_updateCaretTimer {
194 [self _updateCaretTimerWithFirstResponderStatus: self == [[self window] firstResponder]];
195 }
196
197 /* When you click or type, the caret appears immediately - do that here */
198 - (void)_forceCaretOnIfHasCaretTimer {
199 if (caretTimer) {
200 [caretTimer invalidate];
201 [caretTimer release];
202 caretTimer = nil;
203 [self _updateCaretTimer];
204
205 _hftvflags.caretVisible = YES;
206 caretRectToDraw = [self caretRect];
207 [self setNeedsDisplayInRect:caretRectToDraw];
208 }
209 }
210
211 /* Returns the range of lines containing the selected contents ranges (as NSValues containing NSRanges), or {NSNotFound, 0} if ranges is nil or empty */
212 - (NSRange)_lineRangeForContentsRanges:(NSArray *)ranges {
213 NSUInteger minLine = NSUIntegerMax;
214 NSUInteger maxLine = 0;
215 NSUInteger bytesPerLine = [self bytesPerLine];
216 FOREACH(NSValue *, rangeValue, ranges) {
217 NSRange range = [rangeValue rangeValue];
218 if (range.length > 0) {
219 NSUInteger lineForRangeStart = range.location / bytesPerLine;
220 NSUInteger lineForRangeEnd = NSMaxRange(range) / bytesPerLine;
221 HFASSERT(lineForRangeStart <= lineForRangeEnd);
222 minLine = MIN(minLine, lineForRangeStart);
223 maxLine = MAX(maxLine, lineForRangeEnd);
224 }
225 }
226 if (minLine > maxLine) return NSMakeRange(NSNotFound, 0);
227 else return NSMakeRange(minLine, maxLine - minLine + 1);
228 }
229
230 - (NSRect)_rectForLineRange:(NSRange)lineRange {
231 HFASSERT(lineRange.location != NSNotFound);
232 NSUInteger bytesPerLine = [self bytesPerLine];
233 NSRect bounds = [self bounds];
234 NSRect result;
235 result.origin.x = NSMinX(bounds);
236 result.size.width = NSWidth(bounds);
237 result.origin.y = [self originForCharacterAtByteIndex:lineRange.location * bytesPerLine].y;
238 result.size.height = [self lineHeight] * lineRange.length;
239 return result;
240 }
241
242 static int range_compare(const void *ap, const void *bp) {
243 const NSRange *a = ap;
244 const NSRange *b = bp;
245 if (a->location < b->location) return -1;
246 if (a->location > b->location) return 1;
247 if (a->length < b->length) return -1;
248 if (a->length > b->length) return 1;
249 return 0;
250 }
251
252 enum LineCoverage_t {
253 eCoverageNone,
254 eCoveragePartial,
255 eCoverageFull
256 };
257
258 - (void)_linesWithParityChangesFromRanges:(const NSRange *)oldRanges count:(NSUInteger)oldRangeCount toRanges:(const NSRange *)newRanges count:(NSUInteger)newRangeCount intoIndexSet:(NSMutableIndexSet *)result {
259 NSUInteger bytesPerLine = [self bytesPerLine];
260 NSUInteger oldParity=0, newParity=0;
261 NSUInteger oldRangeIndex = 0, newRangeIndex = 0;
262 NSUInteger currentCharacterIndex = MIN(oldRanges[oldRangeIndex].location, newRanges[newRangeIndex].location);
263 oldParity = (currentCharacterIndex >= oldRanges[oldRangeIndex].location);
264 newParity = (currentCharacterIndex >= newRanges[newRangeIndex].location);
265 // NSLog(@"Old %s, new %s at %u (%u, %u)", oldParity ? "on" : "off", newParity ? "on" : "off", currentCharacterIndex, oldRanges[oldRangeIndex].location, newRanges[newRangeIndex].location);
266 for (;;) {
267 NSUInteger oldDivision = NSUIntegerMax, newDivision = NSUIntegerMax;
268 /* Move up to the next parity change */
269 if (oldRangeIndex < oldRangeCount) {
270 const NSRange oldRange = oldRanges[oldRangeIndex];
271 oldDivision = oldRange.location + (oldParity ? oldRange.length : 0);
272 }
273 if (newRangeIndex < newRangeCount) {
274 const NSRange newRange = newRanges[newRangeIndex];
275 newDivision = newRange.location + (newParity ? newRange.length : 0);
276 }
277
278 NSUInteger division = MIN(oldDivision, newDivision);
279 HFASSERT(division > currentCharacterIndex);
280
281 // NSLog(@"Division %u", division);
282
283 if (division == NSUIntegerMax) break;
284
285 if (oldParity != newParity) {
286 /* The parities did not match through this entire range, so add all intersected lines to the result index set */
287 NSUInteger startLine = currentCharacterIndex / bytesPerLine;
288 NSUInteger endLine = HFDivideULRoundingUp(division, bytesPerLine);
289 HFASSERT(endLine >= startLine);
290 // NSLog(@"Adding lines %u -> %u", startLine, endLine);
291 [result addIndexesInRange:NSMakeRange(startLine, endLine - startLine)];
292 }
293 if (division == oldDivision) {
294 oldRangeIndex += oldParity;
295 oldParity = ! oldParity;
296 // NSLog(@"Old range switching %s at %u", oldParity ? "on" : "off", division);
297 }
298 if (division == newDivision) {
299 newRangeIndex += newParity;
300 newParity = ! newParity;
301 // NSLog(@"New range switching %s at %u", newParity ? "on" : "off", division);
302 }
303 currentCharacterIndex = division;
304 }
305 }
306
307 - (void)_addLinesFromRanges:(const NSRange *)ranges count:(NSUInteger)count toIndexSet:(NSMutableIndexSet *)set {
308 NSUInteger bytesPerLine = [self bytesPerLine];
309 NSUInteger i;
310 for (i=0; i < count; i++) {
311 NSUInteger firstLine = ranges[i].location / bytesPerLine;
312 NSUInteger lastLine = HFDivideULRoundingUp(NSMaxRange(ranges[i]), bytesPerLine);
313 [set addIndexesInRange:NSMakeRange(firstLine, lastLine - firstLine)];
314 }
315 }
316
317 - (NSIndexSet *)_indexSetOfLinesNeedingRedrawWhenChangingSelectionFromRanges:(NSArray *)oldSelectedRangeArray toRanges:(NSArray *)newSelectedRangeArray {
318 NSUInteger oldRangeCount = 0, newRangeCount = 0;
319
320 NEW_ARRAY(NSRange, oldRanges, [oldSelectedRangeArray count]);
321 NEW_ARRAY(NSRange, newRanges, [newSelectedRangeArray count]);
322
323 NSMutableIndexSet *result = [NSMutableIndexSet indexSet];
324
325 /* Extract all the ranges into a local array */
326 FOREACH(NSValue *, rangeValue1, oldSelectedRangeArray) {
327 NSRange range = [rangeValue1 rangeValue];
328 if (range.length > 0) {
329 oldRanges[oldRangeCount++] = range;
330 }
331 }
332 FOREACH(NSValue *, rangeValue2, newSelectedRangeArray) {
333 NSRange range = [rangeValue2 rangeValue];
334 if (range.length > 0) {
335 newRanges[newRangeCount++] = range;
336 }
337 }
338
339 #if ! NDEBUG
340 /* Assert that ranges of arrays do not have any self-intersection; this is supposed to be enforced by our HFController. Also assert that they aren't "just touching"; if they are they should be merged into a single range. */
341 for (NSUInteger i=0; i < oldRangeCount; i++) {
342 for (NSUInteger j=i+1; j < oldRangeCount; j++) {
343 HFASSERT(NSIntersectionRange(oldRanges[i], oldRanges[j]).length == 0);
344 HFASSERT(NSMaxRange(oldRanges[i]) != oldRanges[j].location && NSMaxRange(oldRanges[j]) != oldRanges[i].location);
345 }
346 }
347 for (NSUInteger i=0; i < newRangeCount; i++) {
348 for (NSUInteger j=i+1; j < newRangeCount; j++) {
349 HFASSERT(NSIntersectionRange(newRanges[i], newRanges[j]).length == 0);
350 HFASSERT(NSMaxRange(newRanges[i]) != newRanges[j].location && NSMaxRange(newRanges[j]) != newRanges[i].location);
351 }
352 }
353 #endif
354
355 if (newRangeCount == 0) {
356 [self _addLinesFromRanges:oldRanges count:oldRangeCount toIndexSet:result];
357 }
358 else if (oldRangeCount == 0) {
359 [self _addLinesFromRanges:newRanges count:newRangeCount toIndexSet:result];
360 }
361 else {
362 /* Sort the arrays, since _linesWithParityChangesFromRanges needs it */
363 qsort(oldRanges, oldRangeCount, sizeof *oldRanges, range_compare);
364 qsort(newRanges, newRangeCount, sizeof *newRanges, range_compare);
365
366 [self _linesWithParityChangesFromRanges:oldRanges count:oldRangeCount toRanges:newRanges count:newRangeCount intoIndexSet:result];
367 }
368
369 FREE_ARRAY(oldRanges);
370 FREE_ARRAY(newRanges);
371
372 return result;
373 }
374
375 - (void)updateSelectedRanges {
376 NSArray *oldSelectedRanges = cachedSelectedRanges;
377 cachedSelectedRanges = [[[self representer] displayedSelectedContentsRanges] copy];
378 NSIndexSet *indexSet = [self _indexSetOfLinesNeedingRedrawWhenChangingSelectionFromRanges:oldSelectedRanges toRanges:cachedSelectedRanges];
379 BOOL lastCaretRectNeedsRedraw = ! NSIsEmptyRect(lastDrawnCaretRect);
380 NSRange lineRangeToInvalidate = NSMakeRange(NSUIntegerMax, 0);
381 for (NSUInteger lineIndex = [indexSet firstIndex]; ; lineIndex = [indexSet indexGreaterThanIndex:lineIndex]) {
382 if (lineIndex != NSNotFound && NSMaxRange(lineRangeToInvalidate) == lineIndex) {
383 lineRangeToInvalidate.length++;
384 }
385 else {
386 if (lineRangeToInvalidate.length > 0) {
387 NSRect rectToInvalidate = [self _rectForLineRange:lineRangeToInvalidate];
388 [self setNeedsDisplayInRect:rectToInvalidate];
389 lastCaretRectNeedsRedraw = lastCaretRectNeedsRedraw && ! NSContainsRect(rectToInvalidate, lastDrawnCaretRect);
390 }
391 lineRangeToInvalidate = NSMakeRange(lineIndex, 1);
392 }
393 if (lineIndex == NSNotFound) break;
394 }
395
396 if (lastCaretRectNeedsRedraw) [self setNeedsDisplayInRect:lastDrawnCaretRect];
397 [oldSelectedRanges release]; //balance the retain we borrowed from the ivar
398 [self _updateCaretTimer];
399 [self _forceCaretOnIfHasCaretTimer];
400
401 // A new pulse window will be created at the new selected range if necessary.
402 [self terminateSelectionPulse];
403 }
404
405 - (void)drawPulseBackgroundInRect:(NSRect)pulseRect {
406 [[NSColor yellowColor] set];
407 CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
408 CGContextSaveGState(ctx);
409 [[NSBezierPath bezierPathWithRoundedRect:pulseRect xRadius:25 yRadius:25] addClip];
410 NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:[NSColor yellowColor] endingColor:[NSColor colorWithCalibratedRed:(CGFloat)1. green:(CGFloat).75 blue:0 alpha:1]];
411 [gradient drawInRect:pulseRect angle:90];
412 [gradient release];
413 CGContextRestoreGState(ctx);
414 }
415
416 - (void)fadePulseWindowTimer:(NSTimer *)timer {
417 // TODO: close & invalidate immediatley if view scrolls.
418 NSWindow *window = [timer userInfo];
419 CGFloat alpha = [window alphaValue];
420 alpha -= (CGFloat)(3. / 30.);
421 if (alpha < 0) {
422 [window close];
423 [timer invalidate];
424 }
425 else {
426 [window setAlphaValue:alpha];
427 }
428 }
429
430 - (void)terminateSelectionPulse {
431 if (pulseWindow) {
432 [[self window] removeChildWindow:pulseWindow];
433 [pulseWindow setFrame:pulseWindowBaseFrameInScreenCoordinates display:YES animate:NO];
434 [NSTimer scheduledTimerWithTimeInterval:1. / 30. target:self selector:@selector(fadePulseWindowTimer:) userInfo:pulseWindow repeats:YES];
435 //release is not necessary, since it relases when closed by default
436 pulseWindow = nil;
437 pulseWindowBaseFrameInScreenCoordinates = NSZeroRect;
438 }
439 }
440
441 - (void)drawCaretIfNecessaryWithClip:(NSRect)clipRect {
442 NSRect caretRect = NSIntersectionRect(caretRectToDraw, clipRect);
443 if (! NSIsEmptyRect(caretRect)) {
444 [[NSColor controlTextColor] set];
445 NSRectFill(caretRect);
446 lastDrawnCaretRect = caretRect;
447 }
448 if (NSIsEmptyRect(caretRectToDraw)) lastDrawnCaretRect = NSZeroRect;
449 }
450
451
452 /* This is the color when we are the first responder in the key window */
453 - (NSColor *)primaryTextSelectionColor {
454 return [NSColor selectedTextBackgroundColor];
455 }
456
457 /* This is the color when we are not in the key window */
458 - (NSColor *)inactiveTextSelectionColor {
459 if (@available(macOS 10.14, *)) {
460 return [NSColor unemphasizedSelectedTextBackgroundColor];
461 }
462 return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
463 }
464
465 /* This is the color when we are not the first responder, but we are in the key window */
466 - (NSColor *)secondaryTextSelectionColor {
467 if (@available(macOS 10.14, *)) {
468 return [NSColor unemphasizedSelectedTextBackgroundColor];
469 }
470 return [NSColor colorWithCalibratedWhite: (CGFloat)(212./255.) alpha:1];
471 }
472
473 - (NSColor *)textSelectionColor {
474 NSWindow *window = [self window];
475 if (window == nil) return [self primaryTextSelectionColor];
476 else if (! [window isKeyWindow]) return [self inactiveTextSelectionColor];
477 else if (self != [window firstResponder]) return [self secondaryTextSelectionColor];
478 else return [self primaryTextSelectionColor];
479 }
480
481 - (void)drawSelectionIfNecessaryWithClip:(NSRect)clipRect {
482 NSArray *ranges = [self displayedSelectedContentsRanges];
483 NSUInteger bytesPerLine = [self bytesPerLine];
484 [[self textSelectionColor] set];
485 CGFloat lineHeight = [self lineHeight];
486 FOREACH(NSValue *, rangeValue, ranges) {
487 NSRange range = [rangeValue rangeValue];
488 if (range.length > 0) {
489 NSUInteger startByteIndex = range.location;
490 NSUInteger endByteIndexForThisRange = range.location + range.length - 1;
491 NSUInteger byteIndex = startByteIndex;
492 while (byteIndex <= endByteIndexForThisRange) {
493 NSUInteger endByteIndexForLine = ((byteIndex / bytesPerLine) + 1) * bytesPerLine - 1;
494 NSUInteger endByteForThisLineOfRange = MIN(endByteIndexForThisRange, endByteIndexForLine);
495 NSPoint startPoint = [self originForCharacterAtByteIndex:byteIndex];
496 NSPoint endPoint = [self originForCharacterAtByteIndex:endByteForThisLineOfRange];
497 NSRect selectionRect = NSMakeRect(startPoint.x, startPoint.y, endPoint.x + [self advancePerCharacter] - startPoint.x, lineHeight);
498 NSRect clippedSelectionRect = NSIntersectionRect(selectionRect, clipRect);
499 if (! NSIsEmptyRect(clippedSelectionRect)) {
500 NSRectFill(clippedSelectionRect);
501 }
502 byteIndex = endByteForThisLineOfRange + 1;
503 }
504 }
505 }
506 }
507
508 - (BOOL)acceptsFirstResponder {
509 return YES;
510 }
511
512 - (BOOL)hasVisibleDisplayedSelectedContentsRange {
513 FOREACH(NSValue *, rangeValue, [self displayedSelectedContentsRanges]) {
514 NSRange range = [rangeValue rangeValue];
515 if (range.length > 0) {
516 return YES;
517 }
518 }
519 return NO;
520 }
521
522 - (BOOL)becomeFirstResponder {
523 BOOL result = [super becomeFirstResponder];
524 [self _updateCaretTimerWithFirstResponderStatus:YES];
525 if ([self showsFocusRing] || [self hasVisibleDisplayedSelectedContentsRange]) {
526 [self setNeedsDisplay:YES];
527 }
528 return result;
529 }
530
531 - (BOOL)resignFirstResponder {
532 BOOL result = [super resignFirstResponder];
533 [self _updateCaretTimerWithFirstResponderStatus:NO];
534 BOOL needsRedisplay = NO;
535 if ([self showsFocusRing]) needsRedisplay = YES;
536 else if (! NSIsEmptyRect(lastDrawnCaretRect)) needsRedisplay = YES;
537 else if ([self hasVisibleDisplayedSelectedContentsRange]) needsRedisplay = YES;
538 if (needsRedisplay) [self setNeedsDisplay:YES];
539 return result;
540 }
541
542 - (instancetype)initWithRepresenter:(HFTextRepresenter *)rep {
543 self = [super initWithFrame:NSMakeRect(0, 0, 1, 1)];
544 horizontalContainerInset = 4;
545 representer = rep;
546 _hftvflags.editable = YES;
547
548 return self;
549 }
550
551 - (void)clearRepresenter {
552 representer = nil;
553 }
554
555 - (void)encodeWithCoder:(NSCoder *)coder {
556 HFASSERT([coder allowsKeyedCoding]);
557 [super encodeWithCoder:coder];
558 [coder encodeObject:representer forKey:@"HFRepresenter"];
559 [coder encodeObject:_font forKey:@"HFFont"];
560 [coder encodeObject:_data forKey:@"HFData"];
561 [coder encodeDouble:verticalOffset forKey:@"HFVerticalOffset"];
562 [coder encodeDouble:horizontalContainerInset forKey:@"HFHorizontalContainerOffset"];
563 [coder encodeDouble:defaultLineHeight forKey:@"HFDefaultLineHeight"];
564 [coder encodeInt64:bytesBetweenVerticalGuides forKey:@"HFBytesBetweenVerticalGuides"];
565 [coder encodeInt64:startingLineBackgroundColorIndex forKey:@"HFStartingLineBackgroundColorIndex"];
566 [coder encodeObject:rowBackgroundColors forKey:@"HFRowBackgroundColors"];
567 [coder encodeBool:_hftvflags.antialias forKey:@"HFAntialias"];
568 [coder encodeBool:_hftvflags.drawCallouts forKey:@"HFDrawCallouts"];
569 [coder encodeBool:_hftvflags.editable forKey:@"HFEditable"];
570 }
571
572 - (instancetype)initWithCoder:(NSCoder *)coder {
573 HFASSERT([coder allowsKeyedCoding]);
574 self = [super initWithCoder:coder];
575 representer = [coder decodeObjectForKey:@"HFRepresenter"];
576 _font = [[coder decodeObjectForKey:@"HFFont"] retain];
577 _data = [[coder decodeObjectForKey:@"HFData"] retain];
578 verticalOffset = (CGFloat)[coder decodeDoubleForKey:@"HFVerticalOffset"];
579 horizontalContainerInset = (CGFloat)[coder decodeDoubleForKey:@"HFHorizontalContainerOffset"];
580 defaultLineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFDefaultLineHeight"];
581 bytesBetweenVerticalGuides = (NSUInteger)[coder decodeInt64ForKey:@"HFBytesBetweenVerticalGuides"];
582 startingLineBackgroundColorIndex = (NSUInteger)[coder decodeInt64ForKey:@"HFStartingLineBackgroundColorIndex"];
583 rowBackgroundColors = [[coder decodeObjectForKey:@"HFRowBackgroundColors"] retain];
584 _hftvflags.antialias = [coder decodeBoolForKey:@"HFAntialias"];
585 _hftvflags.drawCallouts = [coder decodeBoolForKey:@"HFDrawCallouts"];
586 _hftvflags.editable = [coder decodeBoolForKey:@"HFEditable"];
587 return self;
588 }
589
590 - (CGFloat)horizontalContainerInset {
591 return horizontalContainerInset;
592 }
593
594 - (void)setHorizontalContainerInset:(CGFloat)inset {
595 horizontalContainerInset = inset;
596 }
597
598 - (void)setBytesBetweenVerticalGuides:(NSUInteger)val {
599 bytesBetweenVerticalGuides = val;
600 }
601
602 - (NSUInteger)bytesBetweenVerticalGuides {
603 return bytesBetweenVerticalGuides;
604 }
605
606
607 - (void)setFont:(NSFont *)val {
608 if (val != _font) {
609 [_font release];
610 _font = [val retain];
611 NSLayoutManager *manager = [[NSLayoutManager alloc] init];
612 defaultLineHeight = [manager defaultLineHeightForFont:_font];
613 [manager release];
614 [self setNeedsDisplay:YES];
615 }
616 }
617
618 - (CGFloat)lineHeight {
619 return defaultLineHeight;
620 }
621
622 /* The base implementation does not support font substitution, so we require that it be the base font. */
623 - (NSFont *)fontAtSubstitutionIndex:(uint16_t)idx {
624 HFASSERT(idx == 0);
625 USE(idx);
626 return _font;
627 }
628
629 - (NSRange)roundPartialByteRange:(NSRange)byteRange {
630 NSUInteger bytesPerCharacter = [self bytesPerCharacter];
631 /* Get the left and right edges of the range */
632 NSUInteger left = byteRange.location, right = NSMaxRange(byteRange);
633
634 /* Round both to the left. This may make the range bigger or smaller, or empty! */
635 left -= left % bytesPerCharacter;
636 right -= right % bytesPerCharacter;
637
638 /* Done */
639 HFASSERT(right >= left);
640 return NSMakeRange(left, right - left);
641
642 }
643
644 - (void)setNeedsDisplayForLinesInRange:(NSRange)lineRange {
645 // redisplay the lines in the given range
646 if (lineRange.length == 0) return;
647 NSUInteger firstLine = lineRange.location, lastLine = NSMaxRange(lineRange);
648 CGFloat lineHeight = [self lineHeight];
649 CGFloat vertOffset = [self verticalOffset];
650 CGFloat yOrigin = (firstLine - vertOffset) * lineHeight;
651 CGFloat lastLineBottom = (lastLine - vertOffset) * lineHeight;
652 NSRect bounds = [self bounds];
653 NSRect dirtyRect = NSMakeRect(bounds.origin.x, bounds.origin.y + yOrigin, NSWidth(bounds), lastLineBottom - yOrigin);
654 [self setNeedsDisplayInRect:dirtyRect];
655 }
656
657 - (void)setData:(NSData *)val {
658 if (val != _data) {
659 NSUInteger oldLength = [_data length];
660 NSUInteger newLength = [val length];
661 const unsigned char *oldBytes = (const unsigned char *)[_data bytes];
662 const unsigned char *newBytes = (const unsigned char *)[val bytes];
663 NSUInteger firstDifferingIndex = HFIndexOfFirstByteThatDiffers(oldBytes, oldLength, newBytes, newLength);
664 if (firstDifferingIndex == NSUIntegerMax) {
665 /* Nothing to do! Data is identical! */
666 }
667 else {
668 NSUInteger lastDifferingIndex = HFIndexOfLastByteThatDiffers(oldBytes, oldLength, newBytes, newLength);
669 HFASSERT(lastDifferingIndex != NSUIntegerMax); //if we have a first different byte, we must have a last different byte
670 /* Expand to encompass characters that they touch */
671 NSUInteger bytesPerCharacter = [self bytesPerCharacter];
672 firstDifferingIndex -= firstDifferingIndex % bytesPerCharacter;
673 lastDifferingIndex = HFRoundUpToMultipleInt(lastDifferingIndex, bytesPerCharacter);
674
675 /* Now figure out the line range they touch */
676 const NSUInteger bytesPerLine = [self bytesPerLine];
677 NSUInteger firstLine = firstDifferingIndex / bytesPerLine;
678 NSUInteger lastLine = HFDivideULRoundingUp(MAX(oldLength, newLength), bytesPerLine);
679 /* The +1 is for the following case - if we change the last character, then it may push the caret into the next line (even though there's no text there). This last line may have a background color, so we need to make it draw if it did not draw before (or vice versa - when deleting the last character which pulls the caret from the last line). */
680 NSUInteger lastDifferingLine = (lastDifferingIndex == NSNotFound ? lastLine : HFDivideULRoundingUp(lastDifferingIndex + 1, bytesPerLine));
681 if (lastDifferingLine > firstLine) {
682 [self setNeedsDisplayForLinesInRange:NSMakeRange(firstLine, lastDifferingLine - firstLine)];
683 }
684 }
685 [_data release];
686 _data = [val copy];
687 [self _updateCaretTimer];
688 }
689 }
690
691 - (void)setStyles:(NSArray *)newStyles {
692 if (! [_styles isEqual:newStyles]) {
693
694 /* Figure out which styles changed - that is, we want to compute those objects that are not in oldStyles or newStyles, but not both. */
695 NSMutableSet *changedStyles = _styles ? [[NSMutableSet alloc] initWithArray:_styles] : [[NSMutableSet alloc] init];
696 FOREACH(HFTextVisualStyleRun *, run, newStyles) {
697 if ([changedStyles containsObject:run]) {
698 [changedStyles removeObject:run];
699 }
700 else {
701 [changedStyles addObject:run];
702 }
703 }
704
705 /* Now figure out the first and last indexes of changed ranges. */
706 NSUInteger firstChangedIndex = NSUIntegerMax, lastChangedIndex = 0;
707 FOREACH(HFTextVisualStyleRun *, changedRun, changedStyles) {
708 NSRange range = [changedRun range];
709 if (range.length > 0) {
710 firstChangedIndex = MIN(firstChangedIndex, range.location);
711 lastChangedIndex = MAX(lastChangedIndex, NSMaxRange(range) - 1);
712 }
713 }
714
715 /* Don't need this any more */
716 [changedStyles release];
717
718 /* Expand to cover all touched characters */
719 NSUInteger bytesPerCharacter = [self bytesPerCharacter];
720 firstChangedIndex -= firstChangedIndex % bytesPerCharacter;
721 lastChangedIndex = HFRoundUpToMultipleInt(lastChangedIndex, bytesPerCharacter);
722
723 /* Figure out the changed lines, and trigger redisplay */
724 if (firstChangedIndex <= lastChangedIndex) {
725 const NSUInteger bytesPerLine = [self bytesPerLine];
726 NSUInteger firstLine = firstChangedIndex / bytesPerLine;
727 NSUInteger lastLine = HFDivideULRoundingUp(lastChangedIndex, bytesPerLine);
728 [self setNeedsDisplayForLinesInRange:NSMakeRange(firstLine, lastLine - firstLine + 1)];
729 }
730
731 /* Do the usual Cocoa thing */
732 [_styles release];
733 _styles = [newStyles copy];
734 }
735 }
736
737 - (void)setVerticalOffset:(CGFloat)val {
738 if (val != verticalOffset) {
739 verticalOffset = val;
740 [self setNeedsDisplay:YES];
741 }
742 }
743
744 - (CGFloat)verticalOffset {
745 return verticalOffset;
746 }
747
748 - (NSUInteger)startingLineBackgroundColorIndex {
749 return startingLineBackgroundColorIndex;
750 }
751
752 - (void)setStartingLineBackgroundColorIndex:(NSUInteger)val {
753 startingLineBackgroundColorIndex = val;
754 }
755
756 - (BOOL)isFlipped {
757 return YES;
758 }
759
760 - (HFTextRepresenter *)representer {
761 return representer;
762 }
763
764 - (void)dealloc {
765 HFUnregisterViewForWindowAppearanceChanges(self, _hftvflags.registeredForAppNotifications /* appToo */);
766 [caretTimer invalidate];
767 [caretTimer release];
768 [_font release];
769 [_data release];
770 [_styles release];
771 [cachedSelectedRanges release];
772 [callouts release];
773 if(byteColoring) Block_release(byteColoring);
774 [super dealloc];
775 }
776
777 - (NSColor *)backgroundColorForEmptySpace {
778 NSArray *colors = [[self representer] rowBackgroundColors];
779 if (! [colors count]) return [NSColor clearColor];
780 else return colors[0];
781 }
782
783 - (NSColor *)backgroundColorForLine:(NSUInteger)line {
784 NSArray *colors = [[self representer] rowBackgroundColors];
785 NSUInteger colorCount = [colors count];
786 if (colorCount == 0) return [NSColor clearColor];
787 NSUInteger colorIndex = (line + startingLineBackgroundColorIndex) % colorCount;
788 if (colorIndex == 0) return nil; //will be drawn by empty space
789 else return colors[colorIndex];
790 }
791
792 - (NSUInteger)bytesPerLine {
793 HFASSERT([self representer] != nil);
794 return [[self representer] bytesPerLine];
795 }
796
797 - (NSUInteger)bytesPerColumn {
798 HFASSERT([self representer] != nil);
799 return [[self representer] bytesPerColumn];
800 }
801
802 - (void)_drawDefaultLineBackgrounds:(NSRect)clip withLineHeight:(CGFloat)lineHeight maxLines:(NSUInteger)maxLines {
803 NSRect bounds = [self bounds];
804 NSUInteger lineIndex;
805 NSRect lineRect = NSMakeRect(NSMinX(bounds), NSMinY(bounds), NSWidth(bounds), lineHeight);
806 if ([self showsFocusRing]) lineRect = NSInsetRect(lineRect, 2, 0);
807 lineRect.origin.y -= [self verticalOffset] * [self lineHeight];
808 NSUInteger drawableLineIndex = 0;
809 NEW_ARRAY(NSRect, lineRects, maxLines);
810 NEW_ARRAY(NSColor*, lineColors, maxLines);
811 for (lineIndex = 0; lineIndex < maxLines; lineIndex++) {
812 NSRect clippedLineRect = NSIntersectionRect(lineRect, clip);
813 if (! NSIsEmptyRect(clippedLineRect)) {
814 NSColor *lineColor = [self backgroundColorForLine:lineIndex];
815 if (lineColor) {
816 lineColors[drawableLineIndex] = lineColor;
817 lineRects[drawableLineIndex] = clippedLineRect;
818 drawableLineIndex++;
819 }
820 }
821 lineRect.origin.y += lineHeight;
822 }
823
824 if (drawableLineIndex > 0) {
825 NSRectFillListWithColorsUsingOperation(lineRects, lineColors, drawableLineIndex, NSCompositeSourceOver);
826 }
827
828 FREE_ARRAY(lineRects);
829 FREE_ARRAY(lineColors);
830 }
831
832 - (HFTextVisualStyleRun *)styleRunForByteAtIndex:(NSUInteger)byteIndex {
833 HFTextVisualStyleRun *run = [[HFTextVisualStyleRun alloc] init];
834 [run setRange:NSMakeRange(0, NSUIntegerMax)];
835 [run setForegroundColor:[NSColor textColor]];
836 return [run autorelease];
837 }
838
839 /* Given a list of rects and a parallel list of values, find cases of equal adjacent values, and union together their corresponding rects, deleting the second element from the list. Next, delete all nil values. Returns the new count of the list. */
840 static size_t unionAndCleanLists(NSRect *rectList, id *valueList, size_t count) {
841 size_t trailing = 0, leading = 0;
842 while (leading < count) {
843 /* Copy our value left */
844 valueList[trailing] = valueList[leading];
845 rectList[trailing] = rectList[leading];
846
847 /* Skip one - no point unioning with ourselves */
848 leading += 1;
849
850 /* Sweep right, unioning until we reach a different value or the end */
851 id targetValue = valueList[trailing];
852 for (; leading < count; leading++) {
853 id testValue = valueList[leading];
854 if (targetValue == testValue || (testValue && [targetValue isEqual:testValue])) {
855 /* Values match, so union the two rects */
856 rectList[trailing] = NSUnionRect(rectList[trailing], rectList[leading]);
857 }
858 else {
859 /* Values don't match, we're done sweeping */
860 break;
861 }
862 }
863
864 /* We're done with this index */
865 trailing += 1;
866 }
867
868 /* trailing keeps track of how many values we have */
869 count = trailing;
870
871 /* Now do the same thing, except delete nil values */
872 for (trailing = leading = 0; leading < count; leading++) {
873 if (valueList[leading] != nil) {
874 valueList[trailing] = valueList[leading];
875 rectList[trailing] = rectList[leading];
876 trailing += 1;
877 }
878 }
879 count = trailing;
880
881 /* All done */
882 return count;
883 }
884
885 /* Draw vertical guidelines every four bytes */
886 - (void)drawVerticalGuideLines:(NSRect)clip {
887 if (bytesBetweenVerticalGuides == 0) return;
888
889 NSUInteger bytesPerLine = [self bytesPerLine];
890 NSRect bounds = [self bounds];
891 CGFloat advancePerCharacter = [self advancePerCharacter];
892 CGFloat spaceAdvancement = advancePerCharacter / 2;
893 CGFloat advanceAmount = (advancePerCharacter + spaceAdvancement) * bytesBetweenVerticalGuides;
894 CGFloat lineOffset = (CGFloat)(NSMinX(bounds) + [self horizontalContainerInset] + advanceAmount - spaceAdvancement / 2.);
895 CGFloat endOffset = NSMaxX(bounds) - [self horizontalContainerInset];
896
897 NSUInteger numGuides = (bytesPerLine - 1) / bytesBetweenVerticalGuides; // -1 is a trick to avoid drawing the last line
898 NSUInteger guideIndex = 0, rectIndex = 0;
899 NEW_ARRAY(NSRect, lineRects, numGuides);
900
901 while (lineOffset < endOffset && guideIndex < numGuides) {
902 NSRect lineRect = NSMakeRect(lineOffset - 1, NSMinY(bounds), 1, NSHeight(bounds));
903 NSRect clippedLineRect = NSIntersectionRect(lineRect, clip);
904 if (! NSIsEmptyRect(clippedLineRect)) {
905 lineRects[rectIndex++] = clippedLineRect;
906 }
907 lineOffset += advanceAmount;
908 guideIndex++;
909 }
910 if (rectIndex > 0) {
911 [[NSColor gridColor] set];
912 NSRectFillListUsingOperation(lineRects, rectIndex, NSCompositeSourceOver);
913 }
914 FREE_ARRAY(lineRects);
915 }
916
917 - (NSUInteger)maximumGlyphCountForByteCount:(NSUInteger)byteCount {
918 USE(byteCount);
919 UNIMPLEMENTED();
920 }
921
922 - (void)setByteColoring:(void (^)(uint8_t, uint8_t*, uint8_t*, uint8_t*, uint8_t*))coloring {
923 Block_release(byteColoring);
924 byteColoring = coloring ? Block_copy(coloring) : NULL;
925 [self setNeedsDisplay:YES];
926 }
927
928 - (void)drawByteColoringBackground:(NSRange)range inRect:(NSRect)rect {
929 if(!byteColoring) return;
930
931 size_t width = (size_t)rect.size.width;
932
933 // A rgba, 8-bit, single row image.
934 // +1 in case messing around with floats makes us overshoot a bit.
935 uint32_t *buffer = calloc(width+1, 4);
936
937 const uint8_t *bytes = [_data bytes];
938 bytes += range.location;
939
940 NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
941 CGFloat advancePerCharacter = [self advancePerCharacter];
942 CGFloat advanceBetweenColumns = [self advanceBetweenColumns];
943
944 // For each character, draw the corresponding part of the image
945 CGFloat offset = [self horizontalContainerInset];
946 for(NSUInteger i = 0; i < range.length; i++) {
947 uint8_t r, g, b, a;
948 byteColoring(bytes[i], &r, &g, &b, &a);
949 uint32_t c = ((uint32_t)r<<0) | ((uint32_t)g<<8) | ((uint32_t)b<<16) | ((uint32_t)a<<24);
950 memset_pattern4(&buffer[(size_t)offset], &c, 4*(size_t)(advancePerCharacter+1));
951 offset += advancePerCharacter;
952 if(bytesPerColumn && (i+1) % bytesPerColumn == 0)
953 offset += advanceBetweenColumns;
954 }
955
956 // Do a CGImage dance to draw the buffer
957 CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, 4 * width, NULL);
958 CGColorSpaceRef cgcolorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
959 CGImageRef image = CGImageCreate(width, 1, 8, 32, 4 * width, cgcolorspace,
960 (CGBitmapInfo)kCGImageAlphaLast, provider, NULL, false, kCGRenderingIntentDefault);
961 CGContextDrawImage([[NSGraphicsContext currentContext] graphicsPort], NSRectToCGRect(rect), image);
962 CGColorSpaceRelease(cgcolorspace);
963 CGImageRelease(image);
964 CGDataProviderRelease(provider);
965 free(buffer);
966 }
967
968 - (void)drawStyledBackgroundsForByteRange:(NSRange)range inRect:(NSRect)rect {
969 NSRect remainingRunRect = rect;
970 NSRange remainingRange = range;
971
972 /* Our caller lies to us a little */
973 remainingRunRect.origin.x += [self horizontalContainerInset];
974
975 const NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
976
977 /* Here are the properties we care about */
978 struct PropertyInfo_t {
979 SEL stylePropertyAccessor; // the selector we use to get the property
980 NSRect *rectList; // the list of rects corresponding to the property values
981 id *propertyValueList; // the list of the property values
982 size_t count; //list count, only gets set after cleaning up our lists
983 } propertyInfos[] = {
984 {.stylePropertyAccessor = @selector(backgroundColor)},
985 {.stylePropertyAccessor = @selector(bookmarkStarts)},
986 {.stylePropertyAccessor = @selector(bookmarkExtents)},
987 {.stylePropertyAccessor = @selector(bookmarkEnds)}
988 };
989
990 /* Each list has the same capacity, and (initially) the same count */
991 size_t listCount = 0, listCapacity = 0;
992
993 /* The function pointer we use to get our property values */
994 id (* const funcPtr)(id, SEL) = (id (*)(id, SEL))objc_msgSend;
995
996 size_t propertyIndex;
997 const size_t propertyInfoCount = sizeof propertyInfos / sizeof *propertyInfos;
998
999 while (remainingRange.length > 0) {
1000 /* Get the next run for the remaining range. */
1001 HFTextVisualStyleRun *styleRun = [self styleRunForByteAtIndex:remainingRange.location];
1002
1003 /* The length of the run is the end of the style run or the end of the range we're given (whichever is smaller), minus the beginning of the range we care about. */
1004 NSUInteger runStart = remainingRange.location;
1005 NSUInteger runLength = MIN(NSMaxRange(range), NSMaxRange([styleRun range])) - runStart;
1006
1007 /* Get the width of this run and use it to compute the rect */
1008 CGFloat runRectWidth = [self totalAdvanceForBytesInRange:NSMakeRange(remainingRange.location, runLength)];
1009 NSRect runRect = remainingRunRect;
1010 runRect.size.width = runRectWidth;
1011
1012 /* Update runRect and remainingRunRect based on what we just learned */
1013 remainingRunRect.origin.x += runRectWidth;
1014 remainingRunRect.size.width -= runRectWidth;
1015
1016 /* Do a hack - if we end at a column boundary, subtract the advance between columns. If the next run has the same value for this property, then we'll end up unioning the rects together and the column gap will be filled. This is the primary purpose of this function. */
1017 if (bytesPerColumn > 0 && (runStart + runLength) % bytesPerColumn == 0) {
1018 runRect.size.width -= MIN([self advanceBetweenColumns], runRect.size.width);
1019 }
1020
1021 /* Extend our lists if necessary */
1022 if (listCount == listCapacity) {
1023 /* Our list is too small, extend it */
1024 listCapacity = listCapacity + 16;
1025
1026 for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) {
1027 struct PropertyInfo_t *p = propertyInfos + propertyIndex;
1028 p->rectList = check_realloc(p->rectList, listCapacity * sizeof *p->rectList);
1029 p->propertyValueList = check_realloc(p->propertyValueList, listCapacity * sizeof *p->propertyValueList);
1030 }
1031 }
1032
1033 /* Now append our values to our lists, even if it's nil */
1034 for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) {
1035 struct PropertyInfo_t *p = propertyInfos + propertyIndex;
1036 id value = funcPtr(styleRun, p->stylePropertyAccessor);
1037 p->rectList[listCount] = runRect;
1038 p->propertyValueList[listCount] = value;
1039 }
1040
1041 listCount++;
1042
1043 /* Update remainingRange */
1044 remainingRange.location += runLength;
1045 remainingRange.length -= runLength;
1046
1047 }
1048
1049 /* Now clean up our lists, to delete the gaps we may have introduced */
1050 for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) {
1051 struct PropertyInfo_t *p = propertyInfos + propertyIndex;
1052 p->count = unionAndCleanLists(p->rectList, p->propertyValueList, listCount);
1053 }
1054
1055 /* Finally we can draw them! First, draw byte backgrounds. */
1056 [self drawByteColoringBackground:range inRect:rect];
1057
1058 const struct PropertyInfo_t *p;
1059
1060 /* Draw backgrounds */
1061 p = propertyInfos + 0;
1062 if (p->count > 0) NSRectFillListWithColorsUsingOperation(p->rectList, p->propertyValueList, p->count, NSCompositeSourceOver);
1063
1064 /* Clean up */
1065 for (propertyIndex = 0; propertyIndex < propertyInfoCount; propertyIndex++) {
1066 p = propertyInfos + propertyIndex;
1067 free(p->rectList);
1068 free(p->propertyValueList);
1069 }
1070 }
1071
1072 - (void)drawGlyphs:(const struct HFGlyph_t *)glyphs atPoint:(NSPoint)point withAdvances:(const CGSize *)advances withStyleRun:(HFTextVisualStyleRun *)styleRun count:(NSUInteger)glyphCount {
1073 HFASSERT(glyphs != NULL);
1074 HFASSERT(advances != NULL);
1075 HFASSERT(glyphCount > 0);
1076 if ([styleRun shouldDraw]) {
1077 [styleRun set];
1078 CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
1079
1080 /* Get all the CGGlyphs together */
1081 NEW_ARRAY(CGGlyph, cgglyphs, glyphCount);
1082 for (NSUInteger j=0; j < glyphCount; j++) {
1083 cgglyphs[j] = glyphs[j].glyph;
1084 }
1085
1086 NSUInteger runStart = 0;
1087 HFGlyphFontIndex runFontIndex = glyphs[0].fontIndex;
1088 CGFloat runAdvance = 0;
1089 for (NSUInteger i=1; i <= glyphCount; i++) {
1090 /* Check if this run is finished, or if we are using a substitution font */
1091 if (i == glyphCount || glyphs[i].fontIndex != runFontIndex || runFontIndex > 0) {
1092 /* Draw this run */
1093 NSFont *fontToUse = [self fontAtSubstitutionIndex:runFontIndex];
1094 [[fontToUse screenFont] set];
1095 CGContextSetTextPosition(ctx, point.x + runAdvance, point.y);
1096
1097 if (runFontIndex > 0) {
1098 /* A substitution font. Here we should only have one glyph */
1099 HFASSERT(i - runStart == 1);
1100 /* Get the advance for this glyph. */
1101 NSSize nativeAdvance;
1102 NSGlyph nativeGlyph = cgglyphs[runStart];
1103 [fontToUse getAdvancements:&nativeAdvance forGlyphs:&nativeGlyph count:1];
1104 if (nativeAdvance.width > advances[runStart].width) {
1105 /* This glyph is too wide! We'll have to scale it. Here we only scale horizontally. */
1106 CGFloat horizontalScale = advances[runStart].width / nativeAdvance.width;
1107 CGAffineTransform textCTM = CGContextGetTextMatrix(ctx);
1108 textCTM.a *= horizontalScale;
1109 CGContextSetTextMatrix(ctx, textCTM);
1110 /* Note that we don't have to restore the text matrix, because the next call to set the font will overwrite it. */
1111 }
1112 }
1113
1114 /* Draw the glyphs */
1115 CGContextShowGlyphsWithAdvances(ctx, cgglyphs + runStart, advances + runStart, i - runStart);
1116
1117 /* Record the new run */
1118 if (i < glyphCount) {
1119 /* Sum the advances */
1120 for (NSUInteger j = runStart; j < i; j++) {
1121 runAdvance += advances[j].width;
1122 }
1123
1124 /* Record the new run start and index */
1125 runStart = i;
1126 runFontIndex = glyphs[i].fontIndex;
1127 HFASSERT(runFontIndex != kHFGlyphFontIndexInvalid);
1128 }
1129 }
1130 }
1131 }
1132 }
1133
1134
1135 - (void)extractGlyphsForBytes:(const unsigned char *)bytes count:(NSUInteger)numBytes offsetIntoLine:(NSUInteger)offsetIntoLine intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances resultingGlyphCount:(NSUInteger *)resultGlyphCount {
1136 USE(bytes);
1137 USE(numBytes);
1138 USE(offsetIntoLine);
1139 USE(glyphs);
1140 USE(advances);
1141 USE(resultGlyphCount);
1142 UNIMPLEMENTED_VOID();
1143 }
1144
1145 - (void)extractGlyphsForBytes:(const unsigned char *)bytePtr range:(NSRange)byteRange intoArray:(struct HFGlyph_t *)glyphs advances:(CGSize *)advances withInclusionRanges:(NSArray *)restrictingToRanges initialTextOffset:(CGFloat *)initialTextOffset resultingGlyphCount:(NSUInteger *)resultingGlyphCount {
1146 NSParameterAssert(glyphs != NULL && advances != NULL && restrictingToRanges != nil && bytePtr != NULL);
1147 NSRange priorIntersectionRange = {NSUIntegerMax, NSUIntegerMax};
1148 NSUInteger glyphBufferIndex = 0;
1149 NSUInteger bytesPerLine = [self bytesPerLine];
1150 NSUInteger restrictionRangeCount = [restrictingToRanges count];
1151 for (NSUInteger rangeIndex = 0; rangeIndex < restrictionRangeCount; rangeIndex++) {
1152 NSRange inclusionRange = [restrictingToRanges[rangeIndex] rangeValue];
1153 NSRange intersectionRange = NSIntersectionRange(inclusionRange, byteRange);
1154 if (intersectionRange.length == 0) continue;
1155
1156 NSUInteger offsetIntoLine = intersectionRange.location % bytesPerLine;
1157
1158 NSRange byteRangeToSkip;
1159 if (priorIntersectionRange.location == NSUIntegerMax) {
1160 byteRangeToSkip = NSMakeRange(byteRange.location, intersectionRange.location - byteRange.location);
1161 }
1162 else {
1163 HFASSERT(intersectionRange.location >= NSMaxRange(priorIntersectionRange));
1164 byteRangeToSkip.location = NSMaxRange(priorIntersectionRange);
1165 byteRangeToSkip.length = intersectionRange.location - byteRangeToSkip.location;
1166 }
1167
1168 if (byteRangeToSkip.length > 0) {
1169 CGFloat additionalAdvance = [self totalAdvanceForBytesInRange:byteRangeToSkip];
1170 if (glyphBufferIndex == 0) {
1171 *initialTextOffset = *initialTextOffset + additionalAdvance;
1172 }
1173 else {
1174 advances[glyphBufferIndex - 1].width += additionalAdvance;
1175 }
1176 }
1177
1178 NSUInteger glyphCountForRange = NSUIntegerMax;
1179 [self extractGlyphsForBytes:bytePtr + intersectionRange.location count:intersectionRange.length offsetIntoLine:offsetIntoLine intoArray:glyphs + glyphBufferIndex advances:advances + glyphBufferIndex resultingGlyphCount:&glyphCountForRange];
1180 HFASSERT(glyphCountForRange != NSUIntegerMax);
1181 glyphBufferIndex += glyphCountForRange;
1182 priorIntersectionRange = intersectionRange;
1183 }
1184 if (resultingGlyphCount) *resultingGlyphCount = glyphBufferIndex;
1185 }
1186
1187 - (void)drawTextWithClip:(NSRect)clip restrictingToTextInRanges:(NSArray *)restrictingToRanges {
1188 CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
1189 NSRect bounds = [self bounds];
1190 CGFloat lineHeight = [self lineHeight];
1191
1192 CGAffineTransform textTransform = CGContextGetTextMatrix(ctx);
1193 CGContextSetTextDrawingMode(ctx, kCGTextFill);
1194
1195 NSUInteger lineStartIndex, bytesPerLine = [self bytesPerLine];
1196 NSData *dataObject = [self data];
1197 NSFont *fontObject = [[self font] screenFont];
1198 //const NSUInteger bytesPerChar = [self bytesPerCharacter];
1199 const NSUInteger byteCount = [dataObject length];
1200
1201 const unsigned char * const bytePtr = [dataObject bytes];
1202
1203 NSRect lineRectInBoundsSpace = NSMakeRect(NSMinX(bounds), NSMinY(bounds), NSWidth(bounds), lineHeight);
1204 lineRectInBoundsSpace.origin.y -= [self verticalOffset] * lineHeight;
1205
1206 /* Start us off with the horizontal inset and move the baseline down by the ascender so our glyphs just graze the top of our view */
1207 textTransform.tx += [self horizontalContainerInset];
1208 textTransform.ty += [fontObject ascender] - lineHeight * [self verticalOffset];
1209 const NSUInteger maxGlyphCount = [self maximumGlyphCountForByteCount:bytesPerLine];
1210 NEW_ARRAY(struct HFGlyph_t, glyphs, maxGlyphCount);
1211 NEW_ARRAY(CGSize, advances, maxGlyphCount);
1212 for (lineStartIndex = 0; lineStartIndex < byteCount; lineStartIndex += bytesPerLine) {
1213 if (lineStartIndex > 0) {
1214 textTransform.ty += lineHeight;
1215 lineRectInBoundsSpace.origin.y += lineHeight;
1216 }
1217 if (NSIntersectsRect(lineRectInBoundsSpace, clip)) {
1218 const NSUInteger bytesInThisLine = MIN(bytesPerLine, byteCount - lineStartIndex);
1219
1220 /* Draw the backgrounds of any styles. */
1221 [self drawStyledBackgroundsForByteRange:NSMakeRange(lineStartIndex, bytesInThisLine) inRect:lineRectInBoundsSpace];
1222
1223 NSUInteger byteIndexInLine = 0;
1224 CGFloat advanceIntoLine = 0;
1225 while (byteIndexInLine < bytesInThisLine) {
1226 const NSUInteger byteIndex = lineStartIndex + byteIndexInLine;
1227 HFTextVisualStyleRun *styleRun = [self styleRunForByteAtIndex:byteIndex];
1228 HFASSERT(styleRun != nil);
1229 HFASSERT(byteIndex >= [styleRun range].location);
1230 const NSUInteger bytesInThisRun = MIN(NSMaxRange([styleRun range]) - byteIndex, bytesInThisLine - byteIndexInLine);
1231 const NSRange characterRange = [self roundPartialByteRange:NSMakeRange(byteIndex, bytesInThisRun)];
1232 if (characterRange.length > 0) {
1233 NSUInteger resultGlyphCount = 0;
1234 CGFloat initialTextOffset = 0;
1235 if (restrictingToRanges == nil) {
1236 [self extractGlyphsForBytes:bytePtr + characterRange.location count:characterRange.length offsetIntoLine:byteIndexInLine intoArray:glyphs advances:advances resultingGlyphCount:&resultGlyphCount];
1237 }
1238 else {
1239 [self extractGlyphsForBytes:bytePtr range:NSMakeRange(byteIndex, bytesInThisRun) intoArray:glyphs advances:advances withInclusionRanges:restrictingToRanges initialTextOffset:&initialTextOffset resultingGlyphCount:&resultGlyphCount];
1240 }
1241 HFASSERT(resultGlyphCount <= maxGlyphCount);
1242
1243 #if ! NDEBUG
1244 for (NSUInteger q=0; q < resultGlyphCount; q++) {
1245 HFASSERT(glyphs[q].fontIndex != kHFGlyphFontIndexInvalid);
1246 }
1247 #endif
1248
1249 if (resultGlyphCount > 0) {
1250 textTransform.tx += initialTextOffset + advanceIntoLine;
1251 CGContextSetTextMatrix(ctx, textTransform);
1252 /* Draw them */
1253 [self drawGlyphs:glyphs atPoint:NSMakePoint(textTransform.tx, textTransform.ty) withAdvances:advances withStyleRun:styleRun count:resultGlyphCount];
1254
1255 /* Undo the work we did before so as not to screw up the next run */
1256 textTransform.tx -= initialTextOffset + advanceIntoLine;
1257
1258 /* Record how far into our line this made us move */
1259 NSUInteger glyphIndex;
1260 for (glyphIndex = 0; glyphIndex < resultGlyphCount; glyphIndex++) {
1261 advanceIntoLine += advances[glyphIndex].width;
1262 }
1263 }
1264 }
1265 byteIndexInLine += bytesInThisRun;
1266 }
1267 }
1268 else if (NSMinY(lineRectInBoundsSpace) > NSMaxY(clip)) {
1269 break;
1270 }
1271 }
1272 FREE_ARRAY(glyphs);
1273 FREE_ARRAY(advances);
1274 }
1275
1276
1277 - (void)drawFocusRingWithClip:(NSRect)clip {
1278 USE(clip);
1279 [NSGraphicsContext saveGraphicsState];
1280 NSSetFocusRingStyle(NSFocusRingOnly);
1281 [[NSColor clearColor] set];
1282 NSRectFill([self bounds]);
1283 [NSGraphicsContext restoreGraphicsState];
1284 }
1285
1286 - (BOOL)shouldDrawCallouts {
1287 return _hftvflags.drawCallouts;
1288 }
1289
1290 - (void)setShouldDrawCallouts:(BOOL)val {
1291 _hftvflags.drawCallouts = val;
1292 [self setNeedsDisplay:YES];
1293 }
1294
1295 - (void)drawBookmarksWithClip:(NSRect)clip {
1296 if([self shouldDrawCallouts]) {
1297 /* Figure out which callouts we're going to draw */
1298 NSRect allCalloutsRect = NSZeroRect;
1299 NSMutableArray *localCallouts = [[NSMutableArray alloc] initWithCapacity:[callouts count]];
1300 FOREACH(HFRepresenterTextViewCallout *, callout, [callouts objectEnumerator]) {
1301 NSRect calloutRect = [callout rect];
1302 if (NSIntersectsRect(clip, calloutRect)) {
1303 [localCallouts addObject:callout];
1304 allCalloutsRect = NSUnionRect(allCalloutsRect, calloutRect);
1305 }
1306 }
1307 allCalloutsRect = NSIntersectionRect(allCalloutsRect, clip);
1308
1309 if ([localCallouts count]) {
1310 /* Draw shadows first */
1311 CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
1312 CGContextBeginTransparencyLayerWithRect(ctx, NSRectToCGRect(allCalloutsRect), NULL);
1313 FOREACH(HFRepresenterTextViewCallout *, callout, localCallouts) {
1314 [callout drawShadowWithClip:clip];
1315 }
1316 CGContextEndTransparencyLayer(ctx);
1317
1318 FOREACH(HFRepresenterTextViewCallout *, newCallout, localCallouts) {
1319 // NSRect rect = [callout rect];
1320 // [[NSColor greenColor] set];
1321 // NSFrameRect(rect);
1322 [newCallout drawWithClip:clip];
1323 }
1324 }
1325 [localCallouts release];
1326 }
1327 }
1328
1329 - (void)drawRect:(NSRect)clip {
1330 [[self backgroundColorForEmptySpace] set];
1331 NSRectFillUsingOperation(clip, NSCompositeSourceOver);
1332 BOOL antialias = [self shouldAntialias];
1333 CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort];
1334
1335 [[self.font screenFont] set];
1336
1337 if ([self showsFocusRing]) {
1338 NSWindow *window = [self window];
1339 if (self == [window firstResponder] && [window isKeyWindow]) {
1340 [self drawFocusRingWithClip:clip];
1341 }
1342 }
1343
1344 NSUInteger bytesPerLine = [self bytesPerLine];
1345 if (bytesPerLine == 0) return;
1346 NSUInteger byteCount = [_data length];
1347
1348 [self _drawDefaultLineBackgrounds:clip withLineHeight:[self lineHeight] maxLines:ll2l(HFRoundUpToNextMultipleSaturate(byteCount, bytesPerLine) / bytesPerLine)];
1349 [self drawSelectionIfNecessaryWithClip:clip];
1350
1351 NSColor *textColor = [NSColor textColor];
1352 [textColor set];
1353
1354 if (! antialias) {
1355 CGContextSaveGState(ctx);
1356 CGContextSetShouldAntialias(ctx, NO);
1357 }
1358 [self drawTextWithClip:clip restrictingToTextInRanges:nil];
1359 if (! antialias) {
1360 CGContextRestoreGState(ctx);
1361 }
1362
1363 // Vertical dividers only make sense in single byte mode.
1364 if ([self _effectiveBytesPerColumn] == 1) {
1365 [self drawVerticalGuideLines:clip];
1366 }
1367
1368 [self drawCaretIfNecessaryWithClip:clip];
1369
1370 [self drawBookmarksWithClip:clip];
1371 }
1372
1373 - (NSRect)furthestRectOnEdge:(NSRectEdge)edge forRange:(NSRange)byteRange {
1374 HFASSERT(edge == NSMinXEdge || edge == NSMaxXEdge || edge == NSMinYEdge || edge == NSMaxYEdge);
1375 const NSUInteger bytesPerLine = [self bytesPerLine];
1376 CGFloat lineHeight = [self lineHeight];
1377 CGFloat vertOffset = [self verticalOffset];
1378 NSUInteger firstLine = byteRange.location / bytesPerLine, lastLine = (NSMaxRange(byteRange) - 1) / bytesPerLine;
1379 NSRect result = NSZeroRect;
1380
1381 if (edge == NSMinYEdge || edge == NSMaxYEdge) {
1382 /* This is the top (MinY) or bottom (MaxY). We only have to look at one line. */
1383 NSUInteger lineIndex = (edge == NSMinYEdge ? firstLine : lastLine);
1384 NSRange lineRange = NSMakeRange(lineIndex * bytesPerLine, bytesPerLine);
1385 NSRange intersection = NSIntersectionRange(lineRange, byteRange);
1386 HFASSERT(intersection.length > 0);
1387 CGFloat yOrigin = (lineIndex - vertOffset) * lineHeight;
1388 CGFloat xStart = [self originForCharacterAtByteIndex:intersection.location].x;
1389 CGFloat xEnd = [self originForCharacterAtByteIndex:NSMaxRange(intersection) - 1].x + [self advancePerCharacter];
1390 result = NSMakeRect(xStart, yOrigin, xEnd - xStart, 0);
1391 }
1392 else {
1393 if (firstLine == lastLine) {
1394 /* We only need to consider this one line */
1395 NSRange lineRange = NSMakeRange(firstLine * bytesPerLine, bytesPerLine);
1396 NSRange intersection = NSIntersectionRange(lineRange, byteRange);
1397 HFASSERT(intersection.length > 0);
1398 CGFloat yOrigin = (firstLine - vertOffset) * lineHeight;
1399 CGFloat xCoord;
1400 if (edge == NSMinXEdge) {
1401 xCoord = [self originForCharacterAtByteIndex:intersection.location].x;
1402 }
1403 else {
1404 xCoord = [self originForCharacterAtByteIndex:NSMaxRange(intersection) - 1].x + [self advancePerCharacter];
1405 }
1406 result = NSMakeRect(xCoord, yOrigin, 0, lineHeight);
1407 }
1408 else {
1409 /* We have more than one line. If we are asking for the left edge, sum up the left edge of every line but the first, and handle the first specially. Likewise for the right edge (except handle the last specially) */
1410 BOOL includeFirstLine, includeLastLine;
1411 CGFloat xCoord;
1412 if (edge == NSMinXEdge) {
1413 /* Left edge, include the first line only if it starts at the beginning of the line or there's only one line */
1414 includeFirstLine = (byteRange.location % bytesPerLine == 0);
1415 includeLastLine = YES;
1416 xCoord = [self horizontalContainerInset];
1417 }
1418 else {
1419 /* Right edge, include the last line only if it starts at the beginning of the line or there's only one line */
1420 includeFirstLine = YES;
1421 includeLastLine = (NSMaxRange(byteRange) % bytesPerLine == 0);
1422 NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
1423 /* Don't add in space for the advance after the last column, hence subtract 1. */
1424 NSUInteger numColumns = (bytesPerColumn ? (bytesPerLine / bytesPerColumn - 1) : 0);
1425 xCoord = [self horizontalContainerInset] + ([self advancePerCharacter] * bytesPerLine / [self bytesPerCharacter]) + [self advanceBetweenColumns] * numColumns;
1426 }
1427 NSUInteger firstLineToInclude = (includeFirstLine ? firstLine : firstLine + 1), lastLineToInclude = (includeLastLine ? lastLine : lastLine - 1);
1428 result = NSMakeRect(xCoord, (firstLineToInclude - [self verticalOffset]) * lineHeight, 0, (lastLineToInclude - firstLineToInclude + 1) * lineHeight);
1429 }
1430 }
1431 return result;
1432 }
1433
1434 - (NSUInteger)availableLineCount {
1435 CGFloat result = (CGFloat)ceil(NSHeight([self bounds]) / [self lineHeight]);
1436 HFASSERT(result >= 0.);
1437 HFASSERT(result <= NSUIntegerMax);
1438 return (NSUInteger)result;
1439 }
1440
1441 - (double)maximumAvailableLinesForViewHeight:(CGFloat)viewHeight {
1442 return viewHeight / [self lineHeight];
1443 }
1444
1445 - (void)setFrameSize:(NSSize)size {
1446 NSUInteger currentBytesPerLine = [self bytesPerLine];
1447 double currentLineCount = [self maximumAvailableLinesForViewHeight:NSHeight([self bounds])];
1448 [super setFrameSize:size];
1449 NSUInteger newBytesPerLine = [self maximumBytesPerLineForViewWidth:size.width];
1450 double newLineCount = [self maximumAvailableLinesForViewHeight:NSHeight([self bounds])];
1451 HFControllerPropertyBits bits = 0;
1452 if (newBytesPerLine != currentBytesPerLine) bits |= (HFControllerBytesPerLine | HFControllerDisplayedLineRange);
1453 if (newLineCount != currentLineCount) bits |= HFControllerDisplayedLineRange;
1454 if (bits) [[self representer] representerChangedProperties:bits];
1455 }
1456
1457 - (CGFloat)advanceBetweenColumns {
1458 UNIMPLEMENTED();
1459 }
1460
1461 - (CGFloat)advancePerCharacter {
1462 UNIMPLEMENTED();
1463 }
1464
1465 - (CGFloat)advancePerColumn {
1466 NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
1467 if (bytesPerColumn == 0) {
1468 return 0;
1469 }
1470 else {
1471 return [self advancePerCharacter] * (bytesPerColumn / [self bytesPerCharacter]) + [self advanceBetweenColumns];
1472 }
1473 }
1474
1475 - (CGFloat)totalAdvanceForBytesInRange:(NSRange)range {
1476 if (range.length == 0) return 0;
1477 NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
1478 HFASSERT(bytesPerColumn == 0 || [self bytesPerLine] % bytesPerColumn == 0);
1479 CGFloat result = (range.length * [self advancePerCharacter] / [self bytesPerCharacter]) ;
1480 if (bytesPerColumn > 0) {
1481 NSUInteger numColumnSpaces = NSMaxRange(range) / bytesPerColumn - range.location / bytesPerColumn; //note that integer division does not distribute
1482 result += numColumnSpaces * [self advanceBetweenColumns];
1483 }
1484 return result;
1485 }
1486
1487 /* Returns the number of bytes in a character, e.g. if we are UTF-16 this would be 2. */
1488 - (NSUInteger)bytesPerCharacter {
1489 return 1;
1490 }
1491
1492 - (NSUInteger)maximumBytesPerLineForViewWidth:(CGFloat)viewWidth {
1493 CGFloat availableSpace = (CGFloat)(viewWidth - 2. * [self horizontalContainerInset]);
1494 NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn], bytesPerCharacter = [self bytesPerCharacter];
1495 if (bytesPerColumn == 0) {
1496 /* No columns */
1497 NSUInteger numChars = (NSUInteger)(availableSpace / [self advancePerCharacter]);
1498 /* Return it, except it's at least one character */
1499 return MAX(numChars, 1u) * bytesPerCharacter;
1500 }
1501 else {
1502 /* We have some columns */
1503 CGFloat advancePerColumn = [self advancePerColumn];
1504 //spaceRequiredForNColumns = N * (advancePerColumn) - spaceBetweenColumns
1505 CGFloat fractionalColumns = (availableSpace + [self advanceBetweenColumns]) / advancePerColumn;
1506 NSUInteger columnCount = (NSUInteger)fmax(1., HFFloor(fractionalColumns));
1507 return columnCount * bytesPerColumn;
1508 }
1509 }
1510
1511
1512 - (CGFloat)minimumViewWidthForBytesPerLine:(NSUInteger)bytesPerLine {
1513 HFASSERT(bytesPerLine > 0);
1514 NSUInteger bytesPerColumn = [self _effectiveBytesPerColumn];
1515 CGFloat result;
1516 if (bytesPerColumn == 0) {
1517 result = (CGFloat)((2. * [self horizontalContainerInset]) + [self advancePerCharacter] * (bytesPerLine / [self bytesPerCharacter]));
1518 }
1519 else {
1520 HFASSERT(bytesPerLine % bytesPerColumn == 0);
1521 result = (CGFloat)((2. * [self horizontalContainerInset]) + [self advancePerColumn] * (bytesPerLine / bytesPerColumn) - [self advanceBetweenColumns]);
1522 }
1523 return result;
1524 }
1525
1526 - (BOOL)isEditable {
1527 return _hftvflags.editable;
1528 }
1529
1530 - (void)setEditable:(BOOL)val {
1531 if (val != _hftvflags.editable) {
1532 _hftvflags.editable = val;
1533 [self _updateCaretTimer];
1534 }
1535 }
1536
1537 - (BOOL)shouldAntialias {
1538 return _hftvflags.antialias;
1539 }
1540
1541 - (void)setShouldAntialias:(BOOL)val {
1542 _hftvflags.antialias = !!val;
1543 [self setNeedsDisplay:YES];
1544 }
1545
1546 - (BOOL)behavesAsTextField {
1547 return [[self representer] behavesAsTextField];
1548 }
1549
1550 - (BOOL)showsFocusRing {
1551 return [[self representer] behavesAsTextField];
1552 }
1553
1554 - (BOOL)isWithinMouseDown {
1555 return _hftvflags.withinMouseDown;
1556 }
1557
1558 - (void)_windowDidChangeKeyStatus:(NSNotification *)note {
1559 USE(note);
1560 [self _updateCaretTimer];
1561 if ([[note name] isEqualToString:NSWindowDidBecomeKeyNotification]) {
1562 [self _forceCaretOnIfHasCaretTimer];
1563 }
1564 if ([self showsFocusRing] && self == [[self window] firstResponder]) {
1565 [[self superview] setNeedsDisplayInRect:NSInsetRect([self frame], -6, -6)];
1566 }
1567 [self setNeedsDisplay:YES];
1568 }
1569
1570 - (void)viewDidMoveToWindow {
1571 [self _updateCaretTimer];
1572 HFRegisterViewForWindowAppearanceChanges(self, @selector(_windowDidChangeKeyStatus:), ! _hftvflags.registeredForAppNotifications);
1573 _hftvflags.registeredForAppNotifications = YES;
1574 [super viewDidMoveToWindow];
1575 }
1576
1577 - (void)viewWillMoveToWindow:(NSWindow *)newWindow {
1578 HFUnregisterViewForWindowAppearanceChanges(self, NO /* appToo */);
1579 [super viewWillMoveToWindow:newWindow];
1580 }
1581
1582 /* Computes the character at the given index for selection, properly handling the case where the point is outside the bounds */
1583 - (NSUInteger)characterAtPointForSelection:(NSPoint)point {
1584 NSPoint mungedPoint = point;
1585 // shift us right by half an advance so that we trigger at the midpoint of each character, rather than at the x origin
1586 mungedPoint.x += [self advancePerCharacter] / (CGFloat)2.;
1587 // make sure we're inside the bounds
1588 const NSRect bounds = [self bounds];
1589 mungedPoint.x = HFMax(NSMinX(bounds), mungedPoint.x);
1590 mungedPoint.x = HFMin(NSMaxX(bounds), mungedPoint.x);
1591 mungedPoint.y = HFMax(NSMinY(bounds), mungedPoint.y);
1592 mungedPoint.y = HFMin(NSMaxY(bounds), mungedPoint.y);
1593 return [self indexOfCharacterAtPoint:mungedPoint];
1594 }
1595
1596 - (NSUInteger)maximumCharacterIndex {
1597 //returns the maximum character index that the selection may lie on. It is one beyond the last byte index, to represent the cursor at the end of the document.
1598 return [[self data] length] / [self bytesPerCharacter];
1599 }
1600
1601 - (void)mouseDown:(NSEvent *)event {
1602 HFASSERT(_hftvflags.withinMouseDown == 0);
1603 _hftvflags.withinMouseDown = 1;
1604 [self _forceCaretOnIfHasCaretTimer];
1605 NSPoint mouseDownLocation = [self convertPoint:[event locationInWindow] fromView:nil];
1606 NSUInteger characterIndex = [self characterAtPointForSelection:mouseDownLocation];
1607
1608 characterIndex = MIN(characterIndex, [self maximumCharacterIndex]); //characterIndex may be one beyond the last index, to represent the cursor at the end of the document
1609 [[self representer] beginSelectionWithEvent:event forCharacterIndex:characterIndex];
1610
1611 /* Drive the event loop in event tracking mode until we're done */
1612 HFASSERT(_hftvflags.receivedMouseUp == NO); //paranoia - detect any weird recursive invocations
1613 NSDate *endDate = [NSDate distantFuture];
1614
1615 /* Start periodic events for autoscroll */
1616 [NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.05];
1617
1618 NSPoint autoscrollLocation = mouseDownLocation;
1619 while (! _hftvflags.receivedMouseUp) {
1620 @autoreleasepool {
1621 NSEvent *ev = [NSApp nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask untilDate:endDate inMode:NSEventTrackingRunLoopMode dequeue:YES];
1622
1623 if ([ev type] == NSPeriodic) {
1624 // autoscroll if drag is out of view bounds
1625 CGFloat amountToScroll = 0;
1626 NSRect bounds = [self bounds];
1627 if (autoscrollLocation.y < NSMinY(bounds)) {
1628 amountToScroll = (autoscrollLocation.y - NSMinY(bounds)) / [self lineHeight];
1629 }
1630 else if (autoscrollLocation.y > NSMaxY(bounds)) {
1631 amountToScroll = (autoscrollLocation.y - NSMaxY(bounds)) / [self lineHeight];
1632 }
1633 if (amountToScroll != 0.) {
1634 [[[self representer] controller] scrollByLines:amountToScroll];
1635 characterIndex = [self characterAtPointForSelection:autoscrollLocation];
1636 characterIndex = MIN(characterIndex, [self maximumCharacterIndex]);
1637 [[self representer] continueSelectionWithEvent:ev forCharacterIndex:characterIndex];
1638 }
1639 }
1640 else if ([ev type] == NSLeftMouseDragged) {
1641 autoscrollLocation = [self convertPoint:[ev locationInWindow] fromView:nil];
1642 }
1643
1644 [NSApp sendEvent:ev];
1645 } // @autoreleasepool
1646 }
1647
1648 [NSEvent stopPeriodicEvents];
1649
1650 _hftvflags.receivedMouseUp = NO;
1651 _hftvflags.withinMouseDown = 0;
1652 }
1653
1654 - (void)mouseDragged:(NSEvent *)event {
1655 if (! _hftvflags.withinMouseDown) return;
1656 NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil];
1657 NSUInteger characterIndex = [self characterAtPointForSelection:location];
1658 characterIndex = MIN(characterIndex, [self maximumCharacterIndex]);
1659 [[self representer] continueSelectionWithEvent:event forCharacterIndex:characterIndex];
1660 }
1661
1662 - (void)mouseUp:(NSEvent *)event {
1663 if (! _hftvflags.withinMouseDown) return;
1664 NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil];
1665 NSUInteger characterIndex = [self characterAtPointForSelection:location];
1666 characterIndex = MIN(characterIndex, [self maximumCharacterIndex]);
1667 [[self representer] endSelectionWithEvent:event forCharacterIndex:characterIndex];
1668 _hftvflags.receivedMouseUp = YES;
1669 }
1670
1671 - (void)keyDown:(NSEvent *)event {
1672 HFASSERT(event != NULL);
1673 [self interpretKeyEvents:@[event]];
1674 }
1675
1676 - (void)scrollWheel:(NSEvent *)event {
1677 [[self representer] scrollWheel:event];
1678 }
1679
1680 - (void)insertText:(id)string {
1681 if (! [self isEditable]) {
1682 NSBeep();
1683 }
1684 else {
1685 if ([string isKindOfClass:[NSAttributedString class]]) string = [string string];
1686 [NSCursor setHiddenUntilMouseMoves:YES];
1687 [[self representer] insertText:string];
1688 }
1689 }
1690
1691 - (BOOL)handleCommand:(SEL)sel {
1692 if (sel == @selector(insertTabIgnoringFieldEditor:)) {
1693 [self insertText:@"\t"];
1694 }
1695 else if ([self respondsToSelector:sel]) {
1696 [self performSelector:sel withObject:nil];
1697 }
1698 else {
1699 return NO;
1700 }
1701 return YES;
1702 }
1703
1704 - (void)doCommandBySelector:(SEL)sel {
1705 HFRepresenter *rep = [self representer];
1706 // NSLog(@"%s%s", _cmd, sel);
1707 if ([self handleCommand:sel]) {
1708 /* Nothing to do */
1709 }
1710 else if ([rep respondsToSelector:sel]) {
1711 [rep performSelector:sel withObject:self];
1712 }
1713 else {
1714 [super doCommandBySelector:sel];
1715 }
1716 }
1717
1718 - (IBAction)selectAll:sender {
1719 [[self representer] selectAll:sender];
1720 }
1721
1722 /* Indicates whether at least one byte is selected */
1723 - (BOOL)_selectionIsNonEmpty {
1724 NSArray *selection = [[[self representer] controller] selectedContentsRanges];
1725 FOREACH(HFRangeWrapper *, rangeWrapper, selection) {
1726 if ([rangeWrapper HFRange].length > 0) return YES;
1727 }
1728 return NO;
1729 }
1730
1731 - (SEL)_pasteboardOwnerStringTypeWritingSelector {
1732 UNIMPLEMENTED();
1733 }
1734
1735 - (void)paste:sender {
1736 if (! [self isEditable]) {
1737 NSBeep();
1738 }
1739 else {
1740 USE(sender);
1741 [[self representer] pasteBytesFromPasteboard:[NSPasteboard generalPasteboard]];
1742 }
1743 }
1744
1745 - (void)copy:sender {
1746 USE(sender);
1747 [[self representer] copySelectedBytesToPasteboard:[NSPasteboard generalPasteboard]];
1748 }
1749
1750 - (void)cut:sender {
1751 USE(sender);
1752 [[self representer] cutSelectedBytesToPasteboard:[NSPasteboard generalPasteboard]];
1753 }
1754
1755 - (BOOL)validateMenuItem:(NSMenuItem *)item {
1756 SEL action = [item action];
1757 if (action == @selector(selectAll:)) return YES;
1758 else if (action == @selector(cut:)) return [[self representer] canCut];
1759 else if (action == @selector(copy:)) return [self _selectionIsNonEmpty];
1760 else if (action == @selector(paste:)) return [[self representer] canPasteFromPasteboard:[NSPasteboard generalPasteboard]];
1761 else return YES;
1762 }
1763
1764 /* Compatibility with Sonoma */
1765 + (bool)clipsToBounds
1766 {
1767 return true;
1768 }
1769
1770 @end
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.