libs-gui/Source/NSLayoutManager.m

3140 lines
88 KiB
Mathematica
Raw Permalink Normal View History

/*
NSLayoutManager.m
Copyright (C) 1999, 2002, 2003 Free Software Foundation, Inc.
Author: Alexander Malmberg <alexander@malmberg.org>
Date: November 2002 - February 2003
Parts based on the old NSLayoutManager.m:
Author: Jonathan Gapen <jagapen@smithlab.chem.wisc.edu>
Date: July 1999
Author: Michael Hanni <mhanni@sprintmail.com>
Date: August 1999
Author: Richard Frith-Macdonald <rfm@gnu.org>
Date: January 2001
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
/*
TODO: document exact requirements on typesetting for this class
Roughly:
Line frag rects arranged in lines in which all line frag rects have same
y-origin and height. Lines must not overlap vertically, and must be arranged
with strictly increasing y-origin. Line frag rects go left->right, as do
points inside line frag rects.
"Nominally spaced", to this layout manager, is described at:
http://wiki.gnustep.org/index.php/NominallySpacedGlyphs
Lines are laid out as one unit. Ie. we never do layout for only a part of a
line, or invalidate only some line frags in a line.
Also, we assume that the limit of context on layout is the previous line.
Thus, when we invalidate layout, we invalidate all lines with invalidated
characters, and the line before the first invalidated line, and
soft-invalidate everything after the last invalidated line.
Consider:
|... |
|foo bar zot |
|abcdefghij |
|... |
If we insert a space between the 'a' and the 'b' in "abcd...", the correct
result is:
|... |
|foo bar zot a |
|bcdefghij |
|... |
and to get this, we must invalidate the previous line.
TODO: This is an important assumption, and the typesetter needs to make
sure that it holds. I'm not entirely convinced that it holds for standard
latin-text layout, but I haven't been able to come up with any
counter-examples. If it turns out not to hold, we'll have to fix
invalidation here (invalidate the entire paragraph? not good for
performance, but correctness is more important), or change the typesetter
behavior.
Another assumption is that each text container will contain at least one
line frag (unless there are no more glyphs to typeset).
TODO: this doesn't hold for containers with 0 height or 0 width. need to
test. rare case, though
TODO: We often need to deal with the case where a glyph can't be typeset
(because there's nowhere to typeset it, eg. all text containers are full).
Need to figure out how to handle it.
TODO: Don't do linear searches through the line frags if avoidable. Some
cases are considerably more important than others, and should be fixed
first. Remaining cases, highest priority first:
-glyphIndexForPoint:inTextContainer:fractionOfDistanceThroughGlyph:
Used when selecting with the mouse, and called for every event.
-characterIndexMoving:fromCharacterIndex:originalCharacterIndex:distance:
Keyboard insertion point movement. Performance isn't important.
*/
#include <math.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSException.h>
#import <Foundation/NSValue.h>
#import "AppKit/NSAttributedString.h"
#import "AppKit/NSBezierPath.h"
#import "AppKit/NSColor.h"
#import "AppKit/NSImage.h"
#import "AppKit/NSKeyValueBinding.h"
#import "AppKit/NSLayoutManager.h"
#import "AppKit/NSParagraphStyle.h"
#import "AppKit/NSRulerMarker.h"
#import "AppKit/NSTextContainer.h"
#import "AppKit/NSTextStorage.h"
#import "AppKit/NSWindow.h"
#import "AppKit/DPSOperators.h"
#import "GNUstepGUI/GSLayoutManager_internal.h"
#import "GSBindingHelpers.h"
@interface NSLayoutManager (spelling)
-(void) _drawSpellingState: (NSInteger)spellingState
forGylphRange: (NSRange)range
lineFragmentRect: (NSRect)fragmentRect
lineFragmentGlyphRange: (NSRange)fragmentGlyphRange
containerOrigin: (NSPoint)containerOrigin;
@end
@interface NSLayoutManager (LayoutHelpers)
-(void) _doLayoutToContainer: (int)cindex point: (NSPoint)p;
@end
@implementation NSLayoutManager (LayoutHelpers)
-(void) _doLayoutToContainer: (int)cindex point: (NSPoint)p
{
[self _doLayout];
}
@end
/**
* Temporary attributes are implemented using an NSMutableAttributedString
* stored in the _temporaryAttributes ivar. We only care about this attributed
* string's attributes, not its characters, so to save space,
* _temporaryAttributes is initialized with an instance of the following
* NSMutableString subclass, which doesn't store any character data.
*
* WARNING ... it's an unofficial implementation detail that the
* GSMutableAttributedString class creates it's internal storage by taking
* a mutable copy of its initialisation argument, and we have a comment in
* the source to warn about it. If we change the behavior here, we should
* remove the warning comment from the source in base.
*/
@interface GSDummyMutableString : NSMutableString
{
NSUInteger _length;
}
- (id)initWithLength: (NSUInteger)aLength;
@end
/* Helper for searching for the line frag of a glyph. */
#define LINEFRAG_FOR_GLYPH(glyph) \
do { \
int lo, hi, mid; \
\
lf = tc->linefrags; \
for (lo = 0, hi = tc->num_linefrags - 1; lo < hi;) \
{ \
mid = (lo + hi) / 2; \
if (lf[mid].pos > glyph) \
hi = mid - 1; \
else if (lf[mid].pos + lf[mid].length <= glyph) \
lo = mid + 1; \
else \
lo = hi = mid; \
} \
lf = &lf[lo]; \
i = lo; \
} while (0)
@implementation NSLayoutManager (layout)
-(NSPoint) locationForGlyphAtIndex: (NSUInteger)glyphIndex
{
NSRange r;
NSPoint p;
NSUInteger i;
r = [self rangeOfNominallySpacedGlyphsContainingIndex: glyphIndex
startLocation: &p];
if (r.location == NSNotFound)
{
/* The glyph hasn't been typeset yet, probably because there isn't
enough space in the text containers to fit it. */
return NSMakePoint(0, 0);
}
for (i = r.location; i < glyphIndex; i++)
{
p.x += [self advancementForGlyphAtIndex: i].width;
}
return p;
}
-(void) textContainerChangedTextView: (NSTextContainer *)aContainer
{
/* TODO: what do we do here? invalidate the displayed range for that
container? necessary? */
int i;
/* NSTextContainer will send the necessary messages to update the text
view that was disconnected from us. */
for (i = 0; i < num_textcontainers; i++)
{
[[textcontainers[i].textContainer textView] _updateMultipleTextViews];
if (textcontainers[i].textContainer == aContainer)
{
[[aContainer textView] setNeedsDisplay: YES];
}
}
}
-(NSRect *) rectArrayForGlyphRange: (NSRange)glyphRange
withinSelectedGlyphRange: (NSRange)selGlyphRange
inTextContainer: (NSTextContainer *)container
rectCount: (NSUInteger *)rectCount
{
NSUInteger last;
int i;
textcontainer_t *tc;
linefrag_t *lf;
int num_rects;
float x0, x1;
NSRect r;
*rectCount = 0;
for (tc = textcontainers, i = 0; i < num_textcontainers; i++, tc++)
if (tc->textContainer == container)
break;
//printf("container %i %@, %i+%i\n",i,tc->textContainer,tc->pos,tc->length);
[self _doLayoutToGlyph: NSMaxRange(glyphRange) - 1];
//printf(" now %i+%i\n",tc->pos,tc->length);
if (i == num_textcontainers)
{
if (i == num_textcontainers)
NSLog(@"%s: invalid text container", __PRETTY_FUNCTION__);
return NULL;
}
/* Silently clamp range to the text container.
TODO: is this good? */
if (tc->pos > glyphRange.location)
{
if (tc->pos > NSMaxRange(glyphRange))
return NULL;
glyphRange.length = NSMaxRange(glyphRange) - tc->pos;
glyphRange.location = tc->pos;
}
if (tc->pos + tc->length < NSMaxRange(glyphRange))
{
if (tc->pos + tc->length < glyphRange.location)
return NULL;
glyphRange.length = tc->pos + tc->length - glyphRange.location;
}
if (!glyphRange.length)
{
return NULL;
}
last = NSMaxRange(glyphRange);
num_rects = 0;
LINEFRAG_FOR_GLYPH(glyphRange.location);
/* Main loop. Work through all line frag rects and build the array of
rects. */
while (1)
{
/* Determine the starting x-coordinate for this line frag rect. */
if (lf->pos <= glyphRange.location)
{
/*
The start index is inside the line frag rect, so we need to
search through it to find the exact starting location.
*/
unsigned int i;
int j;
linefrag_point_t *lp;
glyph_run_t *r;
unsigned int gpos, cpos;
for (j = 0, lp = lf->points; j < lf->num_points; j++, lp++)
if (lp->pos + lp->length > glyphRange.location)
break;
NSAssert(j < lf->num_points, @"can't find starting point of glyph");
x0 = lp->p.x + lf->rect.origin.x;
r = run_for_glyph_index(lp->pos, glyphs, &gpos, &cpos);
i = lp->pos - gpos;
while (i + gpos < glyphRange.location)
{
if (!r->glyphs[i].isNotShown && r->glyphs[i].g &&
r->glyphs[i].g != NSControlGlyph)
{
x0 += r->glyphs[i].advancement.width;
}
GLYPH_STEP_FORWARD(r, i, gpos, cpos)
}
}
else
{
/*
The start index was before the this line frag, so the starting
x-coordinate is the left edge of the line frag.
*/
x0 = NSMinX(lf->rect);
}
/* Determine the end x-coordinate for this line frag. */
if (lf->pos + lf->length > last)
{
/*
The end index is inside the line frag, so we need to find the
exact end location.
*/
NSUInteger i;
int j;
linefrag_point_t *lp;
glyph_run_t *r;
unsigned int gpos, cpos;
/*
At this point there is a glyph in our range that is in this
line frag rect. If we're on the first line frag rect, it's
trivially true. If not, the check before the lf++; ensures it.
*/
for (j = 0, lp = lf->points; j < lf->num_points; j++, lp++)
if (lp->pos + lp->length > last)
break;
NSAssert(j < lf->num_points, @"can't find starting point of glyph");
x1 = lp->p.x + lf->rect.origin.x;
r = run_for_glyph_index(lp->pos, glyphs, &gpos, &cpos);
i = lp->pos - gpos;
while (i + gpos < last)
{
if (!r->glyphs[i].isNotShown && r->glyphs[i].g &&
r->glyphs[i].g != NSControlGlyph)
{
x1 += r->glyphs[i].advancement.width;
}
GLYPH_STEP_FORWARD(r, i, gpos, cpos)
}
}
else if (lf->pos + lf->length == last)
{
/*
The range ends in the last glyph of the line frag, so the end
x-coordinate is the right edge of this glyph. This egde is
equal to the right edge of the line fragment's rectangle if
the glyph is invisible or a control glyph, e.g., a newline,
and to the right edge of the line fragment's used rectangle
otherwise.
*/
NSUInteger i;
glyph_run_t *r;
unsigned int gpos, cpos;
r = run_for_glyph_index(last - 1, glyphs, &gpos, &cpos);
i = (last - 1) - gpos;
if (!r->glyphs[i].isNotShown && r->glyphs[i].g &&
r->glyphs[i].g != NSControlGlyph)
x1 = NSMaxX(lf->used_rect);
else
x1 = NSMaxX(lf->rect);
}
else
{
/*
The range continues beyond the end of the line frag, so the end
x-coordinate is the right edge of the line frag.
*/
x1 = NSMaxX(lf->rect);
}
/*
We have the start and end x-coordinates, and use the height of the
line frag for the y-coordinates.
*/
r = NSMakeRect(x0, lf->rect.origin.y, x1 - x0, lf->rect.size.height);
/*
As an optimization of the rectangle array, we check if the previous
rectangle had the same x-coordinates as the new rectangle and touches
it vertically. If so, we combine them.
*/
if (num_rects &&
r.origin.x == rect_array[num_rects - 1].origin.x &&
r.size.width == rect_array[num_rects - 1].size.width &&
r.origin.y == NSMaxY(rect_array[num_rects - 1]))
{
rect_array[num_rects - 1].size.height += r.size.height;
}
else
{
if (num_rects == rect_array_size)
{
rect_array_size += 4;
rect_array = realloc(rect_array, sizeof(NSRect) * rect_array_size);
}
rect_array[num_rects++] = r;
}
if (lf->pos + lf->length >= last)
break;
lf++;
}
*rectCount = num_rects;
return rect_array;
}
-(NSRect *) rectArrayForCharacterRange: (NSRange)charRange
withinSelectedCharacterRange: (NSRange)selCharRange
inTextContainer: (NSTextContainer *)container
rectCount: (NSUInteger *)rectCount
{
NSRange r1, r2;
// FIXME: should accept {NSNotFound, 0} for selCharRange to ignore that parameter
/* TODO: we can actually do better than this by using the insertion point
positioning behavior */
r1 = [self glyphRangeForCharacterRange: charRange
actualCharacterRange: NULL];
r2 = [self glyphRangeForCharacterRange: selCharRange
actualCharacterRange: NULL];
return [self rectArrayForGlyphRange: r1
withinSelectedGlyphRange: r2
inTextContainer: container
rectCount: rectCount];
}
-(NSRect) boundingRectForGlyphRange: (NSRange)glyphRange
inTextContainer: (NSTextContainer *)aTextContainer
{
NSRect *r;
NSRect result;
NSUInteger i, c;
/* TODO: This isn't correct. Need to handle glyphs that extend outside the
line frag rect. */
r = [self rectArrayForGlyphRange: glyphRange
withinSelectedGlyphRange: NSMakeRange(NSNotFound, 0)
inTextContainer: aTextContainer
rectCount: &c];
if (!c)
return NSZeroRect;
result = r[0];
for (r++, i = 1; i < c; i++, r++)
result = NSUnionRect(result, *r);
return result;
}
-(NSRange) glyphRangeForBoundingRect: (NSRect)bounds
inTextContainer: (NSTextContainer *)container
{
int i;
unsigned int j;
int low, high, mid;
textcontainer_t *tc;
linefrag_t *lf;
NSRange range;
for (tc = textcontainers, i = 0; i < num_textcontainers; i++, tc++)
if (tc->textContainer == container)
break;
if (i == num_textcontainers)
{
NSLog(@"%s: invalid text container", __PRETTY_FUNCTION__);
return NSMakeRange(0, 0);
}
[self _doLayoutToContainer: i
point: NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))];
tc = textcontainers + i;
if (!tc->num_linefrags)
return NSMakeRange(0, 0);
/* Find first glyph in bounds. */
/* Find right "line", ie. the first "line" not above bounds. */
for (low = 0, high = tc->num_linefrags - 1; low < high;)
{
mid = (low + high) / 2;
lf = &tc->linefrags[mid];
if (NSMaxY(lf->rect) > NSMinY(bounds))
{
high = mid;
}
else
{
low = mid + 1;
}
}
i = low;
lf = &tc->linefrags[i];
if (NSMaxY(lf->rect) < NSMinY(bounds))
{
return NSMakeRange(0, 0);
}
/* Scan to first line frag intersecting bounds horizontally. */
while (i < tc->num_linefrags - 1 &&
NSMinY(lf[0].rect) == NSMinY(lf[1].rect) &&
NSMaxX(lf[0].rect) < NSMinX(bounds))
i++, lf++;
/* TODO: find proper position in line frag rect */
range.location = lf->pos;
/* Find last glyph in bounds. */
/* Find right "line", ie. last "line" not below bounds. */
for (low = 0, high = tc->num_linefrags - 1; low < high;)
{
mid = (low + high) / 2;
lf = &tc->linefrags[mid];
if (NSMinY(lf->rect) > NSMaxY(bounds))
{
high = mid;
}
else
{
low = mid + 1;
}
}
i = low;
lf = &tc->linefrags[i];
if (i && NSMinY(lf->rect) > NSMaxY(bounds))
i--, lf--;
if (NSMinY(lf->rect) > NSMaxY(bounds))
{
return NSMakeRange(0, 0);
}
/* Scan to last line frag intersecting bounds horizontally. */
while (i > 0 &&
NSMinY(lf[0].rect) == NSMinY(lf[-1].rect) &&
NSMinX(lf[-1].rect) > NSMaxX(bounds))
i--, lf--;
/* TODO: find proper position in line frag rect */
j = lf->pos + lf->length;
if (j <= range.location)
{
return NSMakeRange(0, 0);
}
range.length = j - range.location;
return range;
}
-(NSRange) glyphRangeForBoundingRectWithoutAdditionalLayout: (NSRect)bounds
inTextContainer: (NSTextContainer *)container
{
/* TODO: this should be the same as
-glyphRangeForBoundingRect:inTextContainer: but without the _doLayout...
call.
In other words, it returns the range of glyphs in the rect that have
already been laid out.
*/
return [self glyphRangeForBoundingRect: bounds
inTextContainer: container];
}
-(unsigned int) glyphIndexForPoint: (NSPoint)aPoint
inTextContainer: (NSTextContainer *)aTextContainer
{
return [self glyphIndexForPoint: aPoint
inTextContainer: aTextContainer
fractionOfDistanceThroughGlyph: NULL];
}
/*
TODO: decide on behavior wrt. invisible glyphs and pointer far away from
anything visible
*/
-(NSUInteger) glyphIndexForPoint: (NSPoint)point
inTextContainer: (NSTextContainer *)container
fractionOfDistanceThroughGlyph: (CGFloat *)partialFraction
{
int i;
textcontainer_t *tc;
linefrag_t *lf;
linefrag_point_t *lp;
CGFloat dummy;
if (!partialFraction)
partialFraction = &dummy;
for (tc = textcontainers, i = 0; i < num_textcontainers; i++, tc++)
if (tc->textContainer == container)
break;
if (i == num_textcontainers)
{
NSLog(@"%s: invalid text container", __PRETTY_FUNCTION__);
return NSNotFound;
}
[self _doLayoutToContainer: i point: point];
tc = textcontainers + i;
/* Find the line frag rect that contains the point, and handle the case
where the point isn't inside a line frag rect. */
for (i = 0, lf = tc->linefrags; i < tc->num_linefrags; i++, lf++)
{
/* The point is inside a rect; we're done. */
if (NSPointInRect(point, lf->rect))
break;
/* If the current line frag rect is below the point, the point must
be between the line with the current line frag rect and the line
with the previous line frag rect. */
if (NSMinY(lf->rect) > point.y)
{
/* If this is not the first line frag rect in the text container,
we consider the point to be after the last glyph on the previous
line. Otherwise, we consider it to be before the first glyph on
the current line. */
if (i > 0)
{
*partialFraction = 1.0;
if (lf->pos == 0)
{
return NSNotFound;
}
else
{
return lf->pos - 1;
}
}
else
{
*partialFraction = 0.0;
return lf->pos;
}
}
/* We know that NSMinY(lf->rect) <= point.y. If the point is on the
current line and to the left of the current line frag rect, we
consider the point to be before the first glyph in the current line
frag rect.
(This will happen if the point is between two line frag rects, or
before the first line frag rect. If the point is to the right of the
current line frag rect, it will be inside a subsequent line frag rect
on this line, or to the left of one, which will be handled by the here
or by the first check in the loop, or it will be after all line frag
rects on the line, which will be detected and handled as a 'between
two lines' case, or by the 'after all line frags' code below.)
*/
if (NSMaxY(lf->rect) >= point.y && NSMinX(lf->rect) > point.x)
{
*partialFraction = 0.0;
return lf->pos;
}
}
/* Point is after all line frags. */
if (i == tc->num_linefrags)
{
*partialFraction = 1.0;
/* TODO: this should return the correct thing even if the container is empty */
if (tc->pos + tc->length == 0)
{
return NSNotFound;
}
else
{
return tc->pos + tc->length - 1;
}
}
/* only interested in x from here on */
point.x -= lf->rect.origin.x;
/* scan to the first point beyond the target */
for (i = 0, lp = lf->points; i < lf->num_points; i++, lp++)
if (lp->p.x > point.x)
break;
if (!i)
{
/* Before the first glyph on the line. */
/* TODO: what if it isn't shown? */
*partialFraction = 0;
return lp->pos;
}
else
{
/* There are points in this line frag before the point we're looking
for. */
float cur, prev, next;
glyph_run_t *r;
unsigned int glyph_pos, char_pos, last_visible;
unsigned j;
if (i < lf->num_points)
next = lp->p.x;
else
next = NSMinX(lf->rect);
lp--; /* Valid since we checked for !i above. */
r = run_for_glyph_index(lp->pos, glyphs, &glyph_pos, &char_pos);
prev = lp->p.x;
last_visible = lf->pos;
for (j = lp->pos - glyph_pos; j + glyph_pos < lp->pos + lp->length;)
{
// Don't ignore invisble glyphs.
// if (r->glyphs[j].isNotShown || r->glyphs[j].g == NSControlGlyph ||
if (!r->glyphs[j].g)
{
GLYPH_STEP_FORWARD(r, j, glyph_pos, char_pos)
continue;
}
last_visible = j + glyph_pos;
cur = prev + r->glyphs[j].advancement.width;
if (j + glyph_pos + 1 == lp->pos + lp->length && next > cur)
cur = next;
if (cur >= point.x)
{
*partialFraction = (point.x - prev) / (cur - prev);
if (*partialFraction < 0)
*partialFraction = 0;
return j + glyph_pos;
}
prev = cur;
GLYPH_STEP_FORWARD(r, j, glyph_pos, char_pos)
}
*partialFraction = 1;
return last_visible;
}
}
/*** Insertion point positioning and movement. ***/
/*
Determines at which glyph, and how far through it, the insertion point
should be placed for a certain character index.
*/
-(unsigned int) _glyphIndexForCharacterIndex: (unsigned int)cindex
fractionThrough: (float *)fraction
{
if (cindex == [[_textStorage string] length])
{
*fraction = 0.0;
return (unsigned int)-1;
}
else
{
NSRange glyphRange, charRange;
unsigned int glyph_index;
float fraction_through;
glyphRange = [self glyphRangeForCharacterRange: NSMakeRange(cindex, 1)
actualCharacterRange: &charRange];
/*
Deal with composite characters and ligatures.
We determine how far through the character range this character is a
part of the character is, and then determine the glyph index and
fraction that is the same distance through the glyph range it is
mapped to.
(This gives good behavior when dealing with ligatures, at least.)
Eg. if the character index is at character 3 in a 5 character range,
we are 3/5=0.6 through the entire character range. If this range was
mapped to 4 glyphs, we get 0.6*4=2.4, so the glyph index is 2 and
the fraction is 0.4.
*/
fraction_through = (cindex - charRange.location) / (float)charRange.length;
fraction_through *= glyphRange.length;
glyph_index = glyphRange.location + floor(fraction_through);
fraction_through -= floor(fraction_through);
*fraction = fraction_through;
return glyph_index;
}
}
/*
Note: other methods rely a lot on the fact that the rectangle returned here
has the same y origin and height as the line frag rect it is in.
*/
-(NSRect) _insertionPointRectForCharacterIndex: (unsigned int)cindex
textContainer: (int *)textContainer
{
int i;
textcontainer_t *tc;
linefrag_t *lf;
float x0, x1;
NSRect r;
unsigned int glyph_index;
float fraction_through;
glyph_index = [self _glyphIndexForCharacterIndex: cindex
fractionThrough: &fraction_through];
if (glyph_index == (unsigned int)-1)
{
/* Need complete layout information. */
[self _doLayout];
if (extra_textcontainer)
{
for (tc = textcontainers, i = 0; i < num_textcontainers; i++, tc++)
if (tc->textContainer == extra_textcontainer)
break;
NSAssert(i < num_textcontainers, @"invalid extraTextContainer");
*textContainer = i;
r = extra_rect;
r.size.width = 1;
return r;
}
glyph_index = [self numberOfGlyphs] - 1;
if (glyph_index == (unsigned int)-1)
{ /* No information is available. Get default font height. */
NSFont *f = [_typingAttributes objectForKey: NSFontAttributeName];
/* will be -1 if there are no text containers */
*textContainer = num_textcontainers - 1;
r = NSMakeRect(0, 0, 1, [f boundingRectForFont].size.height);
if (num_textcontainers > 0)
{
NSParagraphStyle *paragraph = [_typingAttributes objectForKey: NSParagraphStyleAttributeName];
NSTextAlignment alignment = [paragraph alignment];
tc = textcontainers + num_textcontainers - 1;
r.origin.x += [tc->textContainer lineFragmentPadding];
// Apply left/right/center justification...
if (alignment == NSRightTextAlignment)
{
r.origin.x += [tc->textContainer containerSize].width;
}
else if (alignment == NSCenterTextAlignment)
{
r.origin.x += [tc->textContainer containerSize].width / 2;
}
}
return r;
}
fraction_through = 1.0;
}
else
[self _doLayoutToGlyph: glyph_index];
for (tc = textcontainers, i = 0; i < num_textcontainers; i++, tc++)
if (tc->pos + tc->length > glyph_index)
break;
if (i == num_textcontainers)
{
*textContainer = -1;
return NSZeroRect;
}
*textContainer = i;
LINEFRAG_FOR_GLYPH(glyph_index);
/* Special case if we are asked for the insertion point rectangle at the
end of text, since the standard code yields an incorrect result if the
last line fragment ends with an invisible character (e.g., a tab).
Note that fraction_through is always less than 1 except when
-_glyphIndexForCharacterIndex:fractionThrough: is called for
cindex == [_textStorage length], in which case we set it to 1. */
if (fraction_through == 1.0)
{
r = (lf == 0) ? NSZeroRect : lf->used_rect;
r.origin.x += r.size.width;
r.size.width = 1;
return r;
}
{
unsigned int i;
int j;
linefrag_point_t *lp;
glyph_run_t *r;
unsigned int gpos, cpos;
for (j = 0, lp = lf->points; j < lf->num_points; j++, lp++)
if (lp->pos + lp->length > glyph_index)
break;
x0 = lp->p.x + lf->rect.origin.x;
r = run_for_glyph_index(lp->pos, glyphs, &gpos, &cpos);
i = lp->pos - gpos;
while (i + gpos < glyph_index)
{
if (!r->glyphs[i].isNotShown && r->glyphs[i].g &&
r->glyphs[i].g != NSControlGlyph)
{
x0 += r->glyphs[i].advancement.width;
}
GLYPH_STEP_FORWARD(r, i, gpos, cpos)
}
x1 = x0;
if (!r->glyphs[i].isNotShown && r->glyphs[i].g &&
r->glyphs[i].g != NSControlGlyph)
{
x1 += r->glyphs[i].advancement.width;
}
}
r = lf->rect;
r.origin.x = x0 + (x1 - x0) * fraction_through;
r.size.width = 1;
return r;
}
-(NSRect) insertionPointRectForCharacterIndex: (unsigned int)cindex
inTextContainer: (NSTextContainer *)textContainer
{
int i;
NSRect r;
r = [self _insertionPointRectForCharacterIndex: cindex
textContainer: &i];
if (i == -1 || textcontainers[i].textContainer != textContainer)
return NSZeroRect;
return r;
}
-(unsigned int) characterIndexMoving: (GSInsertionPointMovementDirection)direction
fromCharacterIndex: (unsigned int)from
originalCharacterIndex: (unsigned int)original
distance: (float)distance
{
NSRect from_rect, new_rect;
int from_tc, new_tc;
unsigned int new;
unsigned int length = [_textStorage length];
/* This call will ensure that layout is built to 'from', and that layout
for the line 'from' is in is built. */
from_rect = [self _insertionPointRectForCharacterIndex: from
textContainer: &from_tc];
if (from_tc == -1)
{
/* The from character index is not in a text container, so move the
cursor to the start of the text. */
return 0;
}
/* Simple case which moves one character left/right or one line up/down,
but supports moving between text containers. */
if (distance == 0.0)
{
if (direction == GSInsertionPointMoveLeft ||
direction == GSInsertionPointMoveRight)
{
new = from;
if (direction == GSInsertionPointMoveLeft && new > 0)
new--;
if (direction == GSInsertionPointMoveRight && new < length)
new++;
return new;
}
else if (direction == GSInsertionPointMoveUp ||
direction == GSInsertionPointMoveDown)
{
int orig_tc;
const float target = [self _insertionPointRectForCharacterIndex: original
textContainer: &orig_tc].origin.x;
const int delta = (direction == GSInsertionPointMoveUp) ? -1 : 1;
/* First scan forward or backwards until we end up on a new line */
for (new = from; (direction == GSInsertionPointMoveUp) ? (new > 0) : (new < length); new += delta)
{
new_rect = [self _insertionPointRectForCharacterIndex: new
textContainer: &new_tc];
if (new_rect.origin.y != from_rect.origin.y || new_tc != from_tc)
break;
}
/* We found the start of the line, now find the target character on that line. */
new_rect = [self _insertionPointRectForCharacterIndex: new
textContainer: &new_tc];
while ((direction == GSInsertionPointMoveUp) ? (new > 0) : (new < length))
{
int prev_tc = new_tc;
NSRect prev_rect = new_rect;
new_rect = [self _insertionPointRectForCharacterIndex: new + delta
textContainer: &new_tc];
/* 'new+delta' is on a different line than the target line, so the
target character must be 'new'.*/
if (new_rect.origin.y != prev_rect.origin.y || new_tc != prev_tc)
return new;
/* If we pass the target point, the character we want is either
'new' or 'new+delta' */
if ((direction == GSInsertionPointMoveDown && NSMinX(new_rect) >= target)
|| (direction == GSInsertionPointMoveUp && NSMinX(new_rect) <= target))
{
if (fabs(NSMinX(new_rect) - target) > fabs(NSMinX(prev_rect) - target))
return new;
return new + delta;
}
new += delta;
}
return new;
}
}
/* The following more complex cases are for when a minimum distance is specified.
However, they will not move out of from's text container.
*/
if (direction == GSInsertionPointMoveLeft ||
direction == GSInsertionPointMoveRight)
{
float target;
/*
This is probably very inefficient, but it shouldn't be a bottleneck,
and it guarantees that insertion point movement matches insertion point
positioning. It also lets us do this by character instead of by glyph.
*/
new = from;
if (direction == GSInsertionPointMoveLeft)
{
target = from_rect.origin.x - distance;
while (new > 0)
{
new_rect = [self _insertionPointRectForCharacterIndex: new - 1
textContainer: &new_tc];
if (new_tc != from_tc)
break;
if (new_rect.origin.y != from_rect.origin.y)
break;
new--;
if (NSMaxX(new_rect) <= target)
break;
}
return new;
}
else
{
target = from_rect.origin.x + distance;
while (new < length)
{
new_rect = [self _insertionPointRectForCharacterIndex: new + 1
textContainer: &new_tc];
if (new_tc != from_tc)
break;
if (new_rect.origin.y != from_rect.origin.y)
break;
new++;
if (NSMinX(new_rect) >= target)
break;
}
return new;
}
}
if (direction == GSInsertionPointMoveUp ||
direction == GSInsertionPointMoveDown)
{
NSRect orig_rect, prev_rect;
int orig_tc;
float target;
textcontainer_t *tc;
linefrag_t *lf;
int i;
orig_rect = [self _insertionPointRectForCharacterIndex: original
textContainer: &orig_tc];
if (orig_tc == from_tc)
target = orig_rect.origin.x;
else
target = from_rect.origin.x;
tc = &textcontainers[from_tc];
/* Find first line frag rect on the from line. */
for (i = 0, lf = tc->linefrags; i < tc->num_linefrags; i++, lf++)
{
if (lf->rect.origin.y == from_rect.origin.y)
break;
}
/* If we don't have a line frag rect that matches the from position,
the from position is probably on the last line, in the extra rect,
and i == tc->num_linefrags. The movement direction specific code
handles this case, as long as tc->num_linefrags > 0. */
if (!tc->num_linefrags)
return from; /* Impossible? Should be, since from_tc!=-1. */
if (direction == GSInsertionPointMoveDown)
{
[self _doLayoutToContainer: from_tc
point: NSMakePoint(target, distance + NSMaxY(from_rect))];
tc = textcontainers + from_tc;
/* Find the target line. Move at least (should be up to?)
distance, and at least one line. */
for (; i < tc->num_linefrags; i++, lf++)
if (NSMaxY(lf->rect) >= distance + NSMaxY(from_rect) &&
NSMinY(lf->rect) != NSMinY(from_rect))
break;
if (i == tc->num_linefrags)
{
/* Special case for moving into the extra line at the end */
if (tc->textContainer == extra_textcontainer &&
NSMaxY(extra_rect) >= distance + NSMaxY(from_rect) &&
NSMinY(extra_rect) != NSMinY(from_rect))
return length;
/* We can't move as far as we want to. In fact, we might not
have been able to move at all.
TODO: figure out how to handle this
*/
return from;
}
}
else
{
if (i == tc->num_linefrags)
i--, lf--;
/* Find the target line. Move at least (should be up to?)
distance, and at least one line. */
for (; i >= 0; i--, lf--)
if (NSMinY(lf->rect) <= NSMinY(from_rect) - distance &&
NSMinY(lf->rect) != NSMinY(from_rect))
break;
/* Now we have the last line frag of the target line. Move
backwards to the first one. */
for (; i > 0; i--, lf--)
if (NSMinY(lf->rect) != NSMinY(lf[-1].rect))
break;
if (i == -1)
{
/* We can't move as far as we want to. In fact, we might not
have been able to move at all.
TODO: figure out how to handle this
*/
return from;
}
}
/* Now we have the first line frag of the target line and the
target x position. */
new = [self characterRangeForGlyphRange: NSMakeRange(lf->pos, 1)
actualGlyphRange: NULL].location;
/* The first character index might not actually be in this line
rect, so move forwards to the first character in the target line. */
while (new < length)
{
new_rect = [self _insertionPointRectForCharacterIndex: new + 1
textContainer: &new_tc];
if (new_tc > from_tc)
break;
if (new_rect.origin.y >= lf->rect.origin.y)
break;
new++;
}
/* Now find the target character in the line. */
new_rect = [self _insertionPointRectForCharacterIndex: new
textContainer: &new_tc];
while (new < length)
{
prev_rect = new_rect;
new_rect = [self _insertionPointRectForCharacterIndex: new + 1
textContainer: &new_tc];
if (new_tc != from_tc)
break;
if (new_rect.origin.y != lf->rect.origin.y)
break;
if (NSMinX(new_rect) >= target)
{
/*
'new+1' is beyond 'target', so either 'new' or 'new+1' is the
character we want. Pick the closest one. (Note that 'new' might
also be beyond 'target'.)
*/
if (fabs(NSMinX(new_rect) - target) < fabs(NSMinX(prev_rect) - target))
new++;
return new;
}
new++;
}
return new;
}
NSLog(@"(%s): invalid direction %i (distance %g)",
__PRETTY_FUNCTION__, direction, distance);
return from;
}
- (void) ensureGlyphsForGlyphRange: (NSRange)glyphRange
{
[self _generateGlyphsUpToGlyph: NSMaxRange(glyphRange) - 1];
}
- (void) ensureGlyphsForCharacterRange: (NSRange)charRange
{
[self _generateGlyphsUpToCharacter: NSMaxRange(charRange) - 1];
}
- (void) ensureLayoutForGlyphRange: (NSRange)glyphRange
{
[self _doLayoutToGlyph: NSMaxRange(glyphRange) - 1];
}
- (void) ensureLayoutForCharacterRange: (NSRange)charRange
{
NSRange glyphRange;
glyphRange = [self glyphRangeForCharacterRange: charRange
actualCharacterRange: NULL];
[self ensureLayoutForGlyphRange: glyphRange];
}
- (void) ensureLayoutForTextContainer: (NSTextContainer*)container
{
int i;
textcontainer_t *tc;
NSSize size;
for (tc = textcontainers, i = 0; i < num_textcontainers; i++, tc++)
if (tc->textContainer == container)
break;
if (i == num_textcontainers)
{
NSLog(@"%s: invalid text container", __PRETTY_FUNCTION__);
return;
}
size = [container containerSize];
[self _doLayoutToContainer: i
point: NSMakePoint(size.width, size.height)];
}
- (void) ensureLayoutForBoundingRect: (NSRect)bounds
inTextContainer: (NSTextContainer*)container
{
int i;
textcontainer_t *tc;
for (tc = textcontainers, i = 0; i < num_textcontainers; i++, tc++)
if (tc->textContainer == container)
break;
if (i == num_textcontainers)
{
NSLog(@"%s: invalid text container", __PRETTY_FUNCTION__);
return;
}
[self _doLayoutToContainer: i
point: NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))];
}
- (void) invalidateLayoutForCharacterRange: (NSRange)charRange
actualCharacterRange: (NSRangePointer)actualCharRange
{
[self invalidateLayoutForCharacterRange: charRange
isSoft: NO
actualCharacterRange: actualCharRange];
}
- (void) invalidateGlyphsOnLayoutInvalidationForGlyphRange: (NSRange)glyphRange
{
// FIXME
}
- (BOOL) allowsNonContiguousLayout
{
return NO;
}
2023-11-20 21:43:49 +00:00
- (void) setAllowsNonContiguousLayout: (BOOL)flag
{
}
2023-11-20 21:43:49 +00:00
- (BOOL) hasNonContiguousLayout
{
return NO;
}
@end
@implementation NSLayoutManager (drawing)
/** Drawing **/
/*
If a range passed to a drawing function isn't contained in the text
container that contains its first glyph, the range is silently clamped.
My thought with this is that the requested glyphs might not fit in the
text container (if it's the last text container, or there's only one).
In that case, it isn't really the caller's fault, and drawing as much as
will fit in the text container makes sense.
TODO: reconsider silently clamping ranges in these methods; might
want to make sure we don't do it if part of the range is in a second
container
*/
-(void) drawBackgroundForGlyphRange: (NSRange)range
atPoint: (NSPoint)containerOrigin
{
NSTextContainer *textContainer;
glyph_run_t *glyph_run;
unsigned int glyph_pos, char_pos, first_char_pos;
int i, j;
NSRect *rects;
NSUInteger count;
NSColor *color = nil;
NSColor *last_color = nil;
NSGraphicsContext *ctxt = GSCurrentContext();
if (!range.length)
return;
[self _doLayoutToGlyph: range.location + range.length - 1];
{
int i;
textcontainer_t *tc;
for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++)
if (tc->pos + tc->length > range.location)
break;
if (i == num_textcontainers)
{
NSLog(@"%s: can't find text container for glyph (internal error)", __PRETTY_FUNCTION__);
return;
}
if (range.location + range.length > tc->pos + tc->length)
range.length = tc->pos + tc->length - range.location;
textContainer = tc->textContainer;
}
glyph_run = run_for_glyph_index(range.location, glyphs, &glyph_pos, &char_pos);
i = range.location - glyph_pos;
first_char_pos = char_pos;
while ((glyph_run != NULL) && (i + glyph_pos < range.location + range.length))
{
NSRange r = NSMakeRange(glyph_pos + i, glyph_run->head.glyph_length - i);
if (NSMaxRange(r) > NSMaxRange(range))
{
r.length = NSMaxRange(range) - r.location;
}
color = [_textStorage attribute: NSBackgroundColorAttributeName
atIndex: char_pos
effectiveRange: NULL];
if (color)
{
rects = [self rectArrayForGlyphRange: r
withinSelectedGlyphRange: NSMakeRange(NSNotFound, 0)
inTextContainer: textContainer
rectCount: &count];
if (count)
{
if (last_color != color)
{
[color set];
last_color = color;
}
for (j = 0; j < count; j++, rects++)
{
DPSrectfill(ctxt,
rects->origin.x + containerOrigin.x,
rects->origin.y + containerOrigin.y,
rects->size.width, rects->size.height);
}
}
}
glyph_pos += glyph_run->head.glyph_length;
char_pos += glyph_run->head.char_length;
i = 0;
glyph_run = (glyph_run_t *)glyph_run->head.next;
}
if (!_selected_range.length || _selected_range.location == NSNotFound)
return;
if (_selected_range.location >= char_pos
|| _selected_range.location + _selected_range.length <= first_char_pos)
{
return;
}
/* The selection (might) intersect our glyph range. */
{
NSRange r = [self glyphRangeForCharacterRange: _selected_range
actualCharacterRange: NULL];
NSRange sel = r;
NSTextView *ftv;
if (r.location < range.location)
{
if (range.location - r.location > r.length)
return;
r.length -= range.location - r.location;
r.location = range.location;
}
if (r.location + r.length > range.location + range.length)
{
if (r.location > range.location + range.length)
return;
r.length = range.location + range.length - r.location;
}
/* Use the text view's selected text attributes */
if ((ftv = [self textViewForBeginningOfSelection]))
color = [[ftv selectedTextAttributes]
objectForKey: NSBackgroundColorAttributeName];
if (!color)
color = [NSColor selectedTextBackgroundColor];
if (!color)
return;
rects = [self rectArrayForGlyphRange: r
withinSelectedGlyphRange: sel
inTextContainer: textContainer
rectCount: &count];
if (count)
{
[color set];
for (j = 0; j < count; j++, rects++)
{
DPSrectfill(ctxt,
rects->origin.x + containerOrigin.x,
rects->origin.y + containerOrigin.y,
rects->size.width, rects->size.height);
}
}
}
}
static inline NSSize
attachmentSize(linefrag_t *lf, NSUInteger glyphIndex)
{
linefrag_attachment_t *la;
int la_i;
la = lf->attachments;
la_i = 0;
if (la)
{
while (la->pos != glyphIndex && la_i < lf->num_attachments)
{
la++;
la_i++;
}
}
if (la_i >= lf->num_attachments)
return NSMakeSize(-1.0, -1.0);
return la->size;
}
- (NSSize) attachmentSizeForGlyphAtIndex: (NSUInteger)glyphIndex
{
textcontainer_t *tc;
int i;
linefrag_t *lf;
for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++)
if (tc->pos + tc->length > glyphIndex)
break;
if (i == num_textcontainers)
{
NSLog(@"%s: can't find text container for glyph (internal error)", __PRETTY_FUNCTION__);
return NSMakeSize(-1.0, -1.0);
}
LINEFRAG_FOR_GLYPH(glyphIndex);
return attachmentSize(lf, glyphIndex);
}
- (void) showAttachmentCell: (NSCell *)cell
inRect: (NSRect)rect
characterIndex: (NSUInteger)attachmentIndex
{
[(id <NSTextAttachmentCell>)cell drawWithFrame: rect
inView: [NSView focusView]
characterIndex: attachmentIndex
layoutManager: self];
}
-(void) drawGlyphsForGlyphRange: (NSRange)range
atPoint: (NSPoint)containerOrigin
{
int i, j;
textcontainer_t *tc;
linefrag_t *lf;
linefrag_point_t *lp;
NSPoint p;
unsigned int g;
NSDictionary *attributes;
NSFont *f;
NSColor *color, *run_color;
NSRange selectedGlyphRange;
BOOL currentGlyphIsSelected;
glyph_run_t *glyph_run;
unsigned int glyph_pos, char_pos;
glyph_t *glyph;
NSGraphicsContext *ctxt = GSCurrentContext();
/*
For performance, it might (if benchmarks or profiling backs it up) be
worthwhile to cache this across calls to this method. However, this
color can change at runtime, so care would have to be taken to keep the
cache in sync with the actual color.
*/
NSColor *defaultTextColor = [NSColor textColor];
NSColor *selectedTextColor = defaultTextColor;
NSColor *link_color = nil;
id linkValue;
#define GBUF_SIZE 16 /* TODO: tweak */
NSGlyph gbuf[GBUF_SIZE];
NSSize advancementbuf[GBUF_SIZE];
int gbuf_len, gbuf_size;
2005-01-21 21:43 Alexander Malmberg <alexander@malmberg.org> Various whitespace cleanups, comment type fixes, and changes to avoid warnings from recent versions of gcc. * Headers/Additions/GNUstepGUI/GSToolbar.h (-_toolbars): Declare. * Source/NSWindow+Toolbar.m: Remove conflicting declaration of [NSToolbar -_toolbars]. * Headers/Additions/GNUstepGUI/GSServicesManager.h, Source/GSServicesMananger.m (-item2title:, -validateMenuItem:): Adjust argument types. * Headers/AppKit/NSMenu.h (-validateMenuItem:): Adjust argument type. * Source/NSTextView.m (-sizeToFit): Don't use size uninitialized if neither resizable flags is set. (-insertText:): Adjust argument type. * Headers/AppKit/NSResponder.h, Source/NSResponder.m (-insertText:): Adjust argument type. Document. * Headers/AppKit/NSView.h: Change type of ivar _window to NSWindow *. * Source/GSTitleView.m (-mouseDown:): Always initialize startWindowOrigin. * Source/NSApplication.m (-setApplicationIconImage:): Add casts to avoid warnings. * Source/NSCell.m (-cellSize): Add default: case. * Source/NSPasteboard.m ([GSFiltered -pasteboard:provideDataForType:]): Detect and warn if we can't find a filter that will get us the desired type. * Source/NSProgressIndicator.m: Comment out unused variable 'images'. * Source/NSBezierPath.m: Declare GSBezierPath fully before using it. (-bezierPathByFlatteningPath, -bezierPathByReversingPath): Make sure variables are always initialized. * Source/NSMenuView.m, * Source/NSPrintOperation.m, * Source/NSSplitView.m, * Source/NSTableHeaderView.m: Make sure variables are always initialized. * Source/NSBox.m, * Source/NSImageview.m, * Source/NSText.m, * Source/NSTextStorage.m: Add missing includes. * Source/GSKeyBindingTable.m, * Source/GSLayoutManager.m, * Source/NSBitmapImageRep+PNM.m, * Source/NSBundleAdditions.m, * Source/NSLayoutManager.m, * Source/nsimage-tiff.h, * Source/tiff.m, * Headers/Additions/GNUstepGUI/GSDisplayServer.h, * Source/GSDisplayServer.m: Change signedness of various variables. * Source/NSPanel.m (-sendEvent:): Remove. * Source/NSWindow.m (-becomesKeyOnlyIfNeeded): New method. (-_sendEvent:becomesKeyOnlyIfNeeded:): Remove. Move code ... (-sendEvent:): ... here. Use -becomesKeyOnlyIfNeeded instead of the argument. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@20590 72102866-910b-0410-8b05-ffd578937521
2005-01-21 20:39:18 +00:00
NSPoint gbuf_point = NSZeroPoint;
if (!range.length)
return;
[self _doLayoutToGlyph: range.location + range.length - 1];
/* Find the selected range of glyphs as it overlaps with the range we
* are about to display.
*/
if (_selected_range.length == 0)
{
selectedGlyphRange.location = 0;
selectedGlyphRange.length = 0;
}
else
{
selectedGlyphRange = [self glyphRangeForCharacterRange: _selected_range
actualCharacterRange: 0];
}
selectedGlyphRange = NSIntersectionRange(selectedGlyphRange, range);
if ([ctxt isDrawingToScreen])
gbuf_size = GBUF_SIZE;
else
gbuf_size = 1;
for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++)
if (tc->pos + tc->length > range.location)
break;
if (i == num_textcontainers)
{
NSLog(@"%s: can't find text container for glyph (internal error)", __PRETTY_FUNCTION__);
return;
}
if (range.location + range.length > tc->pos + tc->length)
range.length = tc->pos + tc->length - range.location;
LINEFRAG_FOR_GLYPH(range.location);
j = 0;
lp = lf->points;
while (lp->pos + lp->length < range.location)
lp++, j++;
glyph_run = run_for_glyph_index(lp->pos, glyphs, &glyph_pos, &char_pos);
currentGlyphIsSelected = NSLocationInRange(lp->pos, selectedGlyphRange);
glyph = glyph_run->glyphs + lp->pos - glyph_pos;
attributes = [_textStorage attributesAtIndex: char_pos
effectiveRange: NULL];
run_color = [attributes valueForKey: NSForegroundColorAttributeName];
if (run_color == nil)
run_color = defaultTextColor;
linkValue = [attributes objectForKey: NSLinkAttributeName];
if (linkValue != nil)
{
if (link_color == nil)
{
NSDictionary *link_attributes = [[self firstTextView] linkTextAttributes];
link_color = [link_attributes valueForKey: NSForegroundColorAttributeName];
}
if (link_color != nil)
run_color = link_color;
}
if (selectedGlyphRange.length > 0)
{
/* Get the text view's color setting for selected text as we will
* be needing to draw some selected glyphs.
*/
selectedTextColor = [[[self textViewForBeginningOfSelection]
selectedTextAttributes] objectForKey: NSForegroundColorAttributeName];
/* FIXME ... should we fall back to using selectedTextColor or
* defaultTextColor?
*/
if (selectedTextColor == nil)
{
selectedTextColor = [NSColor selectedTextColor];
}
}
color = (currentGlyphIsSelected ? selectedTextColor : run_color);
[color set];
f = glyph_run->font;
[f set];
p = lp->p;
p.x += lf->rect.origin.x + containerOrigin.x;
p.y += lf->rect.origin.y + containerOrigin.y;
gbuf_len = 0;
for (g = lp->pos; g < range.location + range.length; g++, glyph++)
{
if (currentGlyphIsSelected != NSLocationInRange(g, selectedGlyphRange))
{
/* When we change between drawing selected and unselected glyphs
* we must flush any glyphs from the buffer and change trhe color
* we use for the text.
*/
if (gbuf_len)
{
DPSmoveto(ctxt, gbuf_point.x, gbuf_point.y);
GSShowGlyphsWithAdvances(ctxt, gbuf, advancementbuf, gbuf_len);
DPSnewpath(ctxt);
gbuf_len = 0;
}
if (currentGlyphIsSelected == YES)
{
currentGlyphIsSelected = NO;
if (color != run_color)
{
color = run_color;
[color set];
}
}
else
{
currentGlyphIsSelected = YES;
if (color != selectedTextColor)
{
color = selectedTextColor;
[color set];
}
}
}
if (g == lp->pos + lp->length)
{
if (gbuf_len)
{
DPSmoveto(ctxt, gbuf_point.x, gbuf_point.y);
GSShowGlyphsWithAdvances(ctxt, gbuf, advancementbuf, gbuf_len);
DPSnewpath(ctxt);
gbuf_len = 0;
}
j++;
lp++;
if (j == lf->num_points)
{
i++;
lf++;
j = 0;
lp = lf->points;
}
p = lp->p;
p.x += lf->rect.origin.x + containerOrigin.x;
p.y += lf->rect.origin.y + containerOrigin.y;
}
if (g == glyph_pos + glyph_run->head.glyph_length)
{
glyph_pos += glyph_run->head.glyph_length;
char_pos += glyph_run->head.char_length;
glyph_run = (glyph_run_t *)glyph_run->head.next;
attributes = [_textStorage attributesAtIndex: char_pos
effectiveRange: NULL];
run_color = [attributes valueForKey: NSForegroundColorAttributeName];
if (run_color == nil)
{
run_color = defaultTextColor;
}
linkValue = [attributes objectForKey: NSLinkAttributeName];
if (linkValue != nil)
{
if (link_color == nil)
{
NSDictionary *link_attributes = [[self firstTextView] linkTextAttributes];
link_color = [link_attributes valueForKey: NSForegroundColorAttributeName];
}
if (link_color != nil)
run_color = link_color;
}
glyph = glyph_run->glyphs;
/* If the font has changed or the color has changed (and we are
* not drawing using the selected text color) then we must flush
* any buffered glyphs and set the new font and color.
*/
if (glyph_run->font != f
|| (currentGlyphIsSelected == NO && run_color != color))
{
if (gbuf_len)
{
DPSmoveto(ctxt, gbuf_point.x, gbuf_point.y);
GSShowGlyphsWithAdvances(ctxt, gbuf, advancementbuf, gbuf_len);
DPSnewpath(ctxt);
gbuf_len = 0;
}
if (f != glyph_run->font)
{
f = glyph_run->font;
[f set];
}
if (currentGlyphIsSelected == NO && run_color != color)
{
color = run_color;
[color set];
}
}
}
if (!glyph->isNotShown && glyph->g && glyph->g != NSControlGlyph)
{
if (glyph->g == GSAttachmentGlyph)
{
if (g >= range.location)
{
unsigned int char_index =
[self characterRangeForGlyphRange: NSMakeRange(g, 1)
actualGlyphRange: NULL].location;
id<NSTextAttachmentCell> cell = [[_textStorage attribute: NSAttachmentAttributeName
atIndex: char_index
effectiveRange: NULL] attachmentCell];
NSRect cellFrame;
cellFrame.origin = p;
cellFrame.size = attachmentSize(lf, g);
cellFrame.origin.y -= cellFrame.size.height;
/* Silently ignore if we don't have any size information for
it. */
if (NSEqualSizes(cellFrame.size, NSMakeSize(-1.0, -1.0)))
continue;
/* Drawing the cell might mess up our state, so we reset
the font and color afterwards. */
/* TODO:
optimize this?
collect attachments and draw them in bunches of eg. 4?
probably not worth effort. better to optimize font and
color setting :)
should they really be drawn in our coordinate system?
*/
[self showAttachmentCell: (NSCell*)cell
inRect: cellFrame
characterIndex: char_index];
[f set];
[color set];
}
continue;
}
if (g >= range.location)
{
if (!gbuf_len)
{
gbuf[0] = glyph->g;
advancementbuf[0] = glyph->advancement;
gbuf_point = p;
gbuf_len = 1;
}
else
{
if (gbuf_len == gbuf_size)
{
DPSmoveto(ctxt, gbuf_point.x, gbuf_point.y);
GSShowGlyphsWithAdvances(ctxt, gbuf, advancementbuf, gbuf_size);
DPSnewpath(ctxt);
gbuf_len = 0;
gbuf_point = p;
}
gbuf[gbuf_len] = glyph->g;
advancementbuf[gbuf_len] = glyph->advancement;
gbuf_len++;
}
}
p.x += glyph->advancement.width;
}
}
if (gbuf_len)
{
/*int i;
printf("%i at (%g %g) 4\n", gbuf_len, gbuf_point.x, gbuf_point.y);
for (i = 0; i < gbuf_len; i++) printf(" %3i : %04x\n", i, gbuf[i]); */
DPSmoveto(ctxt, gbuf_point.x, gbuf_point.y);
GSShowGlyphsWithAdvances(ctxt, gbuf, advancementbuf, gbuf_len);
DPSnewpath(ctxt);
}
#undef GBUF_SIZE
// Draw underline where necessary
// FIXME: Also draw strikeout
{
const NSRange characterRange = [self characterRangeForGlyphRange: range
actualGlyphRange: NULL];
id linkUnderlineValue = nil;
for (i=characterRange.location; i<NSMaxRange(characterRange); )
{
NSRange underlinedCharacterRange;
NSRange linkCharacterRange;
id underlineValue = nil;
linkValue = [_textStorage attribute: NSLinkAttributeName
atIndex: i
longestEffectiveRange: &linkCharacterRange
inRange: characterRange];
if (linkValue != nil)
{
if (linkUnderlineValue == nil)
{
NSDictionary *link_attributes = [[self firstTextView] linkTextAttributes];
linkUnderlineValue = [link_attributes valueForKey: NSUnderlineStyleAttributeName];
}
underlineValue = linkUnderlineValue;
underlinedCharacterRange = linkCharacterRange;
}
else
{
underlineValue = [_textStorage attribute: NSUnderlineStyleAttributeName
atIndex: i
longestEffectiveRange: &underlinedCharacterRange
inRange: characterRange];
underlinedCharacterRange = NSIntersectionRange(underlinedCharacterRange,
linkCharacterRange);
}
if (underlineValue != nil && [underlineValue integerValue] != NSUnderlineStyleNone)
{
const NSRange underlinedGylphRange = [self glyphRangeForCharacterRange: underlinedCharacterRange
actualCharacterRange: NULL];
// we have a range of glpyhs that need underlining, which might span
// multiple line fragments, so we need to iterate though the line fragments
for (j=underlinedGylphRange.location; j<NSMaxRange(underlinedGylphRange); )
{
NSRange lineFragmentGlyphRange;
const NSRect lineFragmentRect = [self lineFragmentRectForGlyphAtIndex: j
effectiveRange: &lineFragmentGlyphRange];
const NSRange rangeToUnderline = NSIntersectionRange(underlinedGylphRange, lineFragmentGlyphRange);
[self underlineGylphRange: rangeToUnderline
underlineType: [underlineValue integerValue]
lineFragmentRect: lineFragmentRect
lineFragmentGlyphRange: lineFragmentGlyphRange
containerOrigin: containerOrigin];
j = NSMaxRange(rangeToUnderline);
}
}
i += underlinedCharacterRange.length;
}
// Draw spelling state (i.e. red underline for misspelled words)
if ([NSGraphicsContext currentContextDrawingToScreen])
{
for (i=characterRange.location; i<NSMaxRange(characterRange); )
{
NSRange underlinedCharacterRange;
id underlineValue = [self temporaryAttribute: NSSpellingStateAttributeName
atCharacterIndex: i
longestEffectiveRange: &underlinedCharacterRange
inRange: characterRange];
if (underlineValue != nil && [underlineValue integerValue] != 0)
{
const NSRange underlinedGylphRange = [self glyphRangeForCharacterRange: underlinedCharacterRange
actualCharacterRange: NULL];
// we have a range of glpyhs that need underlining, which might span
// multiple line fragments, so we need to iterate though the line fragments
for (j=underlinedGylphRange.location; j<NSMaxRange(underlinedGylphRange); )
{
NSRange lineFragmentGlyphRange;
const NSRect lineFragmentRect = [self lineFragmentRectForGlyphAtIndex: j
effectiveRange: &lineFragmentGlyphRange];
const NSRange rangeToUnderline = NSIntersectionRange(underlinedGylphRange, lineFragmentGlyphRange);
[self _drawSpellingState: [underlineValue integerValue]
forGylphRange: rangeToUnderline
lineFragmentRect: lineFragmentRect
lineFragmentGlyphRange: lineFragmentGlyphRange
containerOrigin: containerOrigin];
j = NSMaxRange(rangeToUnderline);
}
}
i += underlinedCharacterRange.length;
}
}
}
}
-(void) underlineGylphRange: (NSRange)range
underlineType: (NSInteger)type
lineFragmentRect: (NSRect)fragmentRect
lineFragmentGlyphRange: (NSRange)fragmentGlyphRange
containerOrigin: (NSPoint)containerOrigin
{
// FIXME: Implement underlining by word
/*if ((type & NSUnderlineByWordMask) != 0)
{
NSCharacterSet *setToSkip = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSRange characterRange = [self characterRangeForGlyphRange: range
actualGlyphRange: NULL];
NSString *string = [[self textStorage] string];
NSRange examiningRange = range;
while (1)
{
NSRange nextRangeToSkip = [string rangeOfCharacterInSet: setToSkip
option: 0
range: NSMakeRange(i, range.length - (i - range.location))];
if (nextRangeToSkip.location == NSNotFound)
{
}
else
{
}
}
}
else*/
{
[self drawUnderlineForGlyphRange: range
underlineType: type
baselineOffset: 0 // FIXME:
lineFragmentRect: fragmentRect
lineFragmentGlyphRange: fragmentGlyphRange
containerOrigin: containerOrigin];
}
}
static void GSDrawPatternLine(NSPoint start, NSPoint end, NSInteger pattern, CGFloat thickness, CGFloat phase)
{
NSBezierPath *path = [NSBezierPath bezierPath];
// FIXME: setLineDash should take CGFloat
if ((pattern & NSUnderlinePatternDot) == NSUnderlinePatternDot)
{
const CGFloat dot[2] = {2.5 * thickness, 2.5 * thickness};
[path setLineDash: dot count: 2 phase: phase];
}
else if ((pattern & NSUnderlinePatternDash) == NSUnderlinePatternDash)
{
const CGFloat dash[2] = {10 * thickness, 5 * thickness};
[path setLineDash: dash count: 2 phase: phase];
}
else if ((pattern & NSUnderlinePatternDashDot) == NSUnderlinePatternDashDot)
{
const CGFloat dashdot[4] = {10 * thickness, 3 * thickness, 3 * thickness, 3 * thickness};
[path setLineDash: dashdot count: 4 phase: phase];
}
else if ((pattern & NSUnderlinePatternDashDotDot) == NSUnderlinePatternDashDotDot)
{
const CGFloat dashdotdot[6] = {10 * thickness, 3 * thickness, 3 * thickness, 3 * thickness, 3 * thickness, 3 * thickness};
[path setLineDash: dashdotdot count: 6 phase: phase];
}
[path setLineWidth: thickness];
[path moveToPoint: start];
[path lineToPoint: end];
[path stroke];
}
-(void) drawUnderlineForGlyphRange: (NSRange)underlineRange
underlineType: (NSInteger)type
baselineOffset: (CGFloat)offset
lineFragmentRect: (NSRect)fragmentRect
lineFragmentGlyphRange: (NSRange)fragmentGlyphRange
containerOrigin: (NSPoint)containerOrigin
{
/*NSLog(@"drawUnderlineForGlyphRange:%@ underlineType:%d baselineOffset:%f lineFragmentRect:%@ lineFragmentGlyphRange:%@ containerOrigin:%@",
NSStringFromRange(underlineRange),
(int)type,
(float)offset,
NSStringFromRect(fragmentRect),
NSStringFromRange(fragmentGlyphRange),
NSStringFromPoint(containerOrigin));*/
// We have to iterate through the attributes (again..) to find
// contiguous regions with the same underline color.
NSUInteger i;
NSColor *link_color = nil;
const NSRange characterRange = [self characterRangeForGlyphRange: underlineRange
actualGlyphRange: NULL];
if (!(underlineRange.location >= fragmentGlyphRange.location &&
NSMaxRange(underlineRange) <= NSMaxRange(fragmentGlyphRange)))
{
NSLog(@"Error, underlineRange must be inside fragmentGlyphRange");
return;
}
for (i = characterRange.location; i < NSMaxRange(characterRange); )
{
NSRange underlineColorCharacterRange, foregroundColorCharacterRange, rangeToDraw;
NSColor *underlineColor = nil;
NSRange glyphRangeToDraw;
NSRange linkCharacterRange;
id linkValue;
linkValue = [_textStorage attribute: NSLinkAttributeName
atIndex: i
longestEffectiveRange: &linkCharacterRange
inRange: NSMakeRange(i, NSMaxRange(characterRange)-i)];
if (linkValue != nil)
{
if (link_color == nil)
{
NSDictionary *link_attributes = [[self firstTextView] linkTextAttributes];
link_color = [link_attributes valueForKey: NSForegroundColorAttributeName];
}
if (link_color != nil)
underlineColor = link_color;
underlineColorCharacterRange = linkCharacterRange;
}
else
{
underlineColor = (NSColor*)[[self textStorage]
attribute: NSUnderlineColorAttributeName
atIndex: i
longestEffectiveRange: &underlineColorCharacterRange
inRange: NSMakeRange(i, NSMaxRange(characterRange)-i)];
underlineColorCharacterRange = NSIntersectionRange(underlineColorCharacterRange,
linkCharacterRange);
}
if (underlineColor != nil)
{
[underlineColor set];
rangeToDraw = underlineColorCharacterRange;
}
else
{
NSColor *foregroundColor = (NSColor*)[[self textStorage]
attribute: NSForegroundColorAttributeName
atIndex: i
longestEffectiveRange: &foregroundColorCharacterRange
inRange: NSMakeRange(i, NSMaxRange(characterRange)-i)];
if (foregroundColor != nil)
{
[foregroundColor set];
}
else
{
[[NSColor textColor] set];
}
// Draw the smaller range
rangeToDraw = underlineColorCharacterRange.length < foregroundColorCharacterRange.length ?
underlineColorCharacterRange : foregroundColorCharacterRange;
}
glyphRangeToDraw = [self glyphRangeForCharacterRange: rangeToDraw
actualCharacterRange: NULL];
if (glyphRangeToDraw.length > 0)
{
// do the actual underline
// FIXME: find the largest font within the range to underline
// NOTE: GS private method
NSFont *largestFont = [self effectiveFontForGlyphAtIndex: glyphRangeToDraw.location
range: NULL];
const CGFloat underlineWidth = [largestFont pointSize] *
(((type & NSUnderlineStyleDouble) != 0) ? 0.05 : 0.07);
NSPoint start = [self locationForGlyphAtIndex: glyphRangeToDraw.location];
NSPoint end = [self locationForGlyphAtIndex: NSMaxRange(glyphRangeToDraw) - 1];
// FIXME: remove this hack lowers the underline slightly
start.y += [largestFont pointSize] * 0.07;
end.y += [largestFont pointSize] * 0.07;
end.x += [self advancementForGlyphAtIndex: (NSMaxRange(glyphRangeToDraw) - 1)].width;
start = NSMakePoint(start.x + containerOrigin.x + fragmentRect.origin.x, start.y + containerOrigin.y + fragmentRect.origin.y);
end = NSMakePoint(end.x + containerOrigin.x + fragmentRect.origin.x, end.y + containerOrigin.y + fragmentRect.origin.y);
if ((type & NSUnderlineStyleDouble) == NSUnderlineStyleDouble)
{
GSDrawPatternLine(NSMakePoint(start.x, start.y - (underlineWidth / 2)),
NSMakePoint(end.x, end.y - (underlineWidth / 2)),
type, underlineWidth / 2, start.x);
GSDrawPatternLine(NSMakePoint(start.x, start.y + (underlineWidth / 2)),
NSMakePoint(end.x, end.y + (underlineWidth / 2)),
type, underlineWidth / 2, start.x);
}
else
{
GSDrawPatternLine(start, end, type, underlineWidth, start.x);
}
}
i += rangeToDraw.length;
}
}
@end
@implementation NSLayoutManager (spelling)
-(void) _drawSpellingState: (NSInteger)spellingState
forGylphRange: (NSRange)range
lineFragmentRect: (NSRect)fragmentRect
lineFragmentGlyphRange: (NSRange)fragmentGlyphRange
containerOrigin: (NSPoint)containerOrigin
{
NSBezierPath *path;
const CGFloat pattern[2] = {2.5, 1.0};
NSFont *largestFont = [self effectiveFontForGlyphAtIndex: range.location // NOTE: GS private method
range: NULL];
NSPoint start = [self locationForGlyphAtIndex: range.location];
NSPoint end = [self locationForGlyphAtIndex: NSMaxRange(range) - 1]; //FIXME: check length > 0
if (spellingState == 0)
{
return;
}
// FIXME: calculate the underline position correctly, using the font on both the start and end glyph
start.y += [largestFont pointSize] * 0.07;
end.y += [largestFont pointSize] * 0.07;
end.x += [self advancementForGlyphAtIndex: (NSMaxRange(range) - 1)].width;
start = NSMakePoint(start.x + containerOrigin.x + fragmentRect.origin.x, start.y + containerOrigin.y + fragmentRect.origin.y);
end = NSMakePoint(end.x + containerOrigin.x + fragmentRect.origin.x, end.y + containerOrigin.y + fragmentRect.origin.y);
path = [NSBezierPath bezierPath];
[path setLineDash: pattern count: 2 phase: 0];
[path setLineWidth: 1.5];
[path moveToPoint: start];
[path lineToPoint: end];
if ((spellingState & NSSpellingStateGrammarFlag) != 0)
{
[[NSColor greenColor] set];
}
else
{
[[NSColor redColor] set];
}
[path stroke];
}
@end
@implementation NSLayoutManager
/*
* Class methods
*/
+ (void) initialize
{
if (self == [NSLayoutManager class])
{
[self exposeBinding: @"hyphenationFactor"];
}
}
-(void) insertTextContainer: (NSTextContainer *)aTextContainer
atIndex: (unsigned int)index
{
int i;
[super insertTextContainer: aTextContainer
atIndex: index];
for (i = 0; i < num_textcontainers; i++)
[[textcontainers[i].textContainer textView] _updateMultipleTextViews];
}
-(void) removeTextContainerAtIndex: (unsigned int)index
{
int i;
NSTextView *tv = [textcontainers[index].textContainer textView];
RETAIN(tv);
[super removeTextContainerAtIndex: index];
[tv _updateMultipleTextViews];
RELEASE(tv);
for (i = 0; i < num_textcontainers; i++)
[[textcontainers[i].textContainer textView] _updateMultipleTextViews];
}
-(void) dealloc
{
// Remove all key value bindings for this object.
[GSKeyValueBinding unbindAllForObject: self];
DESTROY(_typingAttributes);
DESTROY(_temporaryAttributes);
[super dealloc];
}
/*
TODO: Add a general typesetterAttributes dictionary. Implement the
hyphenation factor methods by setting/getting an attribute in this
dictionary.
*/
-(float) hyphenationFactor
{
return 0.0;
}
-(void) setHyphenationFactor: (float)factor
{
NSLog(@"Warning: (NSLayoutManager) %s not implemented", __PRETTY_FUNCTION__);
}
-(NSTextView *) firstTextView
{
int i;
NSTextView *tv;
for (i = 0; i < num_textcontainers; i++)
{
tv = [textcontainers[i].textContainer textView];
if (tv)
return tv;
}
return nil;
}
-(NSTextView *) textViewForBeginningOfSelection
{
/* TODO */
return [self firstTextView];
}
-(BOOL) layoutManagerOwnsFirstResponderInWindow: (NSWindow *)window
{
int i;
NSResponder *tv;
NSResponder *v = [window firstResponder];
for (i = 0; i < num_textcontainers; i++)
{
tv = [textcontainers[i].textContainer textView];
if (tv == v)
return YES;
}
return NO;
}
-(NSArray *) rulerMarkersForTextView: (NSTextView *)textView
paragraphStyle: (NSParagraphStyle *)paragraphStyle
ruler: (NSRulerView *)aRulerView
{
NSRulerMarker *marker;
NSTextTab *tab;
NSImage *image;
NSArray *tabs = [paragraphStyle tabStops];
NSEnumerator *enumerator = [tabs objectEnumerator];
NSMutableArray *markers = [NSMutableArray arrayWithCapacity: [tabs count]];
while ((tab = [enumerator nextObject]) != nil)
{
switch ([tab tabStopType])
{
case NSLeftTabStopType:
image = [NSImage imageNamed: @"common_LeftTabStop"];
break;
case NSRightTabStopType:
image = [NSImage imageNamed: @"common_RightTabStop"];
break;
case NSCenterTabStopType:
image = [NSImage imageNamed: @"common_CenterTabStop"];
break;
case NSDecimalTabStopType:
image = [NSImage imageNamed: @"common_DecimalTabStop"];
break;
default:
image = nil;
break;
}
marker = [[NSRulerMarker alloc]
initWithRulerView: aRulerView
markerLocation: [tab location]
image: image
imageOrigin: NSMakePoint(0, 0)];
[marker setRepresentedObject: tab];
[markers addObject: marker];
RELEASE(marker);
}
return markers;
}
-(NSView *) rulerAccessoryViewForTextView: (NSTextView *)textView
paragraphStyle: (NSParagraphStyle *)style
ruler: (NSRulerView *)ruler
enabled: (BOOL)isEnabled
{
/* TODO */
return nil;
}
/*
TODO: not really clear what these should do
*/
-(void) invalidateDisplayForGlyphRange: (NSRange)aRange
{
int i;
unsigned int m;
NSRange r;
NSRect rect;
NSPoint p;
NSTextView *tv;
for (i = 0; i < num_textcontainers; i++)
{
if (!textcontainers[i].num_linefrags)
break;
if (textcontainers[i].pos >= aRange.location + aRange.length)
break; /* we're past the end of the range */
m = textcontainers[i].pos + textcontainers[i].length;
if (m < aRange.location)
continue;
r.location = textcontainers[i].pos;
if (aRange.location > r.location)
r.location = aRange.location;
if (m > aRange.location + aRange.length)
m = aRange.location + aRange.length;
r.length = m - r.location;
/* Range r in this text view should be invalidated. */
rect = [self boundingRectForGlyphRange: r
inTextContainer: textcontainers[i].textContainer];
tv = [textcontainers[i].textContainer textView];
p = [tv textContainerOrigin];
rect.origin.x += p.x;
rect.origin.y += p.y;
[tv setNeedsDisplayInRect: rect];
}
}
-(void) invalidateDisplayForCharacterRange: (NSRange)aRange
{
if (layout_char <= aRange.location)
return;
if (layout_char < aRange.location + aRange.length)
aRange.length = layout_char - aRange.location;
[self invalidateDisplayForGlyphRange:
[self glyphRangeForCharacterRange: aRange
actualCharacterRange: NULL]];
}
-(void) _didInvalidateLayout
{
unsigned int g;
int i;
/* Invalidate from the first glyph not laid out (which will
generally be the first glyph to have been invalidated). */
g = layout_glyph;
[super _didInvalidateLayout];
for (i = 0; i < num_textcontainers; i++)
{
if (textcontainers[i].complete &&
g < textcontainers[i].pos + textcontainers[i].length)
continue;
[[textcontainers[i].textContainer textView] _layoutManagerDidInvalidateLayout];
}
}
-(void) _dumpLayout
{
int i, j, k;
textcontainer_t *tc;
linefrag_t *lf;
linefrag_point_t *lp;
linefrag_attachment_t *la;
for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++)
{
printf("tc %2i, %5i+%5i (complete %i)\n",
i,tc->pos,tc->length,tc->complete);
printf(" lfs: (%3i)\n", tc->num_linefrags);
for (j = 0, lf = tc->linefrags; j < tc->num_linefrags; j++, lf++)
{
printf(" %3i : %5i+%5i (%g %g)+(%g %g)\n",
j,lf->pos,lf->length,
lf->rect.origin.x,lf->rect.origin.y,
lf->rect.size.width,lf->rect.size.height);
for (k = 0, lp = lf->points; k < lf->num_points; k++, lp++)
printf(" p%3i : %5i+%5i\n",k,lp->pos,lp->length);
for (k = 0, la = lf->attachments; k < lf->num_attachments; k++, la++)
printf(" a%3i : %5i+%5i\n",k,la->pos,la->length);
}
printf(" softs: (%3i)\n", tc->num_soft);
for (; j < tc->num_linefrags + tc->num_soft; j++, lf++)
{
printf(" %3i : %5i+%5i (%g %g)+(%g %g)\n",
j,lf->pos,lf->length,
lf->rect.origin.x,lf->rect.origin.y,
lf->rect.size.width,lf->rect.size.height);
for (k = 0, lp = lf->points; k < lf->num_points; k++, lp++)
printf(" p%3i : %5i+%5i\n",k,lp->pos,lp->length);
for (k = 0, la = lf->attachments; k < lf->num_attachments; k++, la++)
printf(" a%3i : %5i+%5i\n",k,la->pos,la->length);
}
}
printf("layout to: char %i, glyph %i\n",layout_char,layout_glyph);
}
/*
We completely override this method and use the extra information we have
about layout to do smarter invalidation. The comments at the beginning of
this file describes this.
*/
- (void) textStorage: (NSTextStorage *)aTextStorage
edited: (unsigned int)mask
range: (NSRange)range
changeInLength: (int)lengthChange
invalidatedRange: (NSRange)invalidatedRange
{
NSRange r;
unsigned int original_last_glyph;
/* printf("\n*** invalidating\n");
[self _dumpLayout];*/
/*
Using -glyphRangeForChara... here would be safer, but we must make
absolutely sure that we don't cause any glyph generation until the
invalidation is done.
TODO: make sure last_glyph is set as expected
*/
original_last_glyph = layout_glyph;
if (!(mask & NSTextStorageEditedCharacters))
lengthChange = 0;
if (_temporaryAttributes != nil && (mask & NSTextStorageEditedCharacters) != 0)
{
int i;
NSArray *attrs;
NSRange oldRange = NSMakeRange(range.location, range.length - lengthChange);
NSString *replacementString = [[GSDummyMutableString alloc] initWithLength: range.length];
[_temporaryAttributes replaceCharactersInRange: oldRange
withString: replacementString];
[replacementString release];
// In addition, clear any temporary attributes that may have been extended
// over the affected range
if (range.length > 0)
{
attrs = [[self temporaryAttributesAtCharacterIndex: range.location
effectiveRange: NULL] allKeys];
for (i=0; i<[attrs count]; i++)
{
[self removeTemporaryAttribute: [attrs objectAtIndex: i]
forCharacterRange: range];
}
}
}
[self invalidateGlyphsForCharacterRange: invalidatedRange
changeInLength: lengthChange
actualCharacterRange: &r];
/*
If we had layout information and we had layout information for the range
of characters that was modified, we need to invalidate layout information.
TODO: This is broken. Even if we don't have layout for the modified
characters, we might have layout for the preceeding line, and we then need
to invalidate that line. Need to rework this a bit... I really really need
to know the glyph length change here. :/
(Alexander Malmberg 2004-03-22)
*/
if (layout_char > 0 && layout_char >= r.location)
{
unsigned int glyph_index, last_glyph;
textcontainer_t *tc;
linefrag_t *lf;
int i, j, k;
int new_num;
NSRange char_range;
unsigned int new_last_glyph;
int glyph_delta;
/*
If we had layout beyond the modified characters, update layout_char.
Otherwise, just pretend that we have layout up to the end of the range
after the change. This will give glyph_delta and last_glyph incorrect
values, strictly speaking, but glyph_delta is only used if we have
layout beyond the modified range, and last_glyph is used in a way that
makes it safe to overestimate it (as we do here).
When I can get exact information about the modified glyphs (TODO above),
all this will become much cleaner...
*/
if (layout_char >= r.location + r.length - lengthChange)
layout_char += lengthChange;
else
layout_char = r.location + r.length;
if (!layout_char)
new_last_glyph = 0;
else if (layout_char >= [_textStorage length])
new_last_glyph = [self numberOfGlyphs];
else
new_last_glyph = [self glyphRangeForCharacterRange: NSMakeRange(layout_char, 1)
actualCharacterRange: NULL].location;
glyph_delta = new_last_glyph - original_last_glyph;
/*
Note that r.location might not actually have any text container or
line frag.
*/
if (!r.location)
{
glyph_index = 0;
}
else if (r.location == [_textStorage length])
{
/*
Since layout was built beyond r.location, glyphs must have been
too, so invalidation only removed trailing glyphs and we still
have glyphs built up to the end. Thus, -numberOfGlyphs is cheap
to call.
*/
glyph_index = [self numberOfGlyphs];
char_range.location = [_textStorage length];
}
else
{
/*
Will cause generation of glyphs, but I consider that acceptable
for now. Soft-invalidation will cause even more glyph generation,
anyway.
*/
glyph_index =
[self glyphRangeForCharacterRange: NSMakeRange(r.location,1)
actualCharacterRange: &char_range].location;
}
/*
For soft invalidation, we need to know where to stop hard-invalidating.
This will cause immediate glyph generation to fill the gaps the
invalidation caused.
*/
if (NSMaxRange(r) == [_textStorage length])
{
last_glyph = [self numberOfGlyphs];
}
else
{
last_glyph =
[self glyphRangeForCharacterRange: NSMakeRange(NSMaxRange(r),1)
actualCharacterRange: NULL].location;
}
last_glyph -= glyph_delta;
/* glyph_index is the first index we should invalidate for. */
for (j = 0, tc = textcontainers; j < num_textcontainers; j++, tc++)
if (tc->pos + tc->length >= glyph_index)
break;
LINEFRAG_FOR_GLYPH(glyph_index);
/*
We invalidate the entire line containing lf, and the entire
previous line. Thus, we scan backwards to find the first line frag
on the previous line.
*/
while (i > 0 && lf[-1].rect.origin.y == lf->rect.origin.y)
lf--, i--;
/* Now we have the first line frag on this line. */
if (i > 0)
{
lf--, i--;
}
else
{
/*
The previous line isn't in this text container, so we move
to the previous text container.
*/
if (j > 0)
{
j--;
tc--;
i = tc->num_linefrags - 1;
lf = tc->linefrags + i;
}
}
/* Last line frag on previous line. */
while (i > 0 && lf[-1].rect.origin.y == lf->rect.origin.y)
lf--, i--;
/* First line frag on previous line. */
/* Invalidate all line frags that intersect the invalidated range. */
new_num = i;
while (1)
{
for (; i < tc->num_linefrags + tc->num_soft; i++, lf++)
{
/*
Since we must invalidate whole lines, we can only stop if
the line frag is beyond the invalidated range, and the line
frag is the first line frag in a line.
*/
if (lf->pos >= last_glyph &&
(!i || lf[-1].rect.origin.y != lf->rect.origin.y))
{
break;
}
if (lf->points)
{
free(lf->points);
lf->points = NULL;
}
if (lf->attachments)
{
free(lf->attachments);
lf->attachments = NULL;
}
}
if (i < tc->num_linefrags + tc->num_soft)
break;
tc->num_linefrags = new_num;
tc->num_soft = 0;
tc->was_invalidated = YES;
tc->complete = NO;
if (new_num)
{
tc->length = tc->linefrags[new_num-1].pos + tc->linefrags[new_num-1].length - tc->pos;
}
else
{
tc->pos = tc->length = 0;
}
j++, tc++;
if (j == num_textcontainers)
break;
new_num = 0;
i = 0;
lf = tc->linefrags;
}
if (j == num_textcontainers)
goto no_soft_invalidation;
if (new_num != i)
{
/*
There's a gap between the last valid line frag and the first
soft line frag. Compact the linefrags.
*/
memmove(tc->linefrags + new_num, lf, sizeof(linefrag_t) * (tc->num_linefrags + tc->num_soft - i));
tc->num_linefrags -= i - new_num;
i = new_num;
lf = tc->linefrags + i;
}
tc->num_soft += tc->num_linefrags - new_num;
tc->num_linefrags = new_num;
tc->was_invalidated = YES;
tc->complete = NO;
if (new_num)
{
tc->length = tc->linefrags[new_num - 1].pos + tc->linefrags[new_num - 1].length - tc->pos;
}
else
{
tc->pos = tc->length = 0;
}
/*
Soft invalidate all remaining layout. Update their glyph positions
and set the soft-invalidate markers in the text containers.
*/
while (1)
{
for (; i < tc->num_linefrags + tc->num_soft; i++, lf++)
{
lf->pos += glyph_delta;
for (k = 0; k < lf->num_points; k++)
lf->points[k].pos += glyph_delta;
for (k = 0; k < lf->num_attachments; k++)
lf->attachments[k].pos += glyph_delta;
}
j++, tc++;
if (j == num_textcontainers)
break;
i = 0;
lf = tc->linefrags;
tc->num_soft += tc->num_linefrags;
tc->num_linefrags = 0;
tc->was_invalidated = YES;
tc->complete = NO;
}
no_soft_invalidation:
/* Set layout_glyph and layout_char. */
for (i = num_textcontainers - 1, tc = textcontainers + i; i >= 0; i--, tc--)
{
if (tc->num_linefrags)
{
layout_glyph = tc->pos + tc->length;
if (layout_glyph == glyphs->glyph_length)
layout_char = glyphs->char_length;
else
layout_char = [self characterIndexForGlyphAtIndex: layout_glyph]; /* TODO? */
break;
}
}
if (i < 0)
layout_glyph = layout_char = 0;
}
else
{
int i, j;
linefrag_t *lf;
textcontainer_t *tc;
/*
TODO: could handle this better, but it should be a rare case,
handling it efficiently is tricky.
For now, we simply clear out all soft invalidation information.
*/
for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++)
{
for (j = 0, lf = tc->linefrags + tc->num_linefrags; j < tc->num_soft; j++, lf++)
{
if (lf->points)
{
free(lf->points);
lf->points = NULL;
}
if (lf->attachments)
{
free(lf->attachments);
lf->attachments = NULL;
}
}
tc->num_soft = 0;
if (tc->pos + tc->length == r.location)
{
tc->complete = NO;
}
}
}
/* Clear the extra line fragment information. */
extra_textcontainer = nil;
/* [self _dumpLayout];
printf("*** done\n");*/
[self _didInvalidateLayout];
if (mask & NSTextStorageEditedCharacters)
{
/*
Adjust the selected range so it's still valid. We don't try to
be smart here (smart adjustments will have to be done by whoever
made the change), we just want to keep it in range to avoid crashes.
TODO: It feels slightly ugly to be doing this here, but there aren't
many other places that can do this, and it gives reasonable behavior
for select-only text views.
One option is to only adjust when absolutely necessary to keep the
selected range valid.
Current behavior for all cases:
Start End Action
(of selection, wrt range, before change)
--------------------------
after after location += lengthChange;
in after length = NSMaxRange(sel)-(NSMaxRange(range)-lengthChange); location=NSMaxRange(range);
in in length = 0; location=NSMaxRange(range);
before after length += lengthChange;
before in length = range.location-location;
before before do nothing
In other words, unless the selection spans over the entire changed
range, the changed range is deselected.
One important property of this behavior is that if length is 0 before,
it will be 0 after.
*/
NSRange newRange = _selected_range;
if (_selected_range.location >= NSMaxRange(range) - lengthChange)
{ /* after after */
newRange.location += lengthChange;
}
else if (_selected_range.location >= range.location)
{
if (NSMaxRange(_selected_range) > NSMaxRange(range) - lengthChange)
{ /* in after */
newRange.length = NSMaxRange(_selected_range) - (NSMaxRange(range) - lengthChange);
newRange.location = NSMaxRange(range);
}
else
{ /* in in */
newRange.length = 0;
newRange.location = NSMaxRange(range);
}
}
else if (NSMaxRange(_selected_range) > NSMaxRange(range) - lengthChange)
{ /* before after */
newRange.length += lengthChange;
}
else if (NSMaxRange(_selected_range) > range.location)
{ /* before in */
newRange.length = range.location - _selected_range.location;
}
else
{ /* before before */
}
/* sanity check */
if (NSMaxRange(newRange) > [_textStorage length])
{
newRange = NSMakeRange(MIN(range.location, [_textStorage length]), 0);
}
2005-01-21 21:43 Alexander Malmberg <alexander@malmberg.org> Various whitespace cleanups, comment type fixes, and changes to avoid warnings from recent versions of gcc. * Headers/Additions/GNUstepGUI/GSToolbar.h (-_toolbars): Declare. * Source/NSWindow+Toolbar.m: Remove conflicting declaration of [NSToolbar -_toolbars]. * Headers/Additions/GNUstepGUI/GSServicesManager.h, Source/GSServicesMananger.m (-item2title:, -validateMenuItem:): Adjust argument types. * Headers/AppKit/NSMenu.h (-validateMenuItem:): Adjust argument type. * Source/NSTextView.m (-sizeToFit): Don't use size uninitialized if neither resizable flags is set. (-insertText:): Adjust argument type. * Headers/AppKit/NSResponder.h, Source/NSResponder.m (-insertText:): Adjust argument type. Document. * Headers/AppKit/NSView.h: Change type of ivar _window to NSWindow *. * Source/GSTitleView.m (-mouseDown:): Always initialize startWindowOrigin. * Source/NSApplication.m (-setApplicationIconImage:): Add casts to avoid warnings. * Source/NSCell.m (-cellSize): Add default: case. * Source/NSPasteboard.m ([GSFiltered -pasteboard:provideDataForType:]): Detect and warn if we can't find a filter that will get us the desired type. * Source/NSProgressIndicator.m: Comment out unused variable 'images'. * Source/NSBezierPath.m: Declare GSBezierPath fully before using it. (-bezierPathByFlatteningPath, -bezierPathByReversingPath): Make sure variables are always initialized. * Source/NSMenuView.m, * Source/NSPrintOperation.m, * Source/NSSplitView.m, * Source/NSTableHeaderView.m: Make sure variables are always initialized. * Source/NSBox.m, * Source/NSImageview.m, * Source/NSText.m, * Source/NSTextStorage.m: Add missing includes. * Source/GSKeyBindingTable.m, * Source/GSLayoutManager.m, * Source/NSBitmapImageRep+PNM.m, * Source/NSBundleAdditions.m, * Source/NSLayoutManager.m, * Source/nsimage-tiff.h, * Source/tiff.m, * Headers/Additions/GNUstepGUI/GSDisplayServer.h, * Source/GSDisplayServer.m: Change signedness of various variables. * Source/NSPanel.m (-sendEvent:): Remove. * Source/NSWindow.m (-becomesKeyOnlyIfNeeded): New method. (-_sendEvent:becomesKeyOnlyIfNeeded:): Remove. Move code ... (-sendEvent:): ... here. Use -becomesKeyOnlyIfNeeded instead of the argument. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@20590 72102866-910b-0410-8b05-ffd578937521
2005-01-21 20:39:18 +00:00
/* If there are text views attached to us, let them handle the
change. */
if ([self firstTextView])
[[self firstTextView] setSelectedRange: newRange];
else
_selected_range = newRange;
}
}
- (void) encodeWithCoder: (NSCoder*)aCoder
{
if ([aCoder allowsKeyedCoding])
{
int flags =
// FIXME attribute not yet supported by GNUstep
//defaultAttachementScaling |
(backgroundLayoutEnabled ? 0x04 : 0) |
(showsInvisibleCharacters ? 0x08 : 0) |
(showsControlCharacters ? 0x10 : 0);
[aCoder encodeObject: [self textContainers] forKey: @"NSTextContainers"];
[aCoder encodeObject: [self textStorage] forKey: @"NSTextStorage"];
[aCoder encodeObject: [self delegate] forKey: @"NSDelegate"];
[aCoder encodeInt: flags forKey: @"NSLMFlags"];
}
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
self = [self init];
if ([aDecoder allowsKeyedCoding])
{
int i;
int flags;
NSArray *array = [aDecoder decodeObjectForKey: @"NSTextContainers"];
NSTextStorage *storage = [aDecoder decodeObjectForKey: @"NSTextStorage"];
id delegate = [aDecoder decodeObjectForKey: @"NSDelegate"];
if ([aDecoder containsValueForKey: @"NSLMFlags"])
{
flags = [aDecoder decodeIntForKey: @"NSLMFlags"];
// FIXME attribute not yet supported by GNUstep
//defaultAttachementScaling = (NSImageScaling)(flags & 0x03);
backgroundLayoutEnabled = (flags & 0x04) != 0;
showsInvisibleCharacters = (flags & 0x08) != 0;
showsControlCharacters = (flags & 0x10) != 0;
}
[self setDelegate: delegate];
[storage addLayoutManager: self];
for (i = 0; i < [array count]; i++)
{
[self addTextContainer: [array objectAtIndex: i]];
}
return self;
}
else
{
return self;
}
}
- (NSDictionary *) typingAttributes
{
return _typingAttributes;
}
@end
@implementation GSDummyMutableString
- (id)initWithLength: (NSUInteger)aLength
{
self = [super init];
if (self != nil)
{
self->_length = aLength;
}
return self;
}
- (NSUInteger)length
{
return _length;
}
- (unichar)characterAtIndex: (NSUInteger)index
{
return 0;
}
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString
{
_length = (_length - range.length) + [aString length];
}
- (id) copyWithZone: (NSZone*)zone
{
return [self mutableCopyWithZone: zone];
}
- (id) mutableCopyWithZone: (NSZone*)zone
{
return [[GSDummyMutableString allocWithZone: zone] initWithLength: _length];
}
@end
@implementation NSLayoutManager (temporaryattributes)
- (NSMutableAttributedString*) _temporaryAttributes
{
if (_temporaryAttributes == nil)
{
NSString *dummyString = [[GSDummyMutableString alloc] initWithLength: [[self textStorage] length]];
_temporaryAttributes = [[NSMutableAttributedString alloc] initWithString: dummyString];
[dummyString release];
}
return _temporaryAttributes;
}
- (void) setTemporaryAttributes: (NSDictionary *)attrs
forCharacterRange: (NSRange)range
{
[[self _temporaryAttributes] setAttributes: attrs range: range];
[self invalidateDisplayForCharacterRange: range];
}
- (NSDictionary *) temporaryAttributesAtCharacterIndex: (NSUInteger)index
effectiveRange: (NSRange*)longestRange
{
return [[self _temporaryAttributes] attributesAtIndex: index effectiveRange: longestRange];
}
- (void) addTemporaryAttributes: (NSDictionary *)attrs
forCharacterRange: (NSRange)range
{
[[self _temporaryAttributes] addAttributes: attrs range: range];
[self invalidateDisplayForCharacterRange: range];
}
- (void) addTemporaryAttribute: (NSString *)attr
value: (id)value
forCharacterRange: (NSRange)range
{
[[self _temporaryAttributes] addAttribute: attr value: value range: range];
[self invalidateDisplayForCharacterRange: range];
}
- (void) removeTemporaryAttribute: (NSString *)attr
forCharacterRange: (NSRange)range
{
[[self _temporaryAttributes] removeAttribute: attr range: range];
[self invalidateDisplayForCharacterRange: range];
}
- (id) temporaryAttribute: (NSString *)attr
atCharacterIndex: (NSUInteger)index
effectiveRange: (NSRange*)range
{
return [[self _temporaryAttributes] attribute: attr atIndex: index effectiveRange: range];
}
- (id) temporaryAttribute: (NSString *)attr
atCharacterIndex: (NSUInteger)index
longestEffectiveRange: (NSRange*)longestRange
inRange: (NSRange)range
{
return [[self _temporaryAttributes] attribute: attr atIndex: index longestEffectiveRange: longestRange inRange: range];
}
- (NSDictionary *) temporaryAttributesAtCharacterIndex: (NSUInteger)index
longestEffectiveRange: (NSRange*)longestRange
inRange: (NSRange)range
{
return [[self _temporaryAttributes] attributesAtIndex: index longestEffectiveRange: longestRange inRange: range];
}
/**
* Most of this is implemented in GSLayoutManager
*/
- (void) setTextStorage: (NSTextStorage *)aTextStorage
{
DESTROY(_temporaryAttributes);
[super setTextStorage: aTextStorage];
}
@end