2005-02-22 11:22:44 +00:00
/ * *
2001-01-09 08:40:09 +00:00
GSAttributedString . m
1997-09-01 21:59:51 +00:00
Implementation of concrete subclass of a string class with attributes
1999-04-09 17:07:21 +00:00
Copyright ( C ) 1997 , 1999 Free Software Foundation , Inc .
1997-09-01 21:59:51 +00:00
1999-04-06 14:21:08 +00:00
Written by : ANOQ of the sun < anoq @ vip . cybercity . dk >
1997-11-21 18:19:29 +00:00
Date : November 1997
1999-04-09 17:07:21 +00:00
Rewrite by : Richard Frith - Macdonald < richard @ brainstorm . co . uk >
Date : April 1999
2005-02-22 11:22:44 +00:00
1997-11-21 18:19:29 +00:00
This file is part of GNUStep - base
1997-09-01 21:59:51 +00:00
This library is free software ; you can redistribute it and / or
2007-09-14 11:36:11 +00:00
modify it under the terms of the GNU Lesser General Public
1997-09-01 21:59:51 +00:00
License as published by the Free Software Foundation ; either
2008-06-08 10:38:33 +00:00
version 2 of the License , or ( at your option ) any later version .
2005-02-22 11:22:44 +00:00
1999-04-06 14:21:08 +00:00
This library is distributed in the hope that it will be useful ,
1997-09-01 21:59:51 +00:00
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 .
1999-04-06 14:21:08 +00:00
If you are interested in a warranty or support for this source code ,
contact Scott Christley < scottc @ net - community . com > for more information .
2005-02-22 11:22:44 +00:00
2007-09-14 11:36:11 +00:00
You should have received a copy of the GNU Lesser General Public
1997-09-01 21:59:51 +00:00
License along with this library ; if not , write to the Free
2006-06-04 06:42:10 +00:00
Software Foundation , Inc . , 51 Franklin Street , Fifth Floor ,
Boston , MA 02111 USA .
1997-09-01 21:59:51 +00:00
* /
1999-04-09 17:07:21 +00:00
/ * Warning - [ - initWithString : attributes : ] is the designated initialiser ,
* but it doesn ' t provide any way to perform the function of the
* [ - initWithAttributedString : ] initialiser .
2000-10-16 12:35:42 +00:00
* In order to work round this , the string argument of the
1999-04-09 17:07:21 +00:00
* 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 .
* /
1997-11-21 18:19:29 +00:00
2010-02-19 08:12:46 +00:00
# import "common.h"
2007-11-29 20:53:26 +00:00
# import "GNUstepBase/GSLock.h"
2010-02-14 10:48:10 +00:00
# import "GNUstepBase/NSMutableString+GNUstepBase.h"
2007-11-29 20:53:26 +00:00
# import "Foundation/NSAttributedString.h"
# import "Foundation/NSException.h"
# import "Foundation/NSRange.h"
# import "Foundation/NSArray.h"
2010-02-08 17:52:36 +00:00
# import "Foundation/NSInvocation.h"
2007-11-29 20:53:26 +00:00
# import "Foundation/NSLock.h"
2010-02-08 17:52:36 +00:00
# import "Foundation/NSProxy.h"
2007-11-29 20:53:26 +00:00
# import "Foundation/NSThread.h"
# import "Foundation/NSNotification.h"
1997-09-01 21:59:51 +00:00
2000-03-09 19:06:49 +00:00
# define SANITY_CHECKS 0
2010-02-08 17:52:36 +00:00
2001-01-09 08:40:09 +00:00
@ interface GSAttributedString : NSAttributedString
{
NSString * _textChars ;
NSMutableArray * _infoArray ;
}
- ( id ) initWithString : ( NSString * ) aString
attributes : ( NSDictionary * ) attributes ;
- ( NSString * ) string ;
2010-09-16 02:55:24 +00:00
- ( NSDictionary * ) attributesAtIndex : ( NSUInteger ) index
2001-01-09 08:40:09 +00:00
effectiveRange : ( NSRange * ) aRange ;
@ end
@ interface GSMutableAttributedString : NSMutableAttributedString
{
NSMutableString * _textChars ;
NSMutableArray * _infoArray ;
2010-02-09 06:07:10 +00:00
NSString * _textProxy ;
2001-01-09 08:40:09 +00:00
}
- ( id ) initWithString : ( NSString * ) aString
attributes : ( NSDictionary * ) attributes ;
- ( NSString * ) string ;
2010-09-16 02:55:24 +00:00
- ( NSDictionary * ) attributesAtIndex : ( NSUInteger ) index
2001-01-09 08:40:09 +00:00
effectiveRange : ( NSRange * ) aRange ;
- ( void ) setAttributes : ( NSDictionary * ) attributes
range : ( NSRange ) range ;
- ( void ) replaceCharactersInRange : ( NSRange ) range
withString : ( NSString * ) aString ;
@ end
2002-01-24 17:03:04 +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 )
# define GSI_MAP _EQUAL ( M , X , Y ) [ ( X ) . obj isEqualToDictionary : ( Y ) . obj ]
2000-11-13 11:55:27 +00:00
# define GSI_MAP _KTYPES GSUNION_OBJ
2011-03-29 08:16:02 +00:00
# define GSI_MAP _VTYPES GSUNION_NSINT
2002-02-13 18:49:32 +00:00
# define GSI_MAP _NOCLEAN 1
2000-11-13 11:55:27 +00:00
2009-03-09 15:11:51 +00:00
# if GS_WITH _GC
2011-02-27 17:53:14 +00:00
# include < gc / gc_typed . h >
2009-03-09 15:11:51 +00:00
static GC_descr nodeDesc ; // Type descriptor for map node .
# define GSI_MAP _NODES ( M , X ) \
( GSIMapNode ) GC_calloc _explicitly _typed ( X , sizeof ( GSIMapNode_t ) , nodeDesc )
# endif
2003-07-31 23:49:32 +00:00
# include "GNUstepBase/GSIMap.h"
2000-11-13 11:55:27 +00:00
static NSLock * attrLock = nil ;
static GSIMapTable_t attrMap ;
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 )
2013-10-30 04:28:17 +00:00
@ class GSCachedDictionary ;
@ protocol GSCachedDictionary
- ( void ) _uncache ;
@ end
2013-10-29 10:10:24 +00:00
/ * Add a dictionary to the cache - if it was not already there , return
2000-11-13 11:55:27 +00:00
* the copy added to the cache , if it was , count it and return retained
* object that was there .
* /
static NSDictionary *
cacheAttributes ( NSDictionary * attrs )
{
2013-10-29 15:31:24 +00:00
if ( nil ! = attrs )
2000-11-13 11:55:27 +00:00
{
2013-10-29 15:31:24 +00:00
GSIMapNode node ;
ALOCK ( ) ;
node = GSIMapNodeForKey ( & attrMap , ( GSIMapKey ) ( ( id ) attrs ) ) ;
if ( node = = 0 )
{
2013-10-30 12:51:52 +00:00
/ * Deep copy of dictionary , including copying objects . . . .
* result in an immutable dictionary that can safely be cached
* unless the copied objects or their contents are mutated .
2013-10-29 15:31:24 +00:00
* /
2013-10-30 04:28:17 +00:00
attrs = [ ( NSDictionary * ) [ GSCachedDictionary alloc ]
2013-10-30 12:51:52 +00:00
initWithDictionary : attrs copyItems : YES ] ;
2013-10-29 15:31:24 +00:00
GSIMapAddPair ( & attrMap ,
( GSIMapKey ) ( ( id ) attrs ) , ( GSIMapVal ) ( NSUInteger ) 1 ) ;
}
else
{
node -> value . nsu + + ;
2013-10-29 16:05:29 +00:00
attrs = node -> key . obj ;
2013-10-29 15:31:24 +00:00
}
AUNLOCK ( ) ;
2000-11-13 11:55:27 +00:00
}
return attrs ;
}
2013-10-29 10:10:24 +00:00
/ * Decrement the count of a dictionary in the cache and release it .
* If the count goes to zero , remove it from the cache .
* /
2000-11-13 11:55:27 +00:00
static void
unCacheAttributes ( NSDictionary * attrs )
{
2013-10-29 15:31:24 +00:00
if ( nil ! = attrs )
2000-11-13 11:55:27 +00:00
{
2013-10-30 12:51:52 +00:00
GSIMapBucket bucket ;
BOOL found = NO ;
id < GSCachedDictionary > removed = nil ;
2013-10-29 15:31:24 +00:00
ALOCK ( ) ;
bucket = GSIMapBucketForKey ( & attrMap , ( GSIMapKey ) ( ( id ) attrs ) ) ;
if ( bucket ! = 0 )
{
GSIMapNode node ;
node = GSIMapNodeForKeyInBucket ( & attrMap ,
bucket , ( GSIMapKey ) ( ( id ) attrs ) ) ;
if ( node ! = 0 )
{
2013-10-30 12:51:52 +00:00
found = YES ;
2013-10-29 15:31:24 +00:00
if ( - - node -> value . nsu = = 0 )
{
2013-10-29 16:05:29 +00:00
removed = node -> key . obj ;
2013-10-29 15:31:24 +00:00
GSIMapRemoveNodeFromMap ( & attrMap , bucket , node ) ;
GSIMapFreeNode ( & attrMap , node ) ;
}
}
}
AUNLOCK ( ) ;
2013-10-30 04:28:17 +00:00
if ( nil ! = removed )
{
[ removed _uncache ] ;
}
2013-10-30 12:51:52 +00:00
if ( NO = = found )
{
[ NSException raise : NSInternalInconsistencyException
format : @ "NSAttributedString attempt to remove attributes which are not found in the cache. Did someone mutate an object in the attributes dictionary? The object to remove was %@" , attrs ] ;
}
2000-11-13 11:55:27 +00:00
}
}
2009-01-12 18:36:37 +00:00
@ interface GSAttrInfo : NSObject
1999-04-09 17:07:21 +00:00
{
@ public
unsigned loc ;
NSDictionary * attrs ;
}
+ ( GSAttrInfo * ) newWithZone : ( NSZone * ) z value : ( NSDictionary * ) a at : ( unsigned ) l ;
@ end
@ implementation GSAttrInfo
2013-10-30 10:43:28 +00:00
+ ( void ) initialize
{
if ( nil = = attrLock )
{
attrLock = [ NSLock new ] ;
lockSel = @ selector ( lock ) ;
unlockSel = @ selector ( unlock ) ;
lockImp = [ attrLock methodForSelector : lockSel ] ;
unlockImp = [ attrLock methodForSelector : unlockSel ] ;
GSIMapInitWithZoneAndCapacity ( & attrMap , NSDefaultMallocZone ( ) , 32 ) ;
}
}
2000-11-13 11:55:27 +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-04-09 17:07:21 +00:00
+ ( GSAttrInfo * ) newWithZone : ( NSZone * ) z value : ( NSDictionary * ) a at : ( unsigned ) l ;
{
GSAttrInfo * info = ( GSAttrInfo * ) NSAllocateObject ( self , 0 , z ) ;
info -> loc = l ;
2013-10-29 10:10:24 +00:00
info -> attrs = cacheAttributes ( a ) ;
1999-04-09 17:07:21 +00:00
return info ;
}
- ( void ) dealloc
{
2009-01-12 18:36:37 +00:00
[ self finalize ] ;
2009-10-10 08:16:17 +00:00
[ super dealloc ] ;
1999-04-09 17:07:21 +00:00
}
2000-03-09 19:06:49 +00:00
- ( NSString * ) description
{
return [ NSString stringWithFormat : @ "Attributes at %u are - %@" ,
loc , attrs ] ;
}
2000-11-13 11:55:27 +00:00
- ( void ) encodeWithCoder : ( NSCoder * ) aCoder
{
[ aCoder encodeValueOfObjCType : @ encode ( unsigned ) at : & loc ] ;
[ aCoder encodeValueOfObjCType : @ encode ( id ) at : & attrs ] ;
}
2009-01-12 18:36:37 +00:00
- ( void ) finalize
2000-11-13 11:55:27 +00:00
{
unCacheAttributes ( attrs ) ;
2013-10-29 10:10:24 +00:00
attrs = nil ;
2000-11-13 11:55:27 +00:00
}
- ( id ) initWithCoder : ( NSCoder * ) aCoder
{
NSDictionary * a ;
[ aCoder decodeValueOfObjCType : @ encode ( unsigned ) at : & loc ] ;
a = [ aCoder decodeObject ] ;
attrs = cacheAttributes ( a ) ;
return self ;
}
- ( id ) replacementObjectForPortCoder : ( NSPortCoder * ) aCoder
{
return self ;
}
1999-04-09 17:07:21 +00:00
@ end
2001-01-09 08:40:09 +00:00
@ implementation GSAttributedString
1997-09-01 21:59:51 +00:00
2013-10-30 04:28:17 +00:00
static GSAttrInfo * blank ;
1999-04-09 21:42:39 +00:00
static Class infCls = 0 ;
2000-10-30 18:00:27 +00:00
static SEL infSel ;
static SEL addSel ;
static SEL cntSel ;
static SEL insSel ;
static SEL oatSel ;
static SEL remSel ;
static IMP infImp ;
2002-11-09 16:40:00 +00:00
static void ( * addImp ) ( NSMutableArray * , SEL , id ) ;
static unsigned ( * cntImp ) ( NSArray * , SEL ) ;
static void ( * insImp ) ( NSMutableArray * , SEL , id , unsigned ) ;
2000-10-30 18:00:27 +00:00
static IMP oatImp ;
2002-11-09 16:40:00 +00:00
static void ( * remImp ) ( NSMutableArray * , SEL , unsigned ) ;
1999-04-09 21:42:39 +00:00
# define NEWINFO ( Z , O , L ) ( ( * infImp ) ( infCls , infSel , ( Z ) , ( O ) , ( L ) ) )
1999-09-16 07:21:34 +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-04-09 21:42:39 +00:00
static void
_setAttributesFrom (
1999-04-06 14:21:08 +00:00
NSAttributedString * attributedString ,
NSRange aRange ,
1999-09-16 07:21:34 +00:00
NSMutableArray * _infoArray )
1997-09-01 21:59:51 +00:00
{
2010-03-05 09:30:18 +00:00
NSZone * z = [ _infoArray zone ] ;
1999-04-09 17:07:21 +00:00
NSRange range ;
NSDictionary * attr ;
GSAttrInfo * info ;
unsigned loc ;
/ *
* remove any old attributes of the string .
* /
1999-09-16 07:21:34 +00:00
[ _infoArray removeAllObjects ] ;
1999-04-09 17:07:21 +00:00
2001-01-13 20:33:58 +00:00
if ( aRange . length = = 0 )
{
2013-10-30 04:28:17 +00:00
attr = blank -> attrs ;
2004-01-08 00:52:21 +00:00
range = aRange ; / * Set to satisfy the loop condition below . * /
2001-01-13 20:33:58 +00:00
}
else
{
attr = [ attributedString attributesAtIndex : aRange . location
effectiveRange : & range ] ;
}
2000-11-13 11:55:27 +00:00
info = NEWINFO ( z , attr , 0 ) ;
1999-04-09 21:42:39 +00:00
ADDOBJECT ( info ) ;
1999-04-09 17:07:21 +00:00
RELEASE ( info ) ;
while ( ( loc = NSMaxRange ( range ) ) < NSMaxRange ( aRange ) )
{
attr = [ attributedString attributesAtIndex : loc
effectiveRange : & range ] ;
2000-11-13 11:55:27 +00:00
info = NEWINFO ( z , attr , loc - aRange . location ) ;
1999-04-09 21:42:39 +00:00
ADDOBJECT ( info ) ;
1999-04-09 17:07:21 +00:00
RELEASE ( info ) ;
}
1997-09-01 21:59:51 +00:00
}
1999-04-09 21:42:39 +00:00
inline static NSDictionary *
_attributesAtIndexEffectiveRange (
1999-04-06 14:21:08 +00:00
unsigned int index ,
NSRange * aRange ,
unsigned int tmpLength ,
1999-09-16 07:21:34 +00:00
NSMutableArray * _infoArray ,
1997-09-01 21:59:51 +00:00
unsigned int * foundIndex )
{
1999-04-09 17:07:21 +00:00
unsigned low , high , used , cnt , nextLoc ;
GSAttrInfo * found = nil ;
2000-03-09 19:06:49 +00:00
used = ( * cntImp ) ( _infoArray , cntSel ) ;
NSCAssert ( used > 0 , NSInternalInconsistencyException ) ;
high = used - 1 ;
1999-04-09 17:07:21 +00:00
if ( index >= tmpLength )
1999-04-06 14:21:08 +00:00
{
1999-12-17 07:06:29 +00:00
if ( index = = tmpLength )
{
2000-03-09 19:06:49 +00:00
found = OBJECTAT ( high ) ;
if ( foundIndex ! = 0 )
{
* foundIndex = high ;
}
if ( aRange ! = 0 )
{
aRange -> location = found -> loc ;
aRange -> length = tmpLength - found -> loc ;
}
return found -> attrs ;
1999-12-17 07:06:29 +00:00
}
1999-04-09 17:07:21 +00:00
[ NSException raise : NSRangeException
format : @ "index is out of range in function "
@ "_attributesAtIndexEffectiveRange()" ] ;
1999-04-06 14:21:08 +00:00
}
2005-02-22 11:22:44 +00:00
1999-04-09 17:07:21 +00:00
/ *
* Binary search for efficiency in huge attributed strings
* /
low = 0 ;
while ( low <= high )
1997-09-01 21:59:51 +00:00
{
1999-04-09 17:07:21 +00:00
cnt = ( low + high ) / 2 ;
1999-04-09 21:42:39 +00:00
found = OBJECTAT ( cnt ) ;
1999-04-09 17:07:21 +00:00
if ( found -> loc > index )
{
high = cnt - 1 ;
}
1997-09-01 21:59:51 +00:00
else
1999-04-09 17:07:21 +00:00
{
if ( cnt >= used - 1 )
{
nextLoc = tmpLength ;
}
else
{
1999-04-09 21:42:39 +00:00
GSAttrInfo * inf = OBJECTAT ( cnt + 1 ) ;
1999-04-09 17:07:21 +00:00
nextLoc = inf -> loc ;
}
if ( found -> loc = = index || index < nextLoc )
{
// Found
2000-03-09 19:06:49 +00:00
if ( aRange ! = 0 )
1999-04-09 17:07:21 +00:00
{
aRange -> location = found -> loc ;
aRange -> length = nextLoc - found -> loc ;
}
2000-03-09 19:06:49 +00:00
if ( foundIndex ! = 0 )
1999-04-09 17:07:21 +00:00
{
* foundIndex = cnt ;
}
return found -> attrs ;
}
else
{
low = cnt + 1 ;
}
}
1997-09-01 21:59:51 +00:00
}
1999-04-06 14:21:08 +00:00
NSCAssert ( NO , @ "Error in binary search algorithm" ) ;
1997-09-01 21:59:51 +00:00
return nil ;
}
2004-02-08 09:42:38 +00:00
+ ( void ) initialize
2000-11-13 11:55:27 +00:00
{
2013-08-22 15:44:54 +00:00
if ( infCls = = 0 )
{
NSMutableArray * a ;
NSDictionary * d ;
# if GS_WITH _GC
/ * We create a typed memory descriptor for map nodes .
* Only the pointer to the key needs to be scanned .
* /
GC_word w [ GC_BITMAP _SIZE ( GSIMapNode_t ) ] = { 0 } ;
GC_set _bit ( w , GC_WORD _OFFSET ( GSIMapNode_t , key ) ) ;
nodeDesc = GC_make _descriptor ( w , GC_WORD _LEN ( GSIMapNode_t ) ) ;
# endif
infSel = @ selector ( newWithZone : value : at : ) ;
addSel = @ selector ( addObject : ) ;
cntSel = @ selector ( count ) ;
insSel = @ selector ( insertObject : atIndex : ) ;
oatSel = @ selector ( objectAtIndex : ) ;
remSel = @ selector ( removeObjectAtIndex : ) ;
infCls = [ GSAttrInfo class ] ;
infImp = [ infCls methodForSelector : infSel ] ;
2004-02-08 09:42:38 +00:00
2013-10-30 10:43:28 +00:00
d = [ NSDictionary new ] ;
blank = NEWINFO ( NSDefaultMallocZone ( ) , d , 0 ) ;
[ [ NSObject leakAt : & blank ] release ] ;
RELEASE ( d ) ;
2013-08-22 15:44:54 +00:00
a = [ NSMutableArray allocWithZone : NSDefaultMallocZone ( ) ] ;
a = [ a initWithCapacity : 1 ] ;
addImp = ( void ( * ) ( NSMutableArray * , SEL , id ) ) [ a methodForSelector : addSel ] ;
cntImp = ( unsigned ( * ) ( NSArray * , SEL ) ) [ a methodForSelector : cntSel ] ;
insImp = ( void ( * ) ( NSMutableArray * , SEL , id , unsigned ) )
[ a methodForSelector : insSel ] ;
oatImp = [ a methodForSelector : oatSel ] ;
remImp = ( void ( * ) ( NSMutableArray * , SEL , unsigned ) )
[ a methodForSelector : remSel ] ;
RELEASE ( a ) ;
}
[ [ NSObject leakAt : & attrLock ] release ] ;
2000-11-13 11:55:27 +00:00
}
1999-04-09 17:07:21 +00:00
- ( id ) initWithString : ( NSString * ) aString
attributes : ( NSDictionary * ) attributes
1997-09-01 21:59:51 +00:00
{
2010-03-05 09:30:18 +00:00
NSZone * z = [ self zone ] ;
1999-04-09 17:07:21 +00:00
2011-07-21 01:23:08 +00:00
if ( nil = = aString )
{
[ NSException raise : NSInvalidArgumentException
format : @ "aString object passed to -[GSAttributedString initWithString:attributes:] is nil" ] ;
}
if ( ! [ aString respondsToSelector : @ selector ( length ) ] )
{
[ NSException raise : NSInvalidArgumentException
format : @ "aString object passed to -[GSAttributedString initWithString:attributes:] does not respond to -length" ] ;
}
2000-10-09 04:41:18 +00:00
_infoArray = [ [ NSMutableArray allocWithZone : z ] initWithCapacity : 1 ] ;
1999-04-09 17:07:21 +00:00
if ( aString ! = nil && [ aString isKindOfClass : [ NSAttributedString class ] ] )
{
NSAttributedString * as = ( NSAttributedString * ) aString ;
2001-01-13 20:33:58 +00:00
unsigned len ;
1999-04-09 17:07:21 +00:00
aString = [ as string ] ;
2001-01-13 20:33:58 +00:00
len = [ aString length ] ;
_setAttributesFrom ( as , NSMakeRange ( 0 , len ) , _infoArray ) ;
1999-04-09 17:07:21 +00:00
}
else
{
GSAttrInfo * info ;
2000-11-13 11:55:27 +00:00
if ( attributes = = nil )
{
2013-10-30 04:28:17 +00:00
attributes = blank -> attrs ;
2000-11-13 11:55:27 +00:00
}
1999-04-09 21:42:39 +00:00
info = NEWINFO ( z , attributes , 0 ) ;
ADDOBJECT ( info ) ;
1999-04-09 17:07:21 +00:00
RELEASE ( info ) ;
}
if ( aString = = nil )
1999-09-16 07:21:34 +00:00
_textChars = @ "" ;
1999-04-09 17:07:21 +00:00
else
1999-09-16 07:21:34 +00:00
_textChars = [ aString copyWithZone : z ] ;
1997-09-01 21:59:51 +00:00
return self ;
}
1999-04-09 17:07:21 +00:00
- ( NSString * ) string
1997-09-01 21:59:51 +00:00
{
2000-09-19 22:31:18 +00:00
return AUTORELEASE ( [ _textChars copyWithZone : NSDefaultMallocZone ( ) ] ) ;
1997-09-01 21:59:51 +00:00
}
2010-09-16 02:55:24 +00:00
- ( NSDictionary * ) attributesAtIndex : ( NSUInteger ) index
1999-04-09 17:07:21 +00:00
effectiveRange : ( NSRange * ) aRange
1997-09-01 21:59:51 +00:00
{
return _attributesAtIndexEffectiveRange (
1999-09-16 07:21:34 +00:00
index , aRange , [ _textChars length ] , _infoArray , NULL ) ;
1997-09-01 21:59:51 +00:00
}
1999-04-09 17:07:21 +00:00
- ( void ) dealloc
1997-09-01 21:59:51 +00:00
{
1999-09-16 07:21:34 +00:00
RELEASE ( _textChars ) ;
RELEASE ( _infoArray ) ;
1997-09-01 21:59:51 +00:00
[ super dealloc ] ;
}
2001-01-10 20:19:23 +00:00
// The superclass implementation is correct but too slow
2010-09-12 17:05:30 +00:00
- ( NSUInteger ) length
2001-01-10 20:19:23 +00:00
{
return [ _textChars length ] ;
}
1997-09-01 21:59:51 +00:00
@ end
2001-01-09 08:40:09 +00:00
@ implementation GSMutableAttributedString
1997-09-01 21:59:51 +00:00
2000-03-09 19:06:49 +00:00
# if SANITY_CHECKS
2002-04-11 14:35:41 +00:00
# define SANITY ( ) [ self _sanity ]
# else
# define SANITY ( )
# endif
/ * We always compile in this method so that it is available from
* regression test cases . * /
- ( void ) _sanity
2000-03-09 19:06:49 +00:00
{
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 ) ;
2002-03-06 09:44:48 +00:00
NSAssert ( info -> loc < len , NSInternalInconsistencyException ) ;
2000-03-09 19:06:49 +00:00
l = info -> loc ;
}
}
1999-04-09 17:07:21 +00:00
+ ( void ) initialize
1999-04-06 14:21:08 +00:00
{
2001-01-09 08:40:09 +00:00
[ GSAttributedString class ] ; // Ensure immutable class is initialised
1999-04-06 14:21:08 +00:00
}
1999-04-09 17:07:21 +00:00
- ( id ) initWithString : ( NSString * ) aString
attributes : ( NSDictionary * ) attributes
1997-09-01 21:59:51 +00:00
{
2010-03-05 09:30:18 +00:00
NSZone * z = [ self zone ] ;
1999-04-09 17:07:21 +00:00
2011-07-21 01:23:08 +00:00
if ( nil = = aString )
{
[ NSException raise : NSInvalidArgumentException
format : @ "aString object passed to -[GSAttributedString initWithString:attributes:] is nil" ] ;
}
if ( ! [ aString respondsToSelector : @ selector ( length ) ] )
{
[ NSException raise : NSInvalidArgumentException
format : @ "aString object passed to -[GSAttributedString initWithString:attributes:] does not respond to -length" ] ;
}
2000-10-09 04:41:18 +00:00
_infoArray = [ [ NSMutableArray allocWithZone : z ] initWithCapacity : 1 ] ;
1999-04-09 17:07:21 +00:00
if ( aString ! = nil && [ aString isKindOfClass : [ NSAttributedString class ] ] )
{
NSAttributedString * as = ( NSAttributedString * ) aString ;
aString = [ as string ] ;
1999-09-16 07:21:34 +00:00
_setAttributesFrom ( as , NSMakeRange ( 0 , [ aString length ] ) , _infoArray ) ;
1999-04-09 17:07:21 +00:00
}
else
{
GSAttrInfo * info ;
2000-11-13 11:55:27 +00:00
if ( attributes = = nil )
{
2013-10-30 04:28:17 +00:00
attributes = blank -> attrs ;
2000-11-13 11:55:27 +00:00
}
1999-04-09 21:42:39 +00:00
info = NEWINFO ( z , attributes , 0 ) ;
ADDOBJECT ( info ) ;
1999-04-09 17:07:21 +00:00
RELEASE ( info ) ;
}
2011-03-10 08:18:52 +00:00
/ * WARNING . . . NSLayoutManager depends on the fact that we create the
* _textChars instance variable by copying the aString argument to get
* its own string subclass into the attributed string .
* /
1999-04-09 17:07:21 +00:00
if ( aString = = nil )
2000-10-09 04:41:18 +00:00
_textChars = [ [ NSMutableString allocWithZone : z ] init ] ;
1999-04-09 17:07:21 +00:00
else
1999-09-16 07:21:34 +00:00
_textChars = [ aString mutableCopyWithZone : z ] ;
2001-01-13 20:33:58 +00:00
SANITY ( ) ;
1999-04-09 17:07:21 +00:00
return self ;
1997-09-01 21:59:51 +00:00
}
1999-04-09 17:07:21 +00:00
- ( NSString * ) string
1997-09-01 21:59:51 +00:00
{
2010-02-08 17:52:36 +00:00
/ * NB . This method is SUPPOSED to return a proxy to the mutable string !
* This is a performance feature documented ifor OSX .
* /
2010-02-09 06:07:10 +00:00
if ( _textProxy = = nil )
2010-02-08 17:52:36 +00:00
{
2010-02-09 06:07:10 +00:00
_textProxy = [ [ _textChars immutableProxy ] retain ] ;
2010-02-08 17:52:36 +00:00
}
2010-02-09 06:07:10 +00:00
return _textProxy ;
1997-09-01 21:59:51 +00:00
}
2010-09-16 02:55:24 +00:00
- ( NSDictionary * ) attributesAtIndex : ( NSUInteger ) index
1999-04-09 17:07:21 +00:00
effectiveRange : ( NSRange * ) aRange
1997-09-01 21:59:51 +00:00
{
2000-01-09 15:30:11 +00:00
unsigned dummy ;
1997-09-01 21:59:51 +00:00
return _attributesAtIndexEffectiveRange (
2000-01-09 15:30:11 +00:00
index , aRange , [ _textChars length ] , _infoArray , & dummy ) ;
1997-09-01 21:59:51 +00:00
}
2000-11-13 11:55:27 +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-04-09 17:07:21 +00:00
- ( void ) setAttributes : ( NSDictionary * ) attributes
range : ( NSRange ) range
1997-09-01 21:59:51 +00:00
{
2010-08-10 10:38:50 +00:00
unsigned tmpLength ;
unsigned arrayIndex = 0 ;
unsigned arraySize ;
NSRange effectiveRange = NSMakeRange ( 0 , NSNotFound ) ;
1999-04-09 17:07:21 +00:00
unsigned afterRangeLoc , beginRangeLoc ;
NSDictionary * attrs ;
2010-03-05 09:30:18 +00:00
NSZone * z = [ self zone ] ;
1999-04-09 17:07:21 +00:00
GSAttrInfo * info ;
2000-03-09 19:06:49 +00:00
if ( range . length = = 0 )
{
2001-05-22 09:30:07 +00:00
NSWarnMLog ( @ "Attempt to set attribute for zero-length range" ) ;
2000-03-09 19:06:49 +00:00
return ;
}
if ( attributes = = nil )
{
2013-10-30 04:28:17 +00:00
attributes = blank -> attrs ;
2000-03-09 19:06:49 +00:00
}
SANITY ( ) ;
1999-09-16 07:21:34 +00:00
tmpLength = [ _textChars length ] ;
1999-06-21 08:30:26 +00:00
GS_RANGE _CHECK ( range , tmpLength ) ;
1999-09-16 07:21:34 +00:00
arraySize = ( * cntImp ) ( _infoArray , cntSel ) ;
2000-03-09 19:06:49 +00:00
beginRangeLoc = range . location ;
afterRangeLoc = NSMaxRange ( range ) ;
if ( afterRangeLoc < tmpLength )
1997-09-01 21:59:51 +00:00
{
2000-03-09 19:06:49 +00:00
/ *
* Locate the first range that extends beyond our range .
* /
1999-04-09 17:07:21 +00:00
attrs = _attributesAtIndexEffectiveRange (
2000-03-09 19:06:49 +00:00
afterRangeLoc , & effectiveRange , tmpLength , _infoArray , & arrayIndex ) ;
2000-11-13 11:55:27 +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-04-09 17:07:21 +00:00
{
2000-03-09 19:06:49 +00:00
/ *
* The located range also starts at or after our range .
* /
1999-04-09 21:42:39 +00:00
info = OBJECTAT ( arrayIndex ) ;
1999-04-09 17:07:21 +00:00
info -> loc = afterRangeLoc ;
2000-03-09 19:06:49 +00:00
arrayIndex - - ;
1999-04-09 17:07:21 +00:00
}
2000-12-18 19:29:49 +00:00
else if ( NSMaxRange ( effectiveRange ) > afterRangeLoc )
1999-04-09 17:07:21 +00:00
{
2000-03-09 19:06:49 +00:00
/ *
2000-12-18 19:29:49 +00:00
* The located range ends after our range .
2000-03-09 19:06:49 +00:00
* Create a subrange to go from our end to the end of the old range .
* /
2013-10-29 10:10:24 +00:00
info = NEWINFO ( z , attrs , afterRangeLoc ) ;
1999-04-09 21:42:39 +00:00
arrayIndex + + ;
INSOBJECT ( info , arrayIndex ) ;
1999-04-09 17:07:21 +00:00
RELEASE ( info ) ;
2000-03-09 19:06:49 +00:00
arrayIndex - - ;
1999-04-09 17:07:21 +00:00
}
1997-09-01 21:59:51 +00:00
}
else
1999-04-09 17:07:21 +00:00
{
arrayIndex = arraySize - 1 ;
}
2005-02-22 11:22:44 +00:00
2000-03-09 19:06:49 +00:00
/ *
* Remove any ranges completely within ours
* /
1999-04-09 17:07:21 +00:00
while ( arrayIndex > 0 )
{
1999-04-09 21:42:39 +00:00
info = OBJECTAT ( arrayIndex -1 ) ;
2000-03-09 19:06:49 +00:00
if ( info -> loc < beginRangeLoc )
1999-04-09 17:07:21 +00:00
break ;
1999-04-09 21:42:39 +00:00
REMOVEAT ( arrayIndex ) ;
1999-04-09 17:07:21 +00:00
arrayIndex - - ;
}
2000-11-13 11:55:27 +00:00
/ *
* Use the location / attribute info in the current slot if possible ,
* otherwise , add a new slot and use that .
* /
1999-04-09 21:42:39 +00:00
info = OBJECTAT ( arrayIndex ) ;
2000-12-18 18:52:32 +00:00
if ( info -> loc >= beginRangeLoc )
1999-04-05 07:07:03 +00:00
{
2000-10-09 12:08:04 +00:00
info -> loc = beginRangeLoc ;
2013-10-29 10:10:24 +00:00
if ( info -> attrs ! = attributes )
2000-12-18 17:15:04 +00:00
{
unCacheAttributes ( info -> attrs ) ;
2013-10-29 10:10:24 +00:00
info -> attrs = cacheAttributes ( attributes ) ;
2000-12-18 17:15:04 +00:00
}
}
2013-10-29 10:10:24 +00:00
else if ( info -> attrs ! = attributes )
1999-04-09 17:07:21 +00:00
{
arrayIndex + + ;
1999-04-09 21:42:39 +00:00
info = NEWINFO ( z , attributes , beginRangeLoc ) ;
INSOBJECT ( info , arrayIndex ) ;
1999-04-09 17:07:21 +00:00
RELEASE ( info ) ;
}
2005-02-22 11:22:44 +00:00
2000-03-09 19:06:49 +00:00
SANITY ( ) ;
1997-09-01 21:59:51 +00:00
}
1999-04-09 17:07:21 +00:00
- ( void ) replaceCharactersInRange : ( NSRange ) range
withString : ( NSString * ) aString
1997-09-01 21:59:51 +00:00
{
2010-08-10 10:38:50 +00:00
unsigned tmpLength ;
unsigned arrayIndex = 0 ;
unsigned arraySize ;
NSRange effectiveRange = NSMakeRange ( 0 , NSNotFound ) ;
1999-04-09 17:07:21 +00:00
GSAttrInfo * info ;
2000-03-09 19:06:49 +00:00
int moveLocations ;
unsigned start ;
1999-04-09 17:07:21 +00:00
2000-03-09 19:06:49 +00:00
SANITY ( ) ;
if ( aString = = nil )
{
aString = @ "" ;
}
1999-09-16 07:21:34 +00:00
tmpLength = [ _textChars length ] ;
1999-06-21 08:30:26 +00:00
GS_RANGE _CHECK ( range , tmpLength ) ;
2000-03-09 19:06:49 +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 .
* /
[ _textChars appendString : aString ] ;
2000-11-13 11:55:27 +00:00
goto finish ;
2000-03-09 19:06:49 +00:00
}
1999-09-16 07:21:34 +00:00
arraySize = ( * cntImp ) ( _infoArray , cntSel ) ;
2000-03-09 19:06:49 +00:00
if ( arraySize = = 1 )
1997-09-01 21:59:51 +00:00
{
2000-03-09 19:06:49 +00:00
/ *
* Special case - if the string has only one set of attributes
* then the replacement characters will get them too .
* /
[ _textChars replaceCharactersInRange : range withString : aString ] ;
2000-11-13 11:55:27 +00:00
goto finish ;
2000-03-09 19:06:49 +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 ;
2010-09-12 23:25:59 +00:00
_attributesAtIndexEffectiveRange ( start , & effectiveRange ,
2000-03-09 19:06:49 +00:00
tmpLength , _infoArray , & arrayIndex ) ;
1999-04-09 17:07:21 +00:00
2002-03-06 09:44:48 +00:00
moveLocations = [ aString length ] - range . length ;
2000-03-09 19:06:49 +00:00
arrayIndex + + ;
2000-11-13 12:06:58 +00:00
if ( NSMaxRange ( effectiveRange ) < NSMaxRange ( range ) )
2000-03-09 19:06:49 +00:00
{
1999-04-09 17:07:21 +00:00
/ *
2000-03-09 19:06:49 +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-04-09 17:07:21 +00:00
* /
2000-03-09 19:06:49 +00:00
info = OBJECTAT ( arrayIndex ) ;
if ( info -> loc < NSMaxRange ( range ) )
1999-04-09 17:07:21 +00:00
{
2003-01-03 20:14:47 +00:00
unsigned int next = arrayIndex + 1 ;
2000-03-09 19:06:49 +00:00
while ( next < arraySize )
1999-04-09 17:07:21 +00:00
{
2000-03-09 19:06:49 +00:00
GSAttrInfo * n = OBJECTAT ( next ) ;
if ( n -> loc <= NSMaxRange ( range ) )
{
REMOVEAT ( arrayIndex ) ;
arraySize - - ;
info = n ;
}
2000-04-25 15:49:57 +00:00
else
{
break ;
}
1999-04-09 17:07:21 +00:00
}
}
2002-03-06 09:44:48 +00:00
if ( NSMaxRange ( range ) < [ _textChars length ] )
{
info -> loc = NSMaxRange ( range ) ;
}
else
{
2002-03-17 20:04:20 +00:00
REMOVEAT ( arrayIndex ) ;
arraySize - - ;
2002-03-06 09:44:48 +00:00
}
1997-09-01 21:59:51 +00:00
}
2000-03-09 19:06:49 +00:00
2001-04-23 09:30: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-04-05 07:07:03 +00:00
{
2010-09-12 23:25:59 +00:00
_attributesAtIndexEffectiveRange ( start , & effectiveRange ,
2001-04-23 09:30:45 +00:00
tmpLength , _infoArray , & arrayIndex ) ;
arrayIndex + + ;
if ( effectiveRange . location = = range . location
2002-03-06 09:44:48 +00:00
&& effectiveRange . length = = range . length )
{
arrayIndex - - ;
2002-03-17 20:04:20 +00:00
if ( arrayIndex ! = 0 || arraySize > 1 )
2002-03-06 09:44:48 +00:00
{
REMOVEAT ( arrayIndex ) ;
arraySize - - ;
}
else
{
info = OBJECTAT ( 0 ) ;
unCacheAttributes ( info -> attrs ) ;
2013-10-30 04:28:17 +00:00
info -> attrs = cacheAttributes ( blank -> attrs ) ;
2002-03-06 09:44:48 +00:00
info -> loc = NSMaxRange ( range ) ;
}
}
1999-04-05 07:07:03 +00:00
}
1999-04-09 17:07:21 +00:00
2000-03-09 19:06:49 +00:00
/ *
* Now adjust the positions of the ranges following the one we are using .
* /
while ( arrayIndex < arraySize )
1999-04-09 17:07:21 +00:00
{
1999-04-09 21:42:39 +00:00
info = OBJECTAT ( arrayIndex ) ;
2000-03-09 19:06:49 +00:00
info -> loc + = moveLocations ;
arrayIndex + + ;
1999-04-09 17:07:21 +00:00
}
1999-09-16 07:21:34 +00:00
[ _textChars replaceCharactersInRange : range withString : aString ] ;
2000-11-13 11:55:27 +00:00
finish :
2000-03-09 19:06:49 +00:00
SANITY ( ) ;
1997-09-01 21:59:51 +00:00
}
1999-04-09 17:07:21 +00:00
- ( void ) dealloc
1997-09-01 21:59:51 +00:00
{
2010-02-09 06:07:10 +00:00
[ _textProxy release ] ;
1999-09-16 07:21:34 +00:00
RELEASE ( _textChars ) ;
RELEASE ( _infoArray ) ;
1997-09-01 21:59:51 +00:00
[ super dealloc ] ;
}
2001-01-10 20:19:23 +00:00
// The superclass implementation is correct but too slow
2010-09-12 17:05:30 +00:00
- ( NSUInteger ) length
2001-01-10 20:19:23 +00:00
{
return [ _textChars length ] ;
}
1997-09-01 21:59:51 +00:00
@ end
2001-01-09 09:17:31 +00:00
@ interface NSGAttributedString : NSAttributedString
@ end
@ implementation NSGAttributedString
- ( id ) initWithCoder : ( NSCoder * ) aCoder
{
NSLog ( @ "Warning - decoding archive containing obsolete %@ object - please delete/replace this archive" , NSStringFromClass ( [ self class ] ) ) ;
2010-02-25 18:49:31 +00:00
DESTROY ( self ) ;
2001-01-09 09:17:31 +00:00
self = ( id ) NSAllocateObject ( [ GSAttributedString class ] , 0 , NSDefaultMallocZone ( ) ) ;
self = [ self initWithCoder : aCoder ] ;
return self ;
}
@ end
@ interface NSGMutableAttributedString : NSMutableAttributedString
@ end
@ implementation NSGMutableAttributedString
- ( id ) initWithCoder : ( NSCoder * ) aCoder
{
NSLog ( @ "Warning - decoding archive containing obsolete %@ object - please delete/replace this archive" , NSStringFromClass ( [ self class ] ) ) ;
2010-02-25 18:49:31 +00:00
DESTROY ( self ) ;
2001-01-09 09:17:31 +00:00
self = ( id ) NSAllocateObject ( [ GSMutableAttributedString class ] , 0 , NSDefaultMallocZone ( ) ) ;
self = [ self initWithCoder : aCoder ] ;
return self ;
}
@ end