/* GSTextStorage.m Implementation of concrete subclass of a string class with attributes Copyright (C) 1999 Free Software Foundation, Inc. Based on code by: ANOQ of the sun Written by: Richard Frith-Macdonald Date: July 1999 This file is part of GNUStep-gui This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* 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 * 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 * must make sure that your implementation of the initialiser * copes with either an NSString or an NSAttributedString. * If it receives an NSAttributedString, it should ignore the * attributes argument and use the values from the string. */ #include #include #include #include #include @interface GSTextStorage : NSTextStorage { NSMutableString *textChars; NSMutableArray *infoArray; } @end @interface GSTextInfo : NSObject { @public unsigned loc; NSDictionary *attrs; } + (GSTextInfo*) newWithZone: (NSZone*)z value: (NSDictionary*)a at: (unsigned)l; @end @implementation GSTextInfo + (GSTextInfo*) newWithZone: (NSZone*)z value: (NSDictionary*)a at: (unsigned)l; { GSTextInfo *info = (GSTextInfo*)NSAllocateObject(self, 0, z); info->loc = l; info->attrs = [a copy]; return info; } - (void) dealloc { RELEASE(attrs); NSDeallocateObject(self); } - (Class) classForPortCoder { return [self class]; } - (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder { return self; } - (void) encodeWithCoder: (NSCoder*)aCoder { [super encodeWithCoder: aCoder]; [aCoder encodeValueOfObjCType: @encode(unsigned) at: &loc]; [aCoder encodeValueOfObjCType: @encode(id) at: &attrs]; } - (id) initWithCoder: (NSCoder*)aCoder { self = [super initWithCoder: aCoder]; [aCoder decodeValueOfObjCType: @encode(unsigned) at: &loc]; [aCoder decodeValueOfObjCType: @encode(id) at: &attrs]; return self; } @end static Class infCls = 0; static SEL infSel = @selector(newWithZone:value:at:); static IMP infImp = 0; static SEL addSel = @selector(addObject:); static void (*addImp)() = 0; static SEL cntSel = @selector(count); static unsigned (*cntImp)() = 0; static SEL insSel = @selector(insertObject:atIndex:); static void (*insImp)() = 0; static SEL oatSel = @selector(objectAtIndex:); static IMP oatImp = 0; static SEL remSel = @selector(removeObjectAtIndex:); static void (*remImp)() = 0; #define NEWINFO(Z,O,L) ((*infImp)(infCls, infSel, (Z), (O), (L))) #define ADDOBJECT(O) ((*addImp)(infoArray, addSel, (O))) #define INSOBJECT(O,I) ((*insImp)(infoArray, insSel, (O), (I))) #define OBJECTAT(I) ((*oatImp)(infoArray, oatSel, (I))) #define REMOVEAT(I) ((*remImp)(infoArray, remSel, (I))) static void _setup() { if (infCls == 0) { Class c = [NSGMutableArray class]; infCls = [GSTextInfo class]; infImp = [infCls methodForSelector: infSel]; addImp = (void (*)())[c instanceMethodForSelector: addSel]; cntImp = (unsigned (*)())[c instanceMethodForSelector: cntSel]; insImp = (void (*)())[c instanceMethodForSelector: insSel]; oatImp = [c instanceMethodForSelector: oatSel]; remImp = (void (*)())[c instanceMethodForSelector: remSel]; } } static void _setAttributesFrom( NSAttributedString *attributedString, NSRange aRange, NSMutableArray *infoArray) { NSZone *z = [infoArray zone]; NSRange range; NSDictionary *attr; GSTextInfo *info; unsigned loc; /* * remove any old attributes of the string. */ [infoArray removeAllObjects]; if (aRange.length <= 0) return; attr = [attributedString attributesAtIndex: aRange.location effectiveRange: &range]; info = [GSTextInfo newWithZone: z value: attr at: 0]; ADDOBJECT(info); RELEASE(info); while ((loc = NSMaxRange(range)) < NSMaxRange(aRange)) { attr = [attributedString attributesAtIndex: loc effectiveRange: &range]; info = [GSTextInfo newWithZone: z value: attr at: loc - aRange.location]; ADDOBJECT(info); RELEASE(info); } } inline static NSDictionary* _attributesAtIndexEffectiveRange( unsigned int index, NSRange *aRange, unsigned int tmpLength, NSMutableArray *infoArray, unsigned int *foundIndex) { unsigned low, high, used, cnt, nextLoc; GSTextInfo *found = nil; if (index >= tmpLength) { if (index == tmpLength) { *foundIndex = tmpLength; return nil; } [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; found = OBJECTAT(cnt); if (found->loc > index) { high = cnt - 1; } else { if (cnt >= used - 1) { nextLoc = tmpLength; } else { GSTextInfo *inf = OBJECTAT(cnt + 1); nextLoc = inf->loc; } if (found->loc == index || index < nextLoc) { //Found if (aRange) { aRange->location = found->loc; aRange->length = nextLoc - found->loc; } if (foundIndex) { *foundIndex = cnt; } return found->attrs; } else { low = cnt + 1; } } } NSCAssert(NO,@"Error in binary search algorithm"); return nil; } @implementation GSTextStorage + (void) initialize { _setup(); } - (Class) classForPortCoder { return [self class]; } - (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder { return self; } - (void) encodeWithCoder: (NSCoder*)aCoder { [super encodeWithCoder: aCoder]; [aCoder encodeValueOfObjCType: @encode(id) at: &textChars]; [aCoder encodeValueOfObjCType: @encode(id) at: &infoArray]; } - (id) initWithCoder: (NSCoder*)aCoder { self = [super initWithCoder: aCoder]; [aCoder decodeValueOfObjCType: @encode(id) at: &textChars]; [aCoder decodeValueOfObjCType: @encode(id) at: &infoArray]; return self; } - (id) initWithString: (NSString*)aString attributes: (NSDictionary*)attributes { NSZone *z = [self zone]; self = [super initWithString: aString attributes: attributes]; infoArray = [[NSGMutableArray allocWithZone: z] initWithCapacity: 1]; if (aString != nil && [aString isKindOfClass: [NSAttributedString class]]) { NSAttributedString *as = (NSAttributedString*)aString; aString = [as string]; _setAttributesFrom(as, NSMakeRange(0, [aString length]), infoArray); } else { GSTextInfo *info; info = NEWINFO(z, attributes, 0); ADDOBJECT(info); RELEASE(info); } if (aString == nil) textChars = [[NSGMutableString allocWithZone: z] init]; else textChars = [aString mutableCopyWithZone: z]; return self; } - (NSString*) string { return textChars; } - (NSDictionary*) attributesAtIndex: (unsigned)index effectiveRange: (NSRange*)aRange { return _attributesAtIndexEffectiveRange( index, aRange, [textChars length], infoArray, NULL); } /* * Primitive method! Sets attributes and values for a given range of * characters, replacing any previous attributes and values for that * range. * * Sets the attributes for the characters in aRange to attributes. * These new attributes replace any attributes previously associated * with the characters in aRange. Raises an NSRangeException if any * part of aRange lies beyond the end of the receiver's characters. * See also: - addAtributes: range: , - removeAttributes: range: */ - (void) setAttributes: (NSDictionary*)attributes range: (NSRange)range { unsigned tmpLength, arrayIndex, arraySize, location; NSRange effectiveRange; unsigned afterRangeLoc, beginRangeLoc; NSDictionary *attrs; NSZone *z = [self zone]; GSTextInfo *info; if (!attributes) attributes = [NSDictionary dictionary]; tmpLength = [textChars length]; GS_RANGE_CHECK(range, tmpLength); arraySize = (*cntImp)(infoArray, cntSel); if (NSMaxRange(range) < tmpLength) { attrs = _attributesAtIndexEffectiveRange( NSMaxRange(range), &effectiveRange, tmpLength, infoArray, &arrayIndex); afterRangeLoc = NSMaxRange(range); if (effectiveRange.location > range.location) { info = OBJECTAT(arrayIndex); info->loc = afterRangeLoc; } else { info = NEWINFO(z, attrs, afterRangeLoc); arrayIndex++; INSOBJECT(info, arrayIndex); RELEASE(info); } arrayIndex--; } else { arrayIndex = arraySize - 1; } while (arrayIndex > 0) { info = OBJECTAT(arrayIndex-1); if (info->loc < range.location) break; REMOVEAT(arrayIndex); arrayIndex--; } beginRangeLoc = range.location; info = OBJECTAT(arrayIndex); location = info->loc; if (location >= range.location) { if (location > range.location) { info->loc = beginRangeLoc; } ASSIGN(info->attrs, attributes); } else { arrayIndex++; info = NEWINFO(z, attributes, beginRangeLoc); INSOBJECT(info, arrayIndex); RELEASE(info); } /* post changes */ [self edited: NSTextStorageEditedAttributes range: range changeInLength: 0]; } - (void) replaceCharactersInRange: (NSRange)range withString: (NSString*)aString { unsigned aLength; unsigned tmpLength, arrayIndex, arraySize, cnt, moveLocations; NSRange effectiveRange; NSDictionary *attrs; unsigned afterRangeLoc; GSTextInfo *info; NSZone *z = [self zone]; if (!aString) aString = @""; aLength = [aString length]; tmpLength = [textChars length]; GS_RANGE_CHECK(range, tmpLength); arraySize = (*cntImp)(infoArray, cntSel); if (NSMaxRange(range) < tmpLength) { attrs = _attributesAtIndexEffectiveRange( NSMaxRange(range), &effectiveRange, tmpLength, infoArray, &arrayIndex); moveLocations = aLength - 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. */ if (arrayIndex + 1 < arraySize) { unsigned l = arraySize - arrayIndex - 1; NSRange r = NSMakeRange(arrayIndex + 1, l); GSTextInfo *objs[l]; [infoArray getObjects: objs range: r]; for (cnt = 0; cnt < l; cnt++) { objs[cnt]->loc += moveLocations; } } arrayIndex--; } else { arrayIndex = arraySize - 1; } while (arrayIndex > 0) { info = OBJECTAT(arrayIndex); if (info->loc <= range.location) break; REMOVEAT(arrayIndex); arrayIndex--; } [textChars replaceCharactersInRange: range withString: aString]; /* notify of changes */ [self edited: NSTextStorageEditedCharacters range: range changeInLength: [aString length] - range.length]; } - (void) dealloc { RELEASE(textChars); RELEASE(infoArray); [super dealloc]; } @end