1999-07-15 05:52:55 +00:00
|
|
|
|
/*
|
|
|
|
|
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 <anoq@vip.cybercity.dk>
|
|
|
|
|
Written by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
|
|
|
|
|
Date: July 1999
|
|
|
|
|
|
2007-10-29 21:16:17 +00:00
|
|
|
|
This file is part of the GNUstep GUI Library.
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
2007-10-29 21:16:17 +00:00
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
1999-07-15 05:52:55 +00:00
|
|
|
|
License as published by the Free Software Foundation; either
|
2008-06-10 04:01:49 +00:00
|
|
|
|
version 2 of the License, or (at your option) any later version.
|
2007-10-29 21:16:17 +00:00
|
|
|
|
|
1999-07-15 05:52:55 +00:00
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2007-10-29 21:16:17 +00:00
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
Lesser General Public License for more details.
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
2007-10-29 21:16:17 +00:00
|
|
|
|
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.
|
1999-07-15 05:52:55 +00:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2010-02-09 09:14:14 +00:00
|
|
|
|
#import <Foundation/NSAttributedString.h>
|
|
|
|
|
#import <Foundation/NSException.h>
|
|
|
|
|
#import <Foundation/NSRange.h>
|
|
|
|
|
#import <Foundation/NSArray.h>
|
|
|
|
|
#import <Foundation/NSDebug.h>
|
|
|
|
|
#import <Foundation/NSZone.h>
|
|
|
|
|
#import <Foundation/NSLock.h>
|
|
|
|
|
#import <Foundation/NSThread.h>
|
|
|
|
|
#import <Foundation/NSProxy.h>
|
|
|
|
|
#import <Foundation/NSInvocation.h>
|
|
|
|
|
#import <Foundation/NSNotification.h>
|
|
|
|
|
#import "AppKit/NSTextStorage.h"
|
|
|
|
|
#import "GSTextStorage.h"
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
2000-03-09 20:35:48 +00:00
|
|
|
|
#define SANITY_CHECKS 0
|
|
|
|
|
|
2013-11-01 06:45:18 +00:00
|
|
|
|
static BOOL adding;
|
|
|
|
|
|
|
|
|
|
/* When caching attributes we make a shallow copy of the dictionary cached,
|
|
|
|
|
* so that it is immutable and safe to cache.
|
|
|
|
|
* However, we have a potential problem if the objects within the attributes
|
|
|
|
|
* dictionary are themselves mutable, and something mutates them while they
|
|
|
|
|
* are in the cache. In this case we could items added while different and
|
|
|
|
|
* then mutated to have the same contents, so we would not know which of
|
|
|
|
|
* the equal dictionaries to remove.
|
|
|
|
|
* The solution is to require dictionaries to be identical for removal.
|
|
|
|
|
*/
|
|
|
|
|
static inline BOOL
|
|
|
|
|
cacheEqual(id A, id B)
|
|
|
|
|
{
|
|
|
|
|
if (YES == adding)
|
|
|
|
|
return [A isEqualToDictionary: B];
|
|
|
|
|
else
|
|
|
|
|
return A == B;
|
|
|
|
|
}
|
|
|
|
|
|
2002-02-01 11:01:27 +00:00
|
|
|
|
#define GSI_MAP_RETAIN_KEY(M, X)
|
|
|
|
|
#define GSI_MAP_RELEASE_KEY(M, X)
|
|
|
|
|
#define GSI_MAP_RETAIN_VAL(M, X)
|
|
|
|
|
#define GSI_MAP_RELEASE_VAL(M, X)
|
2013-11-01 06:45:18 +00:00
|
|
|
|
#define GSI_MAP_EQUAL(M, X,Y) cacheEqual((X).obj, (Y).obj)
|
2000-11-13 11:54:58 +00:00
|
|
|
|
#define GSI_MAP_KTYPES GSUNION_OBJ
|
2011-03-29 08:20:25 +00:00
|
|
|
|
#define GSI_MAP_VTYPES GSUNION_NSINT
|
2002-02-13 21:10:55 +00:00
|
|
|
|
#define GSI_MAP_NOCLEAN 1
|
2003-07-31 23:52:10 +00:00
|
|
|
|
#include <GNUstepBase/GSIMap.h>
|
2000-11-13 11:54:58 +00:00
|
|
|
|
|
2002-03-06 09:49:07 +00:00
|
|
|
|
static NSDictionary *blank;
|
2000-11-13 11:54:58 +00:00
|
|
|
|
static NSLock *attrLock = nil;
|
2017-04-16 11:54:21 +00:00
|
|
|
|
static GSIMapTable_t attrMap;
|
2000-11-13 11:54:58 +00:00
|
|
|
|
static SEL lockSel;
|
|
|
|
|
static SEL unlockSel;
|
|
|
|
|
static IMP lockImp;
|
|
|
|
|
static IMP unlockImp;
|
|
|
|
|
|
|
|
|
|
#define ALOCK() if (attrLock != nil) (*lockImp)(attrLock, lockSel)
|
|
|
|
|
#define AUNLOCK() if (attrLock != nil) (*unlockImp)(attrLock, unlockSel)
|
|
|
|
|
|
2010-02-09 09:14:14 +00:00
|
|
|
|
@interface GSTextStorageProxy : NSProxy
|
|
|
|
|
{
|
|
|
|
|
NSString *string;
|
|
|
|
|
}
|
|
|
|
|
- (id) _initWithString: (NSString*)s;
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation GSTextStorageProxy
|
|
|
|
|
|
|
|
|
|
static Class NSObjectClass = nil;
|
|
|
|
|
static Class NSStringClass = nil;
|
|
|
|
|
|
|
|
|
|
+ (void) initialize
|
|
|
|
|
{
|
|
|
|
|
NSObjectClass = [NSObject class];
|
|
|
|
|
NSStringClass = [NSString class];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (Class) class
|
|
|
|
|
{
|
|
|
|
|
return NSStringClass;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
|
|
|
|
[string release];
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) forwardInvocation: (NSInvocation*)anInvocation
|
|
|
|
|
{
|
|
|
|
|
SEL aSel = [anInvocation selector];
|
|
|
|
|
|
|
|
|
|
if (YES == [NSStringClass instancesRespondToSelector: aSel])
|
|
|
|
|
{
|
|
|
|
|
[anInvocation invokeWithTarget: string];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[NSException raise: NSGenericException
|
|
|
|
|
format: @"NSString(instance) does not recognize %s",
|
|
|
|
|
aSel ? GSNameFromSelector(aSel) : "(null)"];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSUInteger) hash
|
|
|
|
|
{
|
|
|
|
|
return [string hash];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) _initWithString: (NSString*)s
|
|
|
|
|
{
|
|
|
|
|
string = [s retain];
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) isEqual: (id)other
|
|
|
|
|
{
|
|
|
|
|
return [string isEqual: other];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) isMemberOfClass: (Class)c
|
|
|
|
|
{
|
|
|
|
|
return (c == NSStringClass) ? YES : NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) isKindOfClass: (Class)c
|
|
|
|
|
{
|
|
|
|
|
return (c == NSStringClass || c == NSObjectClass) ? YES : NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSMethodSignature*) methodSignatureForSelector: (SEL)aSelector
|
|
|
|
|
{
|
|
|
|
|
NSMethodSignature *sig;
|
|
|
|
|
|
|
|
|
|
if (YES == [NSStringClass instancesRespondToSelector: aSelector])
|
|
|
|
|
{
|
|
|
|
|
sig = [string methodSignatureForSelector: aSelector];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sig = [super methodSignatureForSelector: aSelector];
|
|
|
|
|
}
|
|
|
|
|
return sig;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL) respondsToSelector: (SEL)aSelector
|
|
|
|
|
{
|
|
|
|
|
if (YES == [NSStringClass instancesRespondToSelector: aSelector])
|
|
|
|
|
{
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
return [super respondsToSelector: aSelector];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
/*
|
|
|
|
|
* Add a dictionary to the cache - if it was not already there, return
|
|
|
|
|
* the copy added to the cache, if it was, count it and return retained
|
|
|
|
|
* object that was there.
|
|
|
|
|
*/
|
|
|
|
|
static NSDictionary*
|
|
|
|
|
cacheAttributes(NSDictionary *attrs)
|
|
|
|
|
{
|
|
|
|
|
GSIMapNode node;
|
|
|
|
|
|
|
|
|
|
ALOCK();
|
2013-11-01 06:45:18 +00:00
|
|
|
|
adding = YES;
|
2005-10-07 09:57:51 +00:00
|
|
|
|
node = GSIMapNodeForKey(&attrMap, (GSIMapKey)((id)attrs));
|
2000-11-13 11:54:58 +00:00
|
|
|
|
if (node == 0)
|
|
|
|
|
{
|
2001-06-07 06:10:33 +00:00
|
|
|
|
/*
|
|
|
|
|
* Shallow copy of dictionary, without copying objects ... results
|
|
|
|
|
* in an immutable dictionary that can safely be cached.
|
|
|
|
|
*/
|
|
|
|
|
attrs = [[NSDictionary alloc] initWithDictionary: attrs copyItems: NO];
|
2013-11-01 06:45:18 +00:00
|
|
|
|
GSIMapAddPair(&attrMap,
|
|
|
|
|
(GSIMapKey)((id)attrs), (GSIMapVal)(NSUInteger)1);
|
2000-11-13 11:54:58 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2011-03-29 08:20:25 +00:00
|
|
|
|
node->value.nsu++;
|
2000-11-13 11:54:58 +00:00
|
|
|
|
attrs = RETAIN(node->key.obj);
|
|
|
|
|
}
|
|
|
|
|
AUNLOCK();
|
|
|
|
|
return attrs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
unCacheAttributes(NSDictionary *attrs)
|
|
|
|
|
{
|
|
|
|
|
GSIMapBucket bucket;
|
|
|
|
|
|
|
|
|
|
ALOCK();
|
2013-11-01 06:45:18 +00:00
|
|
|
|
adding = NO;
|
2005-10-07 09:57:51 +00:00
|
|
|
|
bucket = GSIMapBucketForKey(&attrMap, (GSIMapKey)((id)attrs));
|
2000-11-13 11:54:58 +00:00
|
|
|
|
if (bucket != 0)
|
|
|
|
|
{
|
|
|
|
|
GSIMapNode node;
|
|
|
|
|
|
2013-11-01 06:45:18 +00:00
|
|
|
|
node = GSIMapNodeForKeyInBucket(&attrMap,
|
|
|
|
|
bucket, (GSIMapKey)((id)attrs));
|
2000-11-13 11:54:58 +00:00
|
|
|
|
if (node != 0)
|
|
|
|
|
{
|
2011-03-29 08:20:25 +00:00
|
|
|
|
if (--node->value.nsu == 0)
|
2000-11-13 11:54:58 +00:00
|
|
|
|
{
|
|
|
|
|
GSIMapRemoveNodeFromMap(&attrMap, bucket, node);
|
|
|
|
|
GSIMapFreeNode(&attrMap, node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
AUNLOCK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2009-01-12 18:39:23 +00:00
|
|
|
|
@interface GSTextInfo : NSObject
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
|
|
|
|
@public
|
|
|
|
|
unsigned loc;
|
|
|
|
|
NSDictionary *attrs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (GSTextInfo*) newWithZone: (NSZone*)z value: (NSDictionary*)a at: (unsigned)l;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation GSTextInfo
|
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
/*
|
|
|
|
|
* Called to record attributes at a particular location - the given attributes
|
|
|
|
|
* dictionary must have been produced by 'cacheAttributes()' so that it is
|
|
|
|
|
* already copied/retained and this method doesn't need to do it.
|
|
|
|
|
*/
|
1999-07-15 05:52:55 +00:00
|
|
|
|
+ (GSTextInfo*) newWithZone: (NSZone*)z value: (NSDictionary*)a at: (unsigned)l;
|
|
|
|
|
{
|
|
|
|
|
GSTextInfo *info = (GSTextInfo*)NSAllocateObject(self, 0, z);
|
|
|
|
|
|
|
|
|
|
info->loc = l;
|
2000-11-13 11:54:58 +00:00
|
|
|
|
info->attrs = a;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
- (Class) classForPortCoder
|
|
|
|
|
{
|
|
|
|
|
return [self class];
|
|
|
|
|
}
|
|
|
|
|
|
1999-07-15 05:52:55 +00:00
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
2009-01-12 18:39:23 +00:00
|
|
|
|
[self finalize];
|
2009-10-10 14:10:52 +00:00
|
|
|
|
[super dealloc];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2000-03-09 20:35:48 +00:00
|
|
|
|
- (NSString*) description
|
|
|
|
|
{
|
|
|
|
|
return [NSString stringWithFormat: @"Attributes at %u are - %@",
|
|
|
|
|
loc, attrs];
|
|
|
|
|
}
|
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
- (void) encodeWithCoder: (NSCoder*)aCoder
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2006-10-15 08:34:47 +00:00
|
|
|
|
if ([aCoder allowsKeyedCoding] == NO)
|
2006-06-04 05:16:37 +00:00
|
|
|
|
{
|
|
|
|
|
[aCoder encodeValueOfObjCType: @encode(unsigned) at: &loc];
|
|
|
|
|
[aCoder encodeValueOfObjCType: @encode(id) at: &attrs];
|
|
|
|
|
}
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-01-12 18:39:23 +00:00
|
|
|
|
- (void) finalize
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2000-11-13 11:54:58 +00:00
|
|
|
|
unCacheAttributes(attrs);
|
|
|
|
|
DESTROY(attrs);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
- (id) initWithCoder: (NSCoder*)aCoder
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2006-10-15 08:34:47 +00:00
|
|
|
|
if ([aCoder allowsKeyedCoding] == NO)
|
2006-06-04 05:16:37 +00:00
|
|
|
|
{
|
|
|
|
|
NSDictionary *a;
|
|
|
|
|
[aCoder decodeValueOfObjCType: @encode(unsigned) at: &loc];
|
|
|
|
|
a = [aCoder decodeObject];
|
|
|
|
|
attrs = cacheAttributes(a);
|
|
|
|
|
}
|
2000-11-13 11:54:58 +00:00
|
|
|
|
return self;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static Class infCls = 0;
|
|
|
|
|
|
2000-10-31 12:47:09 +00:00
|
|
|
|
static SEL infSel;
|
|
|
|
|
static SEL addSel;
|
|
|
|
|
static SEL cntSel;
|
|
|
|
|
static SEL insSel;
|
|
|
|
|
static SEL oatSel;
|
|
|
|
|
static SEL remSel;
|
|
|
|
|
|
|
|
|
|
static IMP infImp;
|
|
|
|
|
static void (*addImp)();
|
|
|
|
|
static unsigned (*cntImp)();
|
|
|
|
|
static void (*insImp)();
|
|
|
|
|
static IMP oatImp;
|
|
|
|
|
static void (*remImp)();
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
|
|
|
|
#define NEWINFO(Z,O,L) ((*infImp)(infCls, infSel, (Z), (O), (L)))
|
2001-01-10 20:20:47 +00:00
|
|
|
|
#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)))
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
2000-10-09 12:19:29 +00:00
|
|
|
|
static inline NSDictionary *attrDict(GSTextInfo* info)
|
|
|
|
|
{
|
|
|
|
|
return info->attrs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
1999-07-15 05:52:55 +00:00
|
|
|
|
static void _setup()
|
|
|
|
|
{
|
|
|
|
|
if (infCls == 0)
|
|
|
|
|
{
|
2000-10-08 20:03:24 +00:00
|
|
|
|
NSMutableArray *a;
|
2002-03-06 09:49:07 +00:00
|
|
|
|
NSDictionary *d;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
GSIMapInitWithZoneAndCapacity(&attrMap, NSDefaultMallocZone(), 32);
|
|
|
|
|
|
2000-10-31 12:47:09 +00:00
|
|
|
|
infSel = @selector(newWithZone:value:at:);
|
|
|
|
|
addSel = @selector(addObject:);
|
|
|
|
|
cntSel = @selector(count);
|
|
|
|
|
insSel = @selector(insertObject:atIndex:);
|
|
|
|
|
oatSel = @selector(objectAtIndex:);
|
|
|
|
|
remSel = @selector(removeObjectAtIndex:);
|
|
|
|
|
|
1999-07-15 05:52:55 +00:00
|
|
|
|
infCls = [GSTextInfo class];
|
|
|
|
|
infImp = [infCls methodForSelector: infSel];
|
2000-10-08 20:03:24 +00:00
|
|
|
|
|
2000-10-09 04:03:32 +00:00
|
|
|
|
a = [NSMutableArray allocWithZone: NSDefaultMallocZone()];
|
|
|
|
|
a = [a initWithCapacity: 1];
|
2000-10-08 20:03:24 +00:00
|
|
|
|
addImp = (void (*)())[a methodForSelector: addSel];
|
|
|
|
|
cntImp = (unsigned (*)())[a methodForSelector: cntSel];
|
|
|
|
|
insImp = (void (*)())[a methodForSelector: insSel];
|
|
|
|
|
oatImp = [a methodForSelector: oatSel];
|
|
|
|
|
remImp = (void (*)())[a methodForSelector: remSel];
|
|
|
|
|
RELEASE(a);
|
2002-03-06 09:49:07 +00:00
|
|
|
|
|
|
|
|
|
d = [NSDictionary new];
|
|
|
|
|
blank = cacheAttributes(d);
|
|
|
|
|
RELEASE(d);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
_setAttributesFrom(
|
|
|
|
|
NSAttributedString *attributedString,
|
|
|
|
|
NSRange aRange,
|
2001-01-10 20:20:47 +00:00
|
|
|
|
NSMutableArray *_infoArray)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2001-01-10 20:20:47 +00:00
|
|
|
|
NSZone *z = [_infoArray zone];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
NSRange range;
|
|
|
|
|
NSDictionary *attr;
|
|
|
|
|
GSTextInfo *info;
|
|
|
|
|
unsigned loc;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* remove any old attributes of the string.
|
|
|
|
|
*/
|
2001-01-10 20:20:47 +00:00
|
|
|
|
[_infoArray removeAllObjects];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
|
|
|
|
if (aRange.length <= 0)
|
2001-01-13 20:33:32 +00:00
|
|
|
|
{
|
2002-03-06 09:49:07 +00:00
|
|
|
|
attr = blank;
|
2004-01-11 22:21:13 +00:00
|
|
|
|
range = aRange; /* Set to satisfy the loop condition below. */
|
2001-01-13 20:33:32 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
attr = [attributedString attributesAtIndex: aRange.location
|
|
|
|
|
effectiveRange: &range];
|
|
|
|
|
}
|
2000-11-13 11:54:58 +00:00
|
|
|
|
attr = cacheAttributes(attr);
|
|
|
|
|
info = NEWINFO(z, attr, 0);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
ADDOBJECT(info);
|
|
|
|
|
RELEASE(info);
|
|
|
|
|
|
|
|
|
|
while ((loc = NSMaxRange(range)) < NSMaxRange(aRange))
|
|
|
|
|
{
|
|
|
|
|
attr = [attributedString attributesAtIndex: loc
|
|
|
|
|
effectiveRange: &range];
|
2000-11-13 11:54:58 +00:00
|
|
|
|
attr = cacheAttributes(attr);
|
|
|
|
|
info = NEWINFO(z, attr, loc - aRange.location);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
ADDOBJECT(info);
|
|
|
|
|
RELEASE(info);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline static NSDictionary*
|
|
|
|
|
_attributesAtIndexEffectiveRange(
|
|
|
|
|
unsigned int index,
|
|
|
|
|
NSRange *aRange,
|
|
|
|
|
unsigned int tmpLength,
|
2001-01-10 20:20:47 +00:00
|
|
|
|
NSMutableArray *_infoArray,
|
1999-07-15 05:52:55 +00:00
|
|
|
|
unsigned int *foundIndex)
|
|
|
|
|
{
|
|
|
|
|
unsigned low, high, used, cnt, nextLoc;
|
|
|
|
|
GSTextInfo *found = nil;
|
|
|
|
|
|
2001-01-10 20:20:47 +00:00
|
|
|
|
used = (*cntImp)(_infoArray, cntSel);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
NSCAssert(used > 0, NSInternalInconsistencyException);
|
|
|
|
|
high = used - 1;
|
|
|
|
|
|
1999-07-15 05:52:55 +00:00
|
|
|
|
if (index >= tmpLength)
|
|
|
|
|
{
|
1999-12-17 07:03:00 +00:00
|
|
|
|
if (index == tmpLength)
|
|
|
|
|
{
|
2000-03-09 20:35:48 +00:00
|
|
|
|
found = OBJECTAT(high);
|
|
|
|
|
if (foundIndex != 0)
|
|
|
|
|
{
|
|
|
|
|
*foundIndex = high;
|
|
|
|
|
}
|
|
|
|
|
if (aRange != 0)
|
|
|
|
|
{
|
|
|
|
|
aRange->location = found->loc;
|
|
|
|
|
aRange->length = tmpLength - found->loc;
|
|
|
|
|
}
|
2000-10-09 12:19:29 +00:00
|
|
|
|
return attrDict(found);
|
1999-12-17 07:03:00 +00:00
|
|
|
|
}
|
1999-07-15 05:52:55 +00:00
|
|
|
|
[NSException raise: NSRangeException
|
|
|
|
|
format: @"index is out of range in function "
|
|
|
|
|
@"_attributesAtIndexEffectiveRange()"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Binary search for efficiency in huge attributed strings
|
|
|
|
|
*/
|
|
|
|
|
low = 0;
|
|
|
|
|
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
|
2000-03-09 20:35:48 +00:00
|
|
|
|
if (aRange != 0)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
|
|
|
|
aRange->location = found->loc;
|
|
|
|
|
aRange->length = nextLoc - found->loc;
|
|
|
|
|
}
|
2000-03-09 20:35:48 +00:00
|
|
|
|
if (foundIndex != 0)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
|
|
|
|
*foundIndex = cnt;
|
|
|
|
|
}
|
2000-10-09 12:19:29 +00:00
|
|
|
|
return attrDict(found);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
low = cnt + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NSCAssert(NO,@"Error in binary search algorithm");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@implementation GSTextStorage
|
|
|
|
|
|
2000-03-09 20:35:48 +00:00
|
|
|
|
#if SANITY_CHECKS
|
|
|
|
|
|
|
|
|
|
#define SANITY() [self sanity]
|
2002-04-11 14:38:19 +00:00
|
|
|
|
#else
|
|
|
|
|
#define SANITY()
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* We always compile in this method so that it is available from
|
|
|
|
|
* regression test cases. */
|
|
|
|
|
- (void) _sanity
|
2000-03-09 20:35:48 +00:00
|
|
|
|
{
|
|
|
|
|
GSTextInfo *info;
|
|
|
|
|
unsigned i;
|
|
|
|
|
unsigned l = 0;
|
2001-01-10 20:20:47 +00:00
|
|
|
|
unsigned len = [_textChars length];
|
|
|
|
|
unsigned c = (*cntImp)(_infoArray, cntSel);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
|
|
|
|
|
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);
|
2002-03-06 09:49:07 +00:00
|
|
|
|
NSAssert(info->loc < len, NSInternalInconsistencyException);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
l = info->loc;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
/*
|
|
|
|
|
* If we are multi-threaded, we must guard access to the uniquing set.
|
|
|
|
|
*/
|
|
|
|
|
+ (void) _becomeThreaded: (id)notification
|
|
|
|
|
{
|
|
|
|
|
attrLock = [NSLock new];
|
|
|
|
|
lockSel = @selector(lock);
|
|
|
|
|
unlockSel = @selector(unlock);
|
|
|
|
|
lockImp = [attrLock methodForSelector: lockSel];
|
|
|
|
|
unlockImp = [attrLock methodForSelector: unlockSel];
|
|
|
|
|
}
|
|
|
|
|
|
1999-07-15 05:52:55 +00:00
|
|
|
|
+ (void) initialize
|
|
|
|
|
{
|
|
|
|
|
_setup();
|
2000-11-13 11:54:58 +00:00
|
|
|
|
|
|
|
|
|
if ([NSThread isMultiThreaded])
|
|
|
|
|
{
|
|
|
|
|
[self _becomeThreaded: nil];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
|
|
|
addObserver: self
|
|
|
|
|
selector: @selector(_becomeThreaded:)
|
|
|
|
|
name: NSWillBecomeMultiThreadedNotification
|
|
|
|
|
object: nil];
|
|
|
|
|
}
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithCoder: (NSCoder*)aCoder
|
|
|
|
|
{
|
|
|
|
|
self = [super initWithCoder: aCoder];
|
2009-08-08 07:08:23 +00:00
|
|
|
|
if([aCoder allowsKeyedCoding] == NO)
|
|
|
|
|
{
|
2009-10-13 20:54:12 +00:00
|
|
|
|
if ([aCoder versionForClassName: @"GSTextStorage"] != (NSInteger)NSNotFound)
|
|
|
|
|
{
|
|
|
|
|
NSLog(@"Warning - decoding archive containing obsolete %@ object - please delete/replace this archive", NSStringFromClass([self class]));
|
|
|
|
|
[aCoder decodeValueOfObjCType: @encode(id) at: &_textChars];
|
|
|
|
|
[aCoder decodeValueOfObjCType: @encode(id) at: &_infoArray];
|
|
|
|
|
}
|
2009-08-08 07:08:23 +00:00
|
|
|
|
}
|
1999-07-15 05:52:55 +00:00
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id) initWithString: (NSString*)aString
|
|
|
|
|
attributes: (NSDictionary*)attributes
|
|
|
|
|
{
|
2009-08-12 22:00:16 +00:00
|
|
|
|
NSZone *z = [self zone];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
|
|
|
|
self = [super initWithString: aString attributes: attributes];
|
2001-01-10 20:20:47 +00:00
|
|
|
|
_infoArray = [[NSMutableArray allocWithZone: z] initWithCapacity: 1];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
if (aString != nil && [aString isKindOfClass: [NSAttributedString class]])
|
|
|
|
|
{
|
2009-08-12 22:00:16 +00:00
|
|
|
|
NSAttributedString *as = (NSAttributedString*)aString;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
|
|
|
|
aString = [as string];
|
2001-01-10 20:20:47 +00:00
|
|
|
|
_setAttributesFrom(as, NSMakeRange(0, [aString length]), _infoArray);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2009-08-12 22:00:16 +00:00
|
|
|
|
GSTextInfo *info;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
if (attributes == nil)
|
|
|
|
|
{
|
2002-03-06 09:49:07 +00:00
|
|
|
|
attributes = blank;
|
2000-11-13 11:54:58 +00:00
|
|
|
|
}
|
|
|
|
|
attributes = cacheAttributes(attributes);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
info = NEWINFO(z, attributes, 0);
|
|
|
|
|
ADDOBJECT(info);
|
|
|
|
|
RELEASE(info);
|
|
|
|
|
}
|
|
|
|
|
if (aString == nil)
|
2001-01-10 20:20:47 +00:00
|
|
|
|
_textChars = [[NSMutableString allocWithZone: z] init];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
else
|
2001-01-10 20:20:47 +00:00
|
|
|
|
_textChars = [aString mutableCopyWithZone: z];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString*) string
|
|
|
|
|
{
|
2010-02-09 09:14:14 +00:00
|
|
|
|
/* NB. This method is SUPPOSED to return a proxy to the mutable string!
|
|
|
|
|
* This is a performance feature documented ifor OSX.
|
|
|
|
|
*/
|
|
|
|
|
if (_textProxy == nil)
|
|
|
|
|
{
|
|
|
|
|
_textProxy = [[_textChars immutableProxy] retain];
|
|
|
|
|
}
|
|
|
|
|
return _textProxy;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2010-09-15 22:17:51 +00:00
|
|
|
|
- (NSDictionary*) attributesAtIndex: (NSUInteger)index
|
1999-07-15 05:52:55 +00:00
|
|
|
|
effectiveRange: (NSRange*)aRange
|
|
|
|
|
{
|
2009-08-12 22:00:16 +00:00
|
|
|
|
unsigned dummy;
|
2000-09-19 22:36:30 +00:00
|
|
|
|
|
1999-07-15 05:52:55 +00:00
|
|
|
|
return _attributesAtIndexEffectiveRange(
|
2001-01-10 20:20:47 +00:00
|
|
|
|
index, aRange, [_textChars length], _infoArray, &dummy);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
1999-07-25 10:36:37 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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:
|
|
|
|
|
*/
|
1999-07-15 05:52:55 +00:00
|
|
|
|
- (void) setAttributes: (NSDictionary*)attributes
|
|
|
|
|
range: (NSRange)range
|
|
|
|
|
{
|
2010-08-10 19:53:47 +00:00
|
|
|
|
unsigned tmpLength;
|
|
|
|
|
unsigned arrayIndex = 0;
|
|
|
|
|
unsigned arraySize;
|
|
|
|
|
NSRange effectiveRange = NSMakeRange(0, NSNotFound);
|
2003-02-18 00:35:28 +00:00
|
|
|
|
NSRange originalRange = range;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
unsigned afterRangeLoc, beginRangeLoc;
|
|
|
|
|
NSDictionary *attrs;
|
|
|
|
|
NSZone *z = [self zone];
|
|
|
|
|
GSTextInfo *info;
|
|
|
|
|
|
2000-03-09 20:35:48 +00:00
|
|
|
|
if (range.length == 0)
|
|
|
|
|
{
|
2015-01-31 18:19:29 +00:00
|
|
|
|
NSWarnMLog(@"Attempt to set attribute for zero-length range");
|
2003-03-03 13:57:34 +00:00
|
|
|
|
return;
|
2000-03-09 20:35:48 +00:00
|
|
|
|
}
|
|
|
|
|
if (attributes == nil)
|
|
|
|
|
{
|
2002-03-06 09:49:07 +00:00
|
|
|
|
attributes = blank;
|
2000-03-09 20:35:48 +00:00
|
|
|
|
}
|
2000-11-13 11:54:58 +00:00
|
|
|
|
attributes = cacheAttributes(attributes);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
SANITY();
|
2001-01-10 20:20:47 +00:00
|
|
|
|
tmpLength = [_textChars length];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
GS_RANGE_CHECK(range, tmpLength);
|
2001-01-10 20:20:47 +00:00
|
|
|
|
arraySize = (*cntImp)(_infoArray, cntSel);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
beginRangeLoc = range.location;
|
|
|
|
|
afterRangeLoc = NSMaxRange(range);
|
|
|
|
|
if (afterRangeLoc < tmpLength)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2000-03-09 20:35:48 +00:00
|
|
|
|
/*
|
|
|
|
|
* Locate the first range that extends beyond our range.
|
|
|
|
|
*/
|
1999-07-15 05:52:55 +00:00
|
|
|
|
attrs = _attributesAtIndexEffectiveRange(
|
2001-01-10 20:20:47 +00:00
|
|
|
|
afterRangeLoc, &effectiveRange, tmpLength, _infoArray, &arrayIndex);
|
2000-11-13 11:54:58 +00:00
|
|
|
|
if (attrs == attributes)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* The located range has the same attributes as us - so we can
|
|
|
|
|
* extend our range to include it.
|
|
|
|
|
*/
|
|
|
|
|
if (effectiveRange.location < beginRangeLoc)
|
|
|
|
|
{
|
|
|
|
|
range.length += beginRangeLoc - effectiveRange.location;
|
|
|
|
|
range.location = effectiveRange.location;
|
|
|
|
|
beginRangeLoc = range.location;
|
|
|
|
|
}
|
|
|
|
|
if (NSMaxRange(effectiveRange) > afterRangeLoc)
|
|
|
|
|
{
|
|
|
|
|
range.length = NSMaxRange(effectiveRange) - range.location;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (effectiveRange.location > beginRangeLoc)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2000-03-09 20:35:48 +00:00
|
|
|
|
/*
|
|
|
|
|
* The located range also starts at or after our range.
|
|
|
|
|
*/
|
1999-07-15 05:52:55 +00:00
|
|
|
|
info = OBJECTAT(arrayIndex);
|
|
|
|
|
info->loc = afterRangeLoc;
|
2000-03-09 20:35:48 +00:00
|
|
|
|
arrayIndex--;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
2000-12-18 19:29:49 +00:00
|
|
|
|
else if (NSMaxRange(effectiveRange) > afterRangeLoc)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2000-03-09 20:35:48 +00:00
|
|
|
|
/*
|
|
|
|
|
* The located range starts before our range.
|
|
|
|
|
* Create a subrange to go from our end to the end of the old range.
|
|
|
|
|
*/
|
2000-11-13 11:54:58 +00:00
|
|
|
|
info = NEWINFO(z, cacheAttributes(attrs), afterRangeLoc);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
arrayIndex++;
|
|
|
|
|
INSOBJECT(info, arrayIndex);
|
|
|
|
|
RELEASE(info);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
arrayIndex--;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
arrayIndex = arraySize - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2000-03-09 20:35:48 +00:00
|
|
|
|
/*
|
|
|
|
|
* Remove any ranges completely within ours
|
|
|
|
|
*/
|
1999-07-15 05:52:55 +00:00
|
|
|
|
while (arrayIndex > 0)
|
|
|
|
|
{
|
|
|
|
|
info = OBJECTAT(arrayIndex-1);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
if (info->loc < beginRangeLoc)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
break;
|
|
|
|
|
REMOVEAT(arrayIndex);
|
|
|
|
|
arrayIndex--;
|
|
|
|
|
}
|
|
|
|
|
|
2000-11-13 11:54:58 +00:00
|
|
|
|
/*
|
|
|
|
|
* Use the location/attribute info in the current slot if possible,
|
|
|
|
|
* otherwise, add a new slot and use that.
|
|
|
|
|
*/
|
1999-07-15 05:52:55 +00:00
|
|
|
|
info = OBJECTAT(arrayIndex);
|
2000-12-18 17:14:54 +00:00
|
|
|
|
if (info->loc >= beginRangeLoc)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2000-10-09 12:19:29 +00:00
|
|
|
|
info->loc = beginRangeLoc;
|
2000-12-18 17:14:54 +00:00
|
|
|
|
if (info->attrs == attributes)
|
|
|
|
|
{
|
|
|
|
|
unCacheAttributes(attributes);
|
2003-02-07 16:50:05 +00:00
|
|
|
|
RELEASE(attributes);
|
2000-12-18 17:14:54 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
unCacheAttributes(info->attrs);
|
|
|
|
|
RELEASE(info->attrs);
|
|
|
|
|
info->attrs = attributes;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (info->attrs == attributes)
|
|
|
|
|
{
|
|
|
|
|
unCacheAttributes(attributes);
|
2003-02-07 16:50:05 +00:00
|
|
|
|
RELEASE(attributes);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
arrayIndex++;
|
|
|
|
|
info = NEWINFO(z, attributes, beginRangeLoc);
|
|
|
|
|
INSOBJECT(info, arrayIndex);
|
|
|
|
|
RELEASE(info);
|
|
|
|
|
}
|
2000-03-09 20:35:48 +00:00
|
|
|
|
|
2000-10-09 12:19:29 +00:00
|
|
|
|
SANITY();
|
2000-03-23 11:31:25 +00:00
|
|
|
|
[self edited: NSTextStorageEditedAttributes
|
2003-02-18 00:35:28 +00:00
|
|
|
|
range: originalRange
|
2000-03-23 11:31:25 +00:00
|
|
|
|
changeInLength: 0];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) replaceCharactersInRange: (NSRange)range
|
|
|
|
|
withString: (NSString*)aString
|
|
|
|
|
{
|
2010-08-10 19:53:47 +00:00
|
|
|
|
unsigned tmpLength;
|
|
|
|
|
unsigned arrayIndex = 0;
|
|
|
|
|
unsigned arraySize;
|
|
|
|
|
NSRange effectiveRange = NSMakeRange(0, NSNotFound);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
GSTextInfo *info;
|
2000-03-09 20:35:48 +00:00
|
|
|
|
int moveLocations;
|
|
|
|
|
unsigned start;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
2000-03-09 20:35:48 +00:00
|
|
|
|
SANITY();
|
|
|
|
|
if (aString == nil)
|
|
|
|
|
{
|
|
|
|
|
aString = @"";
|
|
|
|
|
}
|
2001-01-10 20:20:47 +00:00
|
|
|
|
tmpLength = [_textChars length];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
GS_RANGE_CHECK(range, tmpLength);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
if (range.location == tmpLength)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Special case - replacing a zero length string at the end
|
|
|
|
|
* simply appends the new string and attributes are inherited.
|
|
|
|
|
*/
|
2001-01-10 20:20:47 +00:00
|
|
|
|
[_textChars appendString: aString];
|
2000-10-09 12:19:29 +00:00
|
|
|
|
goto finish;
|
2000-03-09 20:35:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2001-01-10 20:20:47 +00:00
|
|
|
|
arraySize = (*cntImp)(_infoArray, cntSel);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
if (arraySize == 1)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2000-03-09 20:35:48 +00:00
|
|
|
|
/*
|
|
|
|
|
* Special case - if the string has only one set of attributes
|
|
|
|
|
* then the replacement characters will get them too.
|
|
|
|
|
*/
|
2001-01-10 20:20:47 +00:00
|
|
|
|
[_textChars replaceCharactersInRange: range withString: aString];
|
2000-10-09 12:19:29 +00:00
|
|
|
|
goto finish;
|
2000-03-09 20:35:48 +00:00
|
|
|
|
}
|
1999-07-15 05:52:55 +00:00
|
|
|
|
|
2000-03-09 20:35:48 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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;
|
2011-02-09 22:20:00 +00:00
|
|
|
|
_attributesAtIndexEffectiveRange(start, &effectiveRange,
|
|
|
|
|
tmpLength, _infoArray, &arrayIndex);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
|
2002-03-06 09:49:07 +00:00
|
|
|
|
moveLocations = [aString length] - range.length;
|
|
|
|
|
|
2000-03-09 20:35:48 +00:00
|
|
|
|
arrayIndex++;
|
2000-11-13 11:54:58 +00:00
|
|
|
|
if (NSMaxRange(effectiveRange) < NSMaxRange(range))
|
2000-03-09 20:35:48 +00:00
|
|
|
|
{
|
1999-07-15 05:52:55 +00:00
|
|
|
|
/*
|
2000-03-09 20:35:48 +00:00
|
|
|
|
* Remove all range info for ranges enclosed within the one
|
|
|
|
|
* we are replacing. Adjust the start point of a range that
|
|
|
|
|
* extends beyond ours.
|
1999-07-15 05:52:55 +00:00
|
|
|
|
*/
|
2000-03-09 20:35:48 +00:00
|
|
|
|
info = OBJECTAT(arrayIndex);
|
|
|
|
|
if (info->loc < NSMaxRange(range))
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2003-06-23 15:50:46 +00:00
|
|
|
|
unsigned int next = arrayIndex + 1;
|
2000-03-09 20:35:48 +00:00
|
|
|
|
|
|
|
|
|
while (next < arraySize)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2000-03-09 20:35:48 +00:00
|
|
|
|
GSTextInfo *n = OBJECTAT(next);
|
|
|
|
|
if (n->loc <= NSMaxRange(range))
|
|
|
|
|
{
|
|
|
|
|
REMOVEAT(arrayIndex);
|
|
|
|
|
arraySize--;
|
|
|
|
|
info = n;
|
|
|
|
|
}
|
2000-04-23 00:35:48 +00:00
|
|
|
|
else
|
2000-04-25 15:50:43 +00:00
|
|
|
|
{
|
2000-04-23 00:35:48 +00:00
|
|
|
|
break;
|
2000-04-25 15:50:43 +00:00
|
|
|
|
}
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2002-03-06 09:49:07 +00:00
|
|
|
|
if (NSMaxRange(range) < [_textChars length])
|
|
|
|
|
{
|
|
|
|
|
info->loc = NSMaxRange(range);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2002-03-17 20:05:20 +00:00
|
|
|
|
REMOVEAT(arrayIndex);
|
|
|
|
|
arraySize--;
|
2002-03-06 09:49:07 +00:00
|
|
|
|
}
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
2000-03-09 20:35:48 +00:00
|
|
|
|
|
2001-04-23 09:32:45 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
if ((moveLocations + range.length) == 0)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
2011-02-09 22:20:00 +00:00
|
|
|
|
_attributesAtIndexEffectiveRange(start, &effectiveRange,
|
|
|
|
|
tmpLength, _infoArray, &arrayIndex);
|
2002-03-06 09:49:07 +00:00
|
|
|
|
arrayIndex++;
|
2001-04-23 09:32:45 +00:00
|
|
|
|
|
|
|
|
|
if (effectiveRange.location == range.location
|
2002-03-06 09:49:07 +00:00
|
|
|
|
&& effectiveRange.length == range.length)
|
|
|
|
|
{
|
|
|
|
|
arrayIndex--;
|
2002-03-17 20:05:20 +00:00
|
|
|
|
if (arrayIndex != 0 || arraySize > 1)
|
2002-03-06 09:49:07 +00:00
|
|
|
|
{
|
|
|
|
|
REMOVEAT(arrayIndex);
|
|
|
|
|
arraySize--;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
NSDictionary *d = blank;
|
|
|
|
|
|
|
|
|
|
info = OBJECTAT(0);
|
|
|
|
|
unCacheAttributes(info->attrs);
|
|
|
|
|
DESTROY(info->attrs);
|
|
|
|
|
d = cacheAttributes(d);
|
|
|
|
|
info->attrs = d;
|
|
|
|
|
info->loc = NSMaxRange(range);
|
|
|
|
|
}
|
|
|
|
|
}
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
2000-03-09 20:35:48 +00:00
|
|
|
|
/*
|
|
|
|
|
* Now adjust the positions of the ranges following the one we are using.
|
|
|
|
|
*/
|
|
|
|
|
while (arrayIndex < arraySize)
|
1999-07-15 05:52:55 +00:00
|
|
|
|
{
|
|
|
|
|
info = OBJECTAT(arrayIndex);
|
2000-03-09 20:35:48 +00:00
|
|
|
|
info->loc += moveLocations;
|
|
|
|
|
arrayIndex++;
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
2001-01-10 20:20:47 +00:00
|
|
|
|
[_textChars replaceCharactersInRange: range withString: aString];
|
2000-03-23 11:31:25 +00:00
|
|
|
|
|
2000-10-09 12:19:29 +00:00
|
|
|
|
finish:
|
|
|
|
|
SANITY();
|
2000-03-23 11:31:25 +00:00
|
|
|
|
[self edited: NSTextStorageEditedCharacters
|
|
|
|
|
range: range
|
|
|
|
|
changeInLength: [aString length] - range.length];
|
1999-07-15 05:52:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
|
{
|
2001-01-10 20:20:47 +00:00
|
|
|
|
RELEASE(_textChars);
|
|
|
|
|
RELEASE(_infoArray);
|
2010-02-09 09:14:14 +00:00
|
|
|
|
RELEASE(_textProxy);
|
1999-07-15 05:52:55 +00:00
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
2001-01-10 20:20:47 +00:00
|
|
|
|
// The superclass implementation is correct but too slow
|
2010-09-15 22:17:51 +00:00
|
|
|
|
- (NSUInteger) length
|
2001-01-10 20:20:47 +00:00
|
|
|
|
{
|
|
|
|
|
return [_textChars length];
|
|
|
|
|
}
|
1999-07-15 05:52:55 +00:00
|
|
|
|
@end
|