Attributed string fixes

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@6238 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
richard 2000-03-09 19:06:49 +00:00
parent 78b3506b6f
commit 0fab02ec02
3 changed files with 256 additions and 114 deletions

View file

@ -1,3 +1,8 @@
Thu Mar 09 18:07:00 2000 Richard Frith-Macdonald <richard@brainstorm.co.uk>
* Source/NSAttributedString.m: Varius bugfixes.
* Source/NSGAttributedString.m: ditto.
2000-03-07 Adam Fedor <fedor@gnu.org> 2000-03-07 Adam Fedor <fedor@gnu.org>
* configure.in: Remove obsolete header checks for Time class. * configure.in: Remove obsolete header checks for Time class.

View file

@ -33,7 +33,7 @@
/* Warning - [-initWithString:attributes:] is the designated initialiser, /* Warning - [-initWithString:attributes:] is the designated initialiser,
* but it doesn't provide any way to perform the function of the * but it doesn't provide any way to perform the function of the
* [-initWithAttributedString:] initialiser. * [-initWithAttributedString:] initialiser.
* In order to work youd this, the string argument of the * In order to work round this, the string argument of the
* designated initialiser has been overloaded such that it * designated initialiser has been overloaded such that it
* is expected to accept an NSAttributedString here instead of * is expected to accept an NSAttributedString here instead of
* a string. If you create an NSAttributedString subclass, you * a string. If you create an NSAttributedString subclass, you
@ -205,34 +205,41 @@ static Class NSMutableAttributedString_concrete_class;
if (rangeLimit.location < 0 || NSMaxRange(rangeLimit) > [self length]) if (rangeLimit.location < 0 || NSMaxRange(rangeLimit) > [self length])
{ {
[NSException raise: NSRangeException format: [NSException raise: NSRangeException
@"RangeError in method -attributesAtIndex: longestEffectiveRange: inRange: in class NSAttributedString"]; format: @"RangeError in method -attributesAtIndex:longestEffectiveRange:inRange: in class NSAttributedString"];
} }
attrDictionary = [self attributesAtIndex: index effectiveRange: aRange]; attrDictionary = [self attributesAtIndex: index effectiveRange: aRange];
if (!aRange) if (aRange == 0)
return attrDictionary; return attrDictionary;
while (aRange->location > rangeLimit.location) while (aRange->location > rangeLimit.location)
{ {
//Check extend range backwards //Check extend range backwards
tmpDictionary = tmpDictionary = [self attributesAtIndex: aRange->location-1
[self attributesAtIndex: aRange->location-1 effectiveRange: &tmpRange];
effectiveRange: &tmpRange];
if ([tmpDictionary isEqualToDictionary: attrDictionary]) if ([tmpDictionary isEqualToDictionary: attrDictionary])
aRange->location = tmpRange.location; {
aRange->length = NSMaxRange(*aRange) - tmpRange.location;
aRange->location = tmpRange.location;
}
else else
break; {
break;
}
} }
while (NSMaxRange(*aRange) < NSMaxRange(rangeLimit)) while (NSMaxRange(*aRange) < NSMaxRange(rangeLimit))
{ {
//Check extend range forwards //Check extend range forwards
tmpDictionary = tmpDictionary = [self attributesAtIndex: NSMaxRange(*aRange)
[self attributesAtIndex: NSMaxRange(*aRange) effectiveRange: &tmpRange];
effectiveRange: &tmpRange];
if ([tmpDictionary isEqualToDictionary: attrDictionary]) if ([tmpDictionary isEqualToDictionary: attrDictionary])
aRange->length = NSMaxRange(tmpRange) - aRange->location; {
aRange->length = NSMaxRange(tmpRange) - aRange->location;
}
else else
break; {
break;
}
} }
*aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit *aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit
return attrDictionary; return attrDictionary;
@ -246,65 +253,78 @@ static Class NSMutableAttributedString_concrete_class;
id attrValue; id attrValue;
tmpDictionary = [self attributesAtIndex: index effectiveRange: aRange]; tmpDictionary = [self attributesAtIndex: index effectiveRange: aRange];
//Raises exception if index is out of range, so that I don't have to test this...
if (!attributeName) if (attributeName == nil)
{ {
if (aRange) if (aRange != 0)
*aRange = NSMakeRange(0,[self length]); {
//If attributeName is nil, then the attribute will not exist in the *aRange = NSMakeRange(0,[self length]);
//entire text - therefore aRange of the entire text must be correct /*
* If attributeName is nil, then the attribute will not exist in the
* entire text - therefore aRange of the entire text must be correct
*/
}
return nil; return nil;
} }
attrValue = [tmpDictionary objectForKey: attributeName]; attrValue = [tmpDictionary objectForKey: attributeName];
return attrValue; return attrValue;
} }
- (id) attribute: (NSString*)attributeName atIndex: (unsigned int)index - (id) attribute: (NSString*)attributeName
longestEffectiveRange: (NSRange*)aRange inRange: (NSRange)rangeLimit atIndex: (unsigned int)index
longestEffectiveRange: (NSRange*)aRange
inRange: (NSRange)rangeLimit
{ {
NSDictionary *tmpDictionary; NSDictionary *tmpDictionary;
id attrValue,tmpAttrValue; id attrValue;
NSRange tmpRange; id tmpAttrValue;
NSRange tmpRange;
if (rangeLimit.location < 0 || NSMaxRange(rangeLimit) > [self length]) if (rangeLimit.location < 0 || NSMaxRange(rangeLimit) > [self length])
{ {
[NSException raise: NSRangeException format: [NSException raise: NSRangeException
@"RangeError in method -attribute: atIndex: longestEffectiveRange: inRange: in class NSAttributedString"]; format: @"RangeError in method -attribute:atIndex:longestEffectiveRange:inRange: in class NSAttributedString"];
} }
attrValue = [self attribute: attributeName atIndex: index effectiveRange: aRange]; attrValue = [self attribute: attributeName
//Raises exception if index is out of range, so that I don't have to test this... atIndex: index
effectiveRange: aRange];
if (!attributeName) if (attributeName == nil)
return nil;//attribute: atIndex: effectiveRange: handles this case... return nil;
if (!aRange) if (aRange == 0)
return attrValue; return attrValue;
while (aRange->location > rangeLimit.location) while (aRange->location > rangeLimit.location)
{ {
//Check extend range backwards //Check extend range backwards
tmpDictionary = tmpDictionary = [self attributesAtIndex: aRange->location-1
[self attributesAtIndex: aRange->location-1 effectiveRange: &tmpRange];
effectiveRange: &tmpRange];
tmpAttrValue = [tmpDictionary objectForKey: attributeName]; tmpAttrValue = [tmpDictionary objectForKey: attributeName];
if (tmpAttrValue == attrValue) if (tmpAttrValue == attrValue)
aRange->location = tmpRange.location; {
aRange->length = NSMaxRange(*aRange) - tmpRange.location;
aRange->location = tmpRange.location;
}
else else
break; {
break;
}
} }
while (NSMaxRange(*aRange) < NSMaxRange(rangeLimit)) while (NSMaxRange(*aRange) < NSMaxRange(rangeLimit))
{ {
//Check extend range forwards //Check extend range forwards
tmpDictionary = tmpDictionary = [self attributesAtIndex: NSMaxRange(*aRange)
[self attributesAtIndex: NSMaxRange(*aRange) effectiveRange: &tmpRange];
effectiveRange: &tmpRange];
tmpAttrValue = [tmpDictionary objectForKey: attributeName]; tmpAttrValue = [tmpDictionary objectForKey: attributeName];
if (tmpAttrValue == attrValue) if (tmpAttrValue == attrValue)
aRange->length = NSMaxRange(tmpRange) - aRange->location; {
aRange->length = NSMaxRange(tmpRange) - aRange->location;
}
else else
break; {
break;
}
} }
*aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit *aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit
return attrValue; return attrValue;
@ -328,9 +348,9 @@ static Class NSMutableAttributedString_concrete_class;
return YES; return YES;
ownDictionary = [self attributesAtIndex: 0 ownDictionary = [self attributesAtIndex: 0
effectiveRange: &ownEffectiveRange]; effectiveRange: &ownEffectiveRange];
otherDictionary = [otherString attributesAtIndex: 0 otherDictionary = [otherString attributesAtIndex: 0
effectiveRange: &otherEffectiveRange]; effectiveRange: &otherEffectiveRange];
result = YES; result = YES;
while (YES) while (YES)
@ -343,14 +363,15 @@ static Class NSMutableAttributedString_concrete_class;
} }
if (NSMaxRange(ownEffectiveRange) < NSMaxRange(otherEffectiveRange)) if (NSMaxRange(ownEffectiveRange) < NSMaxRange(otherEffectiveRange))
{ {
ownDictionary = [self ownDictionary = [self attributesAtIndex: NSMaxRange(ownEffectiveRange)
attributesAtIndex: NSMaxRange(ownEffectiveRange) effectiveRange: &ownEffectiveRange];
effectiveRange: &ownEffectiveRange];
} }
else else
{ {
if (NSMaxRange(otherEffectiveRange) >= length) if (NSMaxRange(otherEffectiveRange) >= length)
break;//End of strings {
break;//End of strings
}
otherDictionary = [otherString otherDictionary = [otherString
attributesAtIndex: NSMaxRange(otherEffectiveRange) attributesAtIndex: NSMaxRange(otherEffectiveRange)
effectiveRange: &otherEffectiveRange]; effectiveRange: &otherEffectiveRange];

View file

@ -47,9 +47,12 @@
#include <Foundation/NSGAttributedString.h> #include <Foundation/NSGAttributedString.h>
#include <Foundation/NSException.h> #include <Foundation/NSException.h>
#include <Foundation/NSRange.h> #include <Foundation/NSRange.h>
#include <Foundation/NSDebug.h>
#include <base/NSGArray.h> #include <base/NSGArray.h>
#include <base/fast.x> #include <base/fast.x>
#define SANITY_CHECKS 0
@interface GSAttrInfo : NSObject @interface GSAttrInfo : NSObject
{ {
@public @public
@ -78,6 +81,12 @@
NSDeallocateObject(self); NSDeallocateObject(self);
} }
- (NSString*) description
{
return [NSString stringWithFormat: @"Attributes at %u are - %@",
loc, attrs];
}
- (Class) classForPortCoder - (Class) classForPortCoder
{ {
return [self class]; return [self class];
@ -198,25 +207,35 @@ _attributesAtIndexEffectiveRange(
unsigned low, high, used, cnt, nextLoc; unsigned low, high, used, cnt, nextLoc;
GSAttrInfo *found = nil; GSAttrInfo *found = nil;
used = (*cntImp)(_infoArray, cntSel);
NSCAssert(used > 0, NSInternalInconsistencyException);
high = used - 1;
if (index >= tmpLength) if (index >= tmpLength)
{ {
if (index == tmpLength) if (index == tmpLength)
{ {
*foundIndex = index; found = OBJECTAT(high);
return nil; if (foundIndex != 0)
{
*foundIndex = high;
}
if (aRange != 0)
{
aRange->location = found->loc;
aRange->length = tmpLength - found->loc;
}
return found->attrs;
} }
[NSException raise: NSRangeException [NSException raise: NSRangeException
format: @"index is out of range in function " format: @"index is out of range in function "
@"_attributesAtIndexEffectiveRange()"]; @"_attributesAtIndexEffectiveRange()"];
} }
used = (*cntImp)(_infoArray, cntSel);
/* /*
* Binary search for efficiency in huge attributed strings * Binary search for efficiency in huge attributed strings
*/ */
low = 0; low = 0;
high = used - 1;
while (low <= high) while (low <= high)
{ {
cnt = (low + high) / 2; cnt = (low + high) / 2;
@ -240,12 +259,12 @@ _attributesAtIndexEffectiveRange(
if (found->loc == index || index < nextLoc) if (found->loc == index || index < nextLoc)
{ {
//Found //Found
if (aRange) if (aRange != 0)
{ {
aRange->location = found->loc; aRange->location = found->loc;
aRange->length = nextLoc - found->loc; aRange->length = nextLoc - found->loc;
} }
if (foundIndex) if (foundIndex != 0)
{ {
*foundIndex = cnt; *foundIndex = cnt;
} }
@ -343,6 +362,33 @@ _attributesAtIndexEffectiveRange(
@implementation NSGMutableAttributedString @implementation NSGMutableAttributedString
#if SANITY_CHECKS
#define SANITY() [self sanity]
- (void) sanity
{
GSAttrInfo *info;
unsigned i;
unsigned l = 0;
unsigned len = [_textChars length];
unsigned c = (*cntImp)(_infoArray, cntSel);
NSAssert(c > 0, NSInternalInconsistencyException);
info = OBJECTAT(0);
NSAssert(info->loc == 0, NSInternalInconsistencyException);
for (i = 1; i < c; i++)
{
info = OBJECTAT(i);
NSAssert(info->loc > l, NSInternalInconsistencyException);
NSAssert(info->loc <= len, NSInternalInconsistencyException);
l = info->loc;
}
}
#else
#define SANITY()
#endif
+ (void) initialize + (void) initialize
{ {
_setup(); _setup();
@ -385,6 +431,7 @@ _attributesAtIndexEffectiveRange(
aString = [as string]; aString = [as string];
_setAttributesFrom(as, NSMakeRange(0, [aString length]), _infoArray); _setAttributesFrom(as, NSMakeRange(0, [aString length]), _infoArray);
SANITY();
} }
else else
{ {
@ -424,51 +471,72 @@ _attributesAtIndexEffectiveRange(
NSZone *z = fastZone(self); NSZone *z = fastZone(self);
GSAttrInfo *info; GSAttrInfo *info;
if (!attributes) if (range.length == 0)
attributes = [NSDictionary dictionary]; {
NSWarnMLog(@"Attempt to set attribute for zero-length range", 0);
return;
}
if (attributes == nil)
{
attributes = [NSDictionary dictionary];
}
SANITY();
tmpLength = [_textChars length]; tmpLength = [_textChars length];
GS_RANGE_CHECK(range, tmpLength); GS_RANGE_CHECK(range, tmpLength);
arraySize = (*cntImp)(_infoArray, cntSel); arraySize = (*cntImp)(_infoArray, cntSel);
if (NSMaxRange(range) < tmpLength) beginRangeLoc = range.location;
afterRangeLoc = NSMaxRange(range);
if (afterRangeLoc < tmpLength)
{ {
/*
* Locate the first range that extends beyond our range.
*/
attrs = _attributesAtIndexEffectiveRange( attrs = _attributesAtIndexEffectiveRange(
NSMaxRange(range), &effectiveRange, tmpLength, _infoArray, &arrayIndex); afterRangeLoc, &effectiveRange, tmpLength, _infoArray, &arrayIndex);
if (effectiveRange.location > beginRangeLoc)
afterRangeLoc = NSMaxRange(range);
if (effectiveRange.location > range.location)
{ {
/*
* The located range also starts at or after our range.
*/
info = OBJECTAT(arrayIndex); info = OBJECTAT(arrayIndex);
info->loc = afterRangeLoc; info->loc = afterRangeLoc;
arrayIndex--;
} }
else else if (effectiveRange.location < beginRangeLoc)
{ {
/*
* The located range starts before our range.
* Create a subrange to go from our end to the end of the old range.
*/
info = NEWINFO(z, attrs, afterRangeLoc); info = NEWINFO(z, attrs, afterRangeLoc);
arrayIndex++; arrayIndex++;
INSOBJECT(info, arrayIndex); INSOBJECT(info, arrayIndex);
RELEASE(info); RELEASE(info);
arrayIndex--;
} }
arrayIndex--;
} }
else else
{ {
arrayIndex = arraySize - 1; arrayIndex = arraySize - 1;
} }
/*
* Remove any ranges completely within ours
*/
while (arrayIndex > 0) while (arrayIndex > 0)
{ {
info = OBJECTAT(arrayIndex-1); info = OBJECTAT(arrayIndex-1);
if (info->loc < range.location) if (info->loc < beginRangeLoc)
break; break;
REMOVEAT(arrayIndex); REMOVEAT(arrayIndex);
arrayIndex--; arrayIndex--;
} }
beginRangeLoc = range.location;
info = OBJECTAT(arrayIndex); info = OBJECTAT(arrayIndex);
location = info->loc; location = info->loc;
if (location >= range.location) if (location >= beginRangeLoc)
{ {
if (location > range.location) if (location > beginRangeLoc)
{ {
info->loc = beginRangeLoc; info->loc = beginRangeLoc;
} }
@ -482,6 +550,7 @@ _attributesAtIndexEffectiveRange(
RELEASE(info); RELEASE(info);
} }
SANITY();
/* /*
* Primitive method! Sets attributes and values for a given range of * Primitive method! Sets attributes and values for a given range of
* characters, replacing any previous attributes and values for that * characters, replacing any previous attributes and values for that
@ -500,71 +569,118 @@ _attributesAtIndexEffectiveRange(
- (void) replaceCharactersInRange: (NSRange)range - (void) replaceCharactersInRange: (NSRange)range
withString: (NSString*)aString withString: (NSString*)aString
{ {
unsigned tmpLength, arrayIndex, arraySize, cnt, moveLocations; unsigned tmpLength, arrayIndex, arraySize;
NSRange effectiveRange; NSRange effectiveRange;
NSDictionary *attrs; NSDictionary *attrs;
unsigned afterRangeLoc;
GSAttrInfo *info; GSAttrInfo *info;
int moveLocations;
NSZone *z = fastZone(self); NSZone *z = fastZone(self);
unsigned start;
if (!aString) SANITY();
aString = @""; if (aString == nil)
{
aString = @"";
}
tmpLength = [_textChars length]; tmpLength = [_textChars length];
GS_RANGE_CHECK(range, tmpLength); GS_RANGE_CHECK(range, tmpLength);
arraySize = (*cntImp)(_infoArray, cntSel); if (range.location == tmpLength)
if (NSMaxRange(range) < tmpLength)
{ {
attrs = _attributesAtIndexEffectiveRange(
NSMaxRange(range), &effectiveRange, tmpLength, _infoArray, &arrayIndex);
moveLocations = [aString length] - range.length;
afterRangeLoc = NSMaxRange(range) + moveLocations;
if (effectiveRange.location > range.location)
{
info = OBJECTAT(arrayIndex);
info->loc = afterRangeLoc;
}
else
{
info = NEWINFO(z, attrs, afterRangeLoc);
arrayIndex++;
INSOBJECT(info, arrayIndex);
arraySize++;
RELEASE(info);
}
/* /*
* Everything after our modified range need to be shifted. * Special case - replacing a zero length string at the end
* simply appends the new string and attributes are inherited.
*/ */
if (arrayIndex + 1 < arraySize) [_textChars appendString: aString];
SANITY();
return;
}
arraySize = (*cntImp)(_infoArray, cntSel);
if (arraySize == 1)
{
/*
* Special case - if the string has only one set of attributes
* then the replacement characters will get them too.
*/
[_textChars replaceCharactersInRange: range withString: aString];
SANITY();
return;
}
/*
* Get the attributes to associate with our replacement string.
* Should be those of the first character replaced.
* If the range replaced is empty, we use the attributes of the
* previous character (if possible).
*/
if (range.length == 0 && range.location > 0)
start = range.location - 1;
else
start = range.location;
attrs = _attributesAtIndexEffectiveRange(start, &effectiveRange,
tmpLength, _infoArray, &arrayIndex);
arrayIndex++;
if (NSMaxRange(effectiveRange) > NSMaxRange(range))
{
info = NEWINFO(z, attrs, NSMaxRange(range));
INSOBJECT(info, arrayIndex);
arraySize++;
}
else if (NSMaxRange(effectiveRange) < NSMaxRange(range))
{
/*
* Remove all range info for ranges enclosed within the one
* we are replacing. Adjust the start point of a range that
* extends beyond ours.
*/
info = OBJECTAT(arrayIndex);
if (info->loc < NSMaxRange(range))
{ {
unsigned l = arraySize - arrayIndex - 1; int next = arrayIndex + 1;
NSRange r = NSMakeRange(arrayIndex + 1, l);
GSAttrInfo *objs[l]; while (next < arraySize)
[_infoArray getObjects: objs range: r];
for (cnt = 0; cnt < l; cnt++)
{ {
objs[cnt]->loc += moveLocations; GSAttrInfo *n = OBJECTAT(next);
if (n->loc <= NSMaxRange(range))
{
REMOVEAT(arrayIndex);
arraySize--;
info = n;
}
break;
} }
} }
arrayIndex--; info->loc = NSMaxRange(range);
}
else
{
arrayIndex = arraySize - 1;
} }
while (arrayIndex > 0) moveLocations = [aString length] - range.length;
if (effectiveRange.location == range.location
&& (moveLocations + range.length) == 0)
{
/*
* If we are replacing a range with a zero length string and the
* range we are using matches the range replaced, then we must
* remove it from the array to avoid getting a zero length range.
*/
arrayIndex--;
REMOVEAT(arrayIndex);
arraySize--;
}
SANITY();
/*
* Now adjust the positions of the ranges following the one we are using.
*/
while (arrayIndex < arraySize)
{ {
info = OBJECTAT(arrayIndex); info = OBJECTAT(arrayIndex);
if (info->loc <= range.location) info->loc += moveLocations;
break; arrayIndex++;
REMOVEAT(arrayIndex);
arrayIndex--;
} }
SANITY();
[_textChars replaceCharactersInRange: range withString: aString]; [_textChars replaceCharactersInRange: range withString: aString];
SANITY();
} }
- (void) dealloc - (void) dealloc