From 0fab02ec024aad698fa03573075edc6716620f12 Mon Sep 17 00:00:00 2001 From: richard Date: Thu, 9 Mar 2000 19:06:49 +0000 Subject: [PATCH] Attributed string fixes git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@6238 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 5 + Source/NSAttributedString.m | 119 ++++++++++------- Source/NSGAttributedString.m | 246 ++++++++++++++++++++++++++--------- 3 files changed, 256 insertions(+), 114 deletions(-) diff --git a/ChangeLog b/ChangeLog index b9fa2ed8b..7933557ee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Thu Mar 09 18:07:00 2000 Richard Frith-Macdonald + + * Source/NSAttributedString.m: Varius bugfixes. + * Source/NSGAttributedString.m: ditto. + 2000-03-07 Adam Fedor * configure.in: Remove obsolete header checks for Time class. diff --git a/Source/NSAttributedString.m b/Source/NSAttributedString.m index b4d94fb41..8792c34d0 100644 --- a/Source/NSAttributedString.m +++ b/Source/NSAttributedString.m @@ -33,7 +33,7 @@ /* Warning - [-initWithString:attributes:] is the designated initialiser, * but it doesn't provide any way to perform the function of the * [-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 * is expected to accept an NSAttributedString here instead of * 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]) { - [NSException raise: NSRangeException format: - @"RangeError in method -attributesAtIndex: longestEffectiveRange: inRange: in class NSAttributedString"]; + [NSException raise: NSRangeException + format: @"RangeError in method -attributesAtIndex:longestEffectiveRange:inRange: in class NSAttributedString"]; } attrDictionary = [self attributesAtIndex: index effectiveRange: aRange]; - if (!aRange) + if (aRange == 0) return attrDictionary; while (aRange->location > rangeLimit.location) { //Check extend range backwards - tmpDictionary = - [self attributesAtIndex: aRange->location-1 - effectiveRange: &tmpRange]; + tmpDictionary = [self attributesAtIndex: aRange->location-1 + effectiveRange: &tmpRange]; if ([tmpDictionary isEqualToDictionary: attrDictionary]) - aRange->location = tmpRange.location; + { + aRange->length = NSMaxRange(*aRange) - tmpRange.location; + aRange->location = tmpRange.location; + } else - break; + { + break; + } } while (NSMaxRange(*aRange) < NSMaxRange(rangeLimit)) { //Check extend range forwards - tmpDictionary = - [self attributesAtIndex: NSMaxRange(*aRange) - effectiveRange: &tmpRange]; + tmpDictionary = [self attributesAtIndex: NSMaxRange(*aRange) + effectiveRange: &tmpRange]; if ([tmpDictionary isEqualToDictionary: attrDictionary]) - aRange->length = NSMaxRange(tmpRange) - aRange->location; + { + aRange->length = NSMaxRange(tmpRange) - aRange->location; + } else - break; + { + break; + } } *aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit return attrDictionary; @@ -246,65 +253,78 @@ static Class NSMutableAttributedString_concrete_class; id attrValue; 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) - *aRange = NSMakeRange(0,[self length]); - //If attributeName is nil, then the attribute will not exist in the - //entire text - therefore aRange of the entire text must be correct - + if (aRange != 0) + { + *aRange = NSMakeRange(0,[self length]); + /* + * 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; } attrValue = [tmpDictionary objectForKey: attributeName]; return attrValue; } -- (id) attribute: (NSString*)attributeName atIndex: (unsigned int)index - longestEffectiveRange: (NSRange*)aRange inRange: (NSRange)rangeLimit +- (id) attribute: (NSString*)attributeName + atIndex: (unsigned int)index + longestEffectiveRange: (NSRange*)aRange + inRange: (NSRange)rangeLimit { - NSDictionary *tmpDictionary; - id attrValue,tmpAttrValue; - NSRange tmpRange; + NSDictionary *tmpDictionary; + id attrValue; + id tmpAttrValue; + NSRange tmpRange; if (rangeLimit.location < 0 || NSMaxRange(rangeLimit) > [self length]) { - [NSException raise: NSRangeException format: - @"RangeError in method -attribute: atIndex: longestEffectiveRange: inRange: in class NSAttributedString"]; + [NSException raise: NSRangeException + format: @"RangeError in method -attribute:atIndex:longestEffectiveRange:inRange: in class NSAttributedString"]; } - attrValue = [self attribute: attributeName atIndex: index effectiveRange: aRange]; - //Raises exception if index is out of range, so that I don't have to test this... + attrValue = [self attribute: attributeName + atIndex: index + effectiveRange: aRange]; - if (!attributeName) - return nil;//attribute: atIndex: effectiveRange: handles this case... - if (!aRange) + if (attributeName == nil) + return nil; + if (aRange == 0) return attrValue; while (aRange->location > rangeLimit.location) { //Check extend range backwards - tmpDictionary = - [self attributesAtIndex: aRange->location-1 - effectiveRange: &tmpRange]; + tmpDictionary = [self attributesAtIndex: aRange->location-1 + effectiveRange: &tmpRange]; tmpAttrValue = [tmpDictionary objectForKey: attributeName]; if (tmpAttrValue == attrValue) - aRange->location = tmpRange.location; + { + aRange->length = NSMaxRange(*aRange) - tmpRange.location; + aRange->location = tmpRange.location; + } else - break; + { + break; + } } while (NSMaxRange(*aRange) < NSMaxRange(rangeLimit)) { //Check extend range forwards - tmpDictionary = - [self attributesAtIndex: NSMaxRange(*aRange) - effectiveRange: &tmpRange]; + tmpDictionary = [self attributesAtIndex: NSMaxRange(*aRange) + effectiveRange: &tmpRange]; tmpAttrValue = [tmpDictionary objectForKey: attributeName]; if (tmpAttrValue == attrValue) - aRange->length = NSMaxRange(tmpRange) - aRange->location; + { + aRange->length = NSMaxRange(tmpRange) - aRange->location; + } else - break; + { + break; + } } *aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit return attrValue; @@ -328,9 +348,9 @@ static Class NSMutableAttributedString_concrete_class; return YES; ownDictionary = [self attributesAtIndex: 0 - effectiveRange: &ownEffectiveRange]; + effectiveRange: &ownEffectiveRange]; otherDictionary = [otherString attributesAtIndex: 0 - effectiveRange: &otherEffectiveRange]; + effectiveRange: &otherEffectiveRange]; result = YES; while (YES) @@ -343,14 +363,15 @@ static Class NSMutableAttributedString_concrete_class; } if (NSMaxRange(ownEffectiveRange) < NSMaxRange(otherEffectiveRange)) { - ownDictionary = [self - attributesAtIndex: NSMaxRange(ownEffectiveRange) - effectiveRange: &ownEffectiveRange]; + ownDictionary = [self attributesAtIndex: NSMaxRange(ownEffectiveRange) + effectiveRange: &ownEffectiveRange]; } else { if (NSMaxRange(otherEffectiveRange) >= length) - break;//End of strings + { + break;//End of strings + } otherDictionary = [otherString attributesAtIndex: NSMaxRange(otherEffectiveRange) effectiveRange: &otherEffectiveRange]; diff --git a/Source/NSGAttributedString.m b/Source/NSGAttributedString.m index 74da49b85..1c145490f 100644 --- a/Source/NSGAttributedString.m +++ b/Source/NSGAttributedString.m @@ -47,9 +47,12 @@ #include #include #include +#include #include #include +#define SANITY_CHECKS 0 + @interface GSAttrInfo : NSObject { @public @@ -78,6 +81,12 @@ NSDeallocateObject(self); } +- (NSString*) description +{ + return [NSString stringWithFormat: @"Attributes at %u are - %@", + loc, attrs]; +} + - (Class) classForPortCoder { return [self class]; @@ -198,25 +207,35 @@ _attributesAtIndexEffectiveRange( unsigned low, high, used, cnt, nextLoc; GSAttrInfo *found = nil; + used = (*cntImp)(_infoArray, cntSel); + NSCAssert(used > 0, NSInternalInconsistencyException); + high = used - 1; + if (index >= tmpLength) { if (index == tmpLength) { - *foundIndex = index; - return nil; + found = OBJECTAT(high); + if (foundIndex != 0) + { + *foundIndex = high; + } + if (aRange != 0) + { + aRange->location = found->loc; + aRange->length = tmpLength - found->loc; + } + return found->attrs; } [NSException raise: NSRangeException format: @"index is out of range in function " @"_attributesAtIndexEffectiveRange()"]; } - used = (*cntImp)(_infoArray, cntSel); - /* * Binary search for efficiency in huge attributed strings */ low = 0; - high = used - 1; while (low <= high) { cnt = (low + high) / 2; @@ -240,12 +259,12 @@ _attributesAtIndexEffectiveRange( if (found->loc == index || index < nextLoc) { //Found - if (aRange) + if (aRange != 0) { aRange->location = found->loc; aRange->length = nextLoc - found->loc; } - if (foundIndex) + if (foundIndex != 0) { *foundIndex = cnt; } @@ -343,6 +362,33 @@ _attributesAtIndexEffectiveRange( @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 { _setup(); @@ -385,6 +431,7 @@ _attributesAtIndexEffectiveRange( aString = [as string]; _setAttributesFrom(as, NSMakeRange(0, [aString length]), _infoArray); +SANITY(); } else { @@ -424,51 +471,72 @@ _attributesAtIndexEffectiveRange( NSZone *z = fastZone(self); GSAttrInfo *info; - if (!attributes) - attributes = [NSDictionary dictionary]; + if (range.length == 0) + { + NSWarnMLog(@"Attempt to set attribute for zero-length range", 0); + return; + } + if (attributes == nil) + { + attributes = [NSDictionary dictionary]; + } +SANITY(); tmpLength = [_textChars length]; GS_RANGE_CHECK(range, tmpLength); 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( - NSMaxRange(range), &effectiveRange, tmpLength, _infoArray, &arrayIndex); - - afterRangeLoc = NSMaxRange(range); - if (effectiveRange.location > range.location) + afterRangeLoc, &effectiveRange, tmpLength, _infoArray, &arrayIndex); + if (effectiveRange.location > beginRangeLoc) { + /* + * The located range also starts at or after our range. + */ info = OBJECTAT(arrayIndex); 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); arrayIndex++; INSOBJECT(info, arrayIndex); RELEASE(info); + arrayIndex--; } - arrayIndex--; } else { arrayIndex = arraySize - 1; } + /* + * Remove any ranges completely within ours + */ while (arrayIndex > 0) { info = OBJECTAT(arrayIndex-1); - if (info->loc < range.location) + if (info->loc < beginRangeLoc) break; REMOVEAT(arrayIndex); arrayIndex--; } - beginRangeLoc = range.location; info = OBJECTAT(arrayIndex); location = info->loc; - if (location >= range.location) + if (location >= beginRangeLoc) { - if (location > range.location) + if (location > beginRangeLoc) { info->loc = beginRangeLoc; } @@ -482,6 +550,7 @@ _attributesAtIndexEffectiveRange( RELEASE(info); } +SANITY(); /* * Primitive method! Sets attributes and values for a given range of * characters, replacing any previous attributes and values for that @@ -500,71 +569,118 @@ _attributesAtIndexEffectiveRange( - (void) replaceCharactersInRange: (NSRange)range withString: (NSString*)aString { - unsigned tmpLength, arrayIndex, arraySize, cnt, moveLocations; + unsigned tmpLength, arrayIndex, arraySize; NSRange effectiveRange; NSDictionary *attrs; - unsigned afterRangeLoc; GSAttrInfo *info; + int moveLocations; NSZone *z = fastZone(self); + unsigned start; - if (!aString) - aString = @""; +SANITY(); + if (aString == nil) + { + aString = @""; + } tmpLength = [_textChars length]; GS_RANGE_CHECK(range, tmpLength); - arraySize = (*cntImp)(_infoArray, cntSel); - if (NSMaxRange(range) < tmpLength) + if (range.location == 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; - NSRange r = NSMakeRange(arrayIndex + 1, l); - GSAttrInfo *objs[l]; - - [_infoArray getObjects: objs range: r]; - for (cnt = 0; cnt < l; cnt++) + int next = arrayIndex + 1; + + while (next < arraySize) { - objs[cnt]->loc += moveLocations; + GSAttrInfo *n = OBJECTAT(next); + if (n->loc <= NSMaxRange(range)) + { + REMOVEAT(arrayIndex); + arraySize--; + info = n; + } + break; } } - arrayIndex--; - } - else - { - arrayIndex = arraySize - 1; + info->loc = NSMaxRange(range); } - 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); - if (info->loc <= range.location) - break; - REMOVEAT(arrayIndex); - arrayIndex--; + info->loc += moveLocations; + arrayIndex++; } +SANITY(); [_textChars replaceCharactersInRange: range withString: aString]; +SANITY(); } - (void) dealloc