API  1.0.0
CPAttributedString.j
Go to the documentation of this file.
1 /*
2  * CPAttributedString.j
3  * Foundation
4  *
5  * Created by Ross Boucher.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
39 @implementation CPAttributedString : CPObject
40 {
41  CPString _string;
42  CPArray _rangeEntries;
43 }
44 
45 // Creating a CPAttributedString Object
50 - (id)init
51 {
52  return [self initWithString:@"" attributes:nil];
53 }
54 
60 - (id)initWithString:(CPString)aString
61 {
62  return [self initWithString:aString attributes:nil];
63 }
64 
70 - (id)initWithAttributedString:(CPAttributedString)aString
71 {
72  var string = [self initWithString:@"" attributes:nil];
73 
74  [string setAttributedString:aString];
75 
76  return string;
77 }
78 
87 - (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
88 {
89  self = [super init];
90 
91  if (self)
92  {
93  if (!attributes)
94  attributes = @{};
95 
96  _string = "" + aString;
97  _rangeEntries = [makeRangeEntry(CPMakeRange(0, _string.length), attributes)];
98  }
99 
100  return self;
101 }
102 
103 //Retrieving Character Information
109 - (CPString)string
110 {
111  return _string;
112 }
113 
119 - (CPString)mutableString
120 {
121  return [self string];
122 }
123 
129 - (unsigned)length
130 {
131  return _string.length;
132 }
133 
134 // private method
135 - (unsigned)_indexOfEntryWithIndex:(unsigned)anIndex
136 {
137  if (anIndex < 0 || anIndex > _string.length || anIndex === undefined)
138  return CPNotFound;
139 
140  // find the range entry that contains anIndex.
141  var sortFunction = function(index, entry)
142  {
143  // index is the character index we're searching for,
144  // while range is the actual range entry we're comparing against
145  if (CPLocationInRange(index, entry.range) || (!index && !CPMaxRange(entry.range)))
146  return CPOrderedSame;
147  else if (CPMaxRange(entry.range) <= index)
148  return CPOrderedDescending;
149  else
150  return CPOrderedAscending;
151  };
152 
153  return [_rangeEntries indexOfObject:anIndex inSortedRange:nil options:0 usingComparator:sortFunction];
154 }
155 
156 //Retrieving Attribute Information
175 - (CPDictionary)attributesAtIndex:(CPUInteger)anIndex effectiveRange:(CPRangePointer)aRange
176 {
177  // find the range entry that contains anIndex.
178  var entryIndex = [self _indexOfEntryWithIndex:anIndex];
179 
180  if (entryIndex === CPNotFound)
181  return @{};
182 
183  var matchingRange = _rangeEntries[entryIndex];
184 
185  if (aRange)
186  {
187  aRange.location = matchingRange.range.location;
188  aRange.length = matchingRange.range.length;
189  }
190 
191  return matchingRange.attributes;
192 }
193 
215 - (CPDictionary)attributesAtIndex:(CPUInteger)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
216 {
217  var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
218 
219  if (startingEntryIndex === CPNotFound)
220  return @{};
221 
222  if (!aRange)
223  return _rangeEntries[startingEntryIndex].attributes;
224 
225  if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
226  {
227  aRange.location = rangeLimit.location;
228  aRange.length = rangeLimit.length;
229 
230  return _rangeEntries[startingEntryIndex].attributes;
231  }
232 
233  // scan backwards
234  var nextRangeIndex = startingEntryIndex - 1,
235  currentEntry = _rangeEntries[startingEntryIndex],
236  comparisonDict = currentEntry.attributes;
237 
238  while (nextRangeIndex >= 0)
239  {
240  var nextEntry = _rangeEntries[nextRangeIndex];
241 
242  if (CPMaxRange(nextEntry.range) > rangeLimit.location && [nextEntry.attributes isEqualToDictionary:comparisonDict])
243  {
244  currentEntry = nextEntry;
245  nextRangeIndex--;
246  }
247  else
248  break;
249  }
250 
251  aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
252 
253  // scan forwards
254  currentEntry = _rangeEntries[startingEntryIndex];
255  nextRangeIndex = startingEntryIndex + 1;
256 
257  while (nextRangeIndex < _rangeEntries.length)
258  {
259  var nextEntry = _rangeEntries[nextRangeIndex];
260 
261  if (nextEntry.range.location < CPMaxRange(rangeLimit) && [nextEntry.attributes isEqualToDictionary:comparisonDict])
262  {
263  currentEntry = nextEntry;
264  nextRangeIndex++;
265  }
266  else
267  break;
268  }
269 
270  aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
271 
272  return comparisonDict;
273 }
274 
291 - (id)attribute:(CPString)attribute atIndex:(CPUInteger)index effectiveRange:(CPRangePointer)aRange
292 {
293  if (!attribute)
294  {
295  if (aRange)
296  {
297  aRange.location = 0;
298  aRange.length = _string.length;
299  }
300 
301  return nil;
302  }
303 
304  return [[self attributesAtIndex:index effectiveRange:aRange] valueForKey:attribute];
305 }
306 
328 - (id)attribute:(CPString)attribute atIndex:(CPUInteger)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
329 {
330  var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
331 
332  if (startingEntryIndex === CPNotFound || !attribute)
333  return nil;
334 
335  if (!aRange)
336  return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
337 
338  if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
339  {
340  aRange.location = rangeLimit.location;
341  aRange.length = rangeLimit.length;
342 
343  return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
344  }
345 
346  // scan backwards
347  var nextRangeIndex = startingEntryIndex - 1,
348  currentEntry = _rangeEntries[startingEntryIndex],
349  comparisonAttribute = [currentEntry.attributes objectForKey:attribute];
350 
351  while (nextRangeIndex >= 0)
352  {
353  var nextEntry = _rangeEntries[nextRangeIndex];
354 
355  if (CPMaxRange(nextEntry.range) > rangeLimit.location && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
356  {
357  currentEntry = nextEntry;
358  nextRangeIndex--;
359  }
360  else
361  break;
362  }
363 
364  aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
365 
366  // scan forwards
367  currentEntry = _rangeEntries[startingEntryIndex];
368  nextRangeIndex = startingEntryIndex + 1;
369 
370  while (nextRangeIndex < _rangeEntries.length)
371  {
372  var nextEntry = _rangeEntries[nextRangeIndex];
373 
374  if (nextEntry.range.location < CPMaxRange(rangeLimit) && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
375  {
376  currentEntry = nextEntry;
377  nextRangeIndex++;
378  }
379  else
380  break;
381  }
382 
383  aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
384 
385  return comparisonAttribute;
386 }
387 
388 //Comparing Attributed Strings
395 - (BOOL)isEqualToAttributedString:(CPAttributedString)aString
396 {
397  if (!aString)
398  return NO;
399 
400  if (_string !== [aString string])
401  return NO;
402 
403  var myRange = CPMakeRange(),
404  comparisonRange = CPMakeRange(),
405  myAttributes = [self attributesAtIndex:0 effectiveRange:myRange],
406  comparisonAttributes = [aString attributesAtIndex:0 effectiveRange:comparisonRange],
407  length = _string.length;
408 
409  do
410  {
411  if (CPIntersectionRange(myRange, comparisonRange).length > 0 &&
412  ![myAttributes isEqualToDictionary:comparisonAttributes])
413  {
414  return NO;
415  }
416  else if (CPMaxRange(myRange) < CPMaxRange(comparisonRange))
417  myAttributes = [self attributesAtIndex:CPMaxRange(myRange) effectiveRange:myRange];
418  else
419  comparisonAttributes = [aString attributesAtIndex:CPMaxRange(comparisonRange) effectiveRange:comparisonRange];
420  } while (CPMaxRange(CPUnionRange(myRange, comparisonRange)) < length);
421 
422  return YES;
423 }
424 
432 - (BOOL)isEqual:(id)anObject
433 {
434  if (anObject === self)
435  return YES;
436 
437  if ([anObject isKindOfClass:[self class]])
438  return [self isEqualToAttributedString:anObject];
439 
440  return NO;
441 }
442 
443 //Extracting a Substring
451 - (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange
452 {
453  if (!aRange || CPMaxRange(aRange) > _string.length || aRange.location < 0)
454  [CPException raise:CPRangeException
455  reason:"tried to get attributedSubstring for an invalid range: "+(aRange?CPStringFromRange(aRange):"nil")];
456 
457  var newString = [[CPAttributedString alloc] initWithString:_string.substring(aRange.location, CPMaxRange(aRange))],
458  entryIndex = [self _indexOfEntryWithIndex:aRange.location];
459 
460  if (entryIndex === CPNotFound)
461  _CPRaiseRangeException(self, _cmd, aRange.location, _string.length);
462 
463  var currentRangeEntry = _rangeEntries[entryIndex],
464  lastIndex = CPMaxRange(aRange);
465 
466  newString._rangeEntries = [];
467 
468  while (currentRangeEntry && CPMaxRange(currentRangeEntry.range) < lastIndex)
469  {
470  var newEntry = copyRangeEntry(currentRangeEntry);
471  newEntry.range.location -= aRange.location;
472 
473  if (newEntry.range.location < 0)
474  {
475  newEntry.range.length += newEntry.range.location;
476  newEntry.range.location = 0;
477  }
478 
479  newString._rangeEntries.push(newEntry);
480  currentRangeEntry = _rangeEntries[++entryIndex];
481  }
482 
483  if (currentRangeEntry)
484  {
485  var newRangeEntry = copyRangeEntry(currentRangeEntry);
486 
487  newRangeEntry.range.length = CPMaxRange(aRange) - newRangeEntry.range.location;
488  newRangeEntry.range.location -= aRange.location;
489 
490  if (newRangeEntry.range.location < 0)
491  {
492  newRangeEntry.range.length += newRangeEntry.range.location;
493  newRangeEntry.range.location = 0;
494  }
495 
496  newString._rangeEntries.push(newRangeEntry);
497  }
498 
499  return newString;
500 }
501 
502 //Changing Characters
517 - (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
518 {
519  if (!aString)
520  aString = @"";
521 
522  var lastValidIndex = MAX(_rangeEntries.length - 1, 0),
523  startingIndex = [self _indexOfEntryWithIndex:aRange.location];
524 
525  if (startingIndex < 0)
526  startingIndex = lastValidIndex;
527 
528  var endingIndex = [self _indexOfEntryWithIndex:CPMaxRange(aRange)];
529 
530  if (endingIndex < 0)
531  endingIndex = lastValidIndex;
532 
533  var additionalLength = aString.length - aRange.length,
534  patchPosition = startingIndex;
535 
536  _string = _string.substring(0, aRange.location) + aString + _string.substring(CPMaxRange(aRange));
537  var originalLength = _rangeEntries[patchPosition].range.length;
538 
539  if (startingIndex === endingIndex)
540  _rangeEntries[patchPosition].range.length += additionalLength;
541  else
542  {
543  if (CPIntersectionRange(_rangeEntries[patchPosition].range, aRange).length < originalLength)
544  {
545  startingIndex++;
546  }
547 
548  if (endingIndex > startingIndex)
549  {
550  var originalOffset= _rangeEntries[startingIndex].range.location,
551  offsetFromSplicing = CPMaxRange(_rangeEntries[endingIndex].range) - originalOffset;
552  _rangeEntries.splice(startingIndex, endingIndex - startingIndex);
553  _rangeEntries[startingIndex].range = CPMakeRange(originalOffset, offsetFromSplicing);
554  }
555 
556  if (patchPosition !== startingIndex)
557  {
558  var lhsOffset = aString.length - CPIntersectionRange(_rangeEntries[patchPosition].range, aRange).length;
559  _rangeEntries[patchPosition].range.length = originalLength + lhsOffset;
560  var rhsOffset = aString.length - CPIntersectionRange(_rangeEntries[startingIndex].range, aRange).length;
561  _rangeEntries[startingIndex].range.location += lhsOffset;
562  _rangeEntries[startingIndex].range.length += rhsOffset;
563  patchPosition = startingIndex;
564  } else
565  _rangeEntries[patchPosition].range.length += additionalLength;
566  }
567 
568  for (var patchIndex = patchPosition + 1, l = _rangeEntries.length; patchIndex < l; patchIndex++)
569  _rangeEntries[patchIndex].range.location += additionalLength;
570 }
571 
576 - (void)deleteCharactersInRange:(CPRange)aRange
577 {
578  [self replaceCharactersInRange:aRange withString:nil];
579 }
580 
581 //Changing Attributes
593 - (void)setAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
594 {
595  var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
596  endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
597  current = startingEntryIndex;
598 
599  if (current < 0)
600  current = MAX(_rangeEntries.length - 1, 0);
601 
602  if (endingEntryIndex === CPNotFound)
603  endingEntryIndex = _rangeEntries.length;
604 
605  while (current < endingEntryIndex)
606  _rangeEntries[current++].attributes = [aDictionary copy];
607 
608  //necessary?
609  [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
610 }
611 
622 - (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
623 {
624  var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
625  endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
626  current = startingEntryIndex;
627 
628  if (endingEntryIndex === CPNotFound)
629  endingEntryIndex = _rangeEntries.length;
630 
631  while (current < endingEntryIndex)
632  {
633  var keys = [aDictionary allKeys],
634  count = [keys count];
635 
636  while (count--)
637  [_rangeEntries[current].attributes setObject:[aDictionary objectForKey:keys[count]] forKey:keys[count]];
638 
639  current++;
640  }
641 
642  //necessary?
643  [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
644 }
645 
658 - (void)addAttribute:(CPString)anAttribute value:(id)aValue range:(CPRange)aRange
659 {
660  [self addAttributes:@{ anAttribute: aValue } range:aRange];
661 }
662 
669 - (void)removeAttribute:(CPString)anAttribute range:(CPRange)aRange
670 {
671  var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
672  endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
673  current = startingEntryIndex;
674 
675  if (endingEntryIndex === CPNotFound)
676  endingEntryIndex = _rangeEntries.length;
677 
678  while (current < endingEntryIndex)
679  [_rangeEntries[current++].attributes removeObjectForKey:anAttribute];
680 
681  //necessary?
682  [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
683 }
684 
685 //Changing Characters and Attributes
691 - (void)appendAttributedString:(CPAttributedString)aString
692 {
693  [self insertAttributedString:aString atIndex:_string.length];
694 }
695 
705 - (void)insertAttributedString:(CPAttributedString)aString atIndex:(CPUInteger)anIndex
706 {
707  if (anIndex < 0 || anIndex > [self length])
708  [CPException raise:CPRangeException reason:"tried to insert attributed string at an invalid index: " + anIndex];
709 
710  var entryIndexOfNextEntry = [self _indexOfRangeEntryForIndex:anIndex splitOnMaxIndex:YES],
711  otherRangeEntries = aString._rangeEntries,
712  length = [aString length];
713 
714  if (entryIndexOfNextEntry === CPNotFound)
715  entryIndexOfNextEntry = _rangeEntries.length;
716 
717  _string = _string.substring(0, anIndex) + aString._string + _string.substring(anIndex);
718 
719  var current = entryIndexOfNextEntry;
720  while (current < _rangeEntries.length)
721  _rangeEntries[current++].range.location += length;
722 
723  var newRangeEntryCount = otherRangeEntries.length,
724  index = 0;
725 
726  while (index < newRangeEntryCount)
727  {
728  var entryCopy = copyRangeEntry(otherRangeEntries[index++]);
729  entryCopy.range.location += anIndex;
730 
731  _rangeEntries.splice(entryIndexOfNextEntry - 1 + index, 0, entryCopy);
732  }
733 
734  [self _coalesceRangeEntriesFromIndex:MAX(0, entryIndexOfNextEntry - 1) toIndex:MIN(entryIndexOfNextEntry + 1, _rangeEntries.length - 1)];
735 }
736 
745 - (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString
746 {
747  [self deleteCharactersInRange:aRange];
748  [self insertAttributedString:aString atIndex:aRange.location];
749 }
750 
756 - (void)setAttributedString:(CPAttributedString)aString
757 {
758  _string = aString._string;
759  _rangeEntries = [aString._rangeEntries arrayByApplyingBlock:function(entry)
760  {
761  return copyRangeEntry(entry);
762  }];
763 }
764 
765 //Private methods
766 - (Number)_indexOfRangeEntryForIndex:(unsigned)characterIndex splitOnMaxIndex:(BOOL)split
767 {
768  var index = [self _indexOfEntryWithIndex:characterIndex];
769 
770  if (index === CPNotFound)
771  return index;
772 
773  var rangeEntry = _rangeEntries[index];
774 
775  if (rangeEntry.range.location === characterIndex || (CPMaxRange(rangeEntry.range) - 1 === characterIndex && !split))
776  return index;
777 
778  var newEntries = splitRangeEntryAtIndex(rangeEntry, characterIndex);
779  _rangeEntries.splice(index, 1, newEntries[0], newEntries[1]);
780  index++;
781 
782  return index;
783 }
784 
785 - (void)_coalesceRangeEntriesFromIndex:(unsigned)start toIndex:(unsigned)end
786 {
787  var current = start;
788 
789  if (end >= _rangeEntries.length)
790  end = _rangeEntries.length - 1;
791 
792  while (current < end)
793  {
794  var a = _rangeEntries[current],
795  b = _rangeEntries[current + 1];
796 
797  if (a && b && [a.attributes isEqualToDictionary:b.attributes])
798  {
799  a.range.length = CPMaxRange(b.range) - a.range.location;
800  _rangeEntries.splice(current + 1, 1);
801  end--;
802  }
803  else
804  current++;
805  }
806 }
807 
808 //Grouping Changes
813 - (void)beginEditing
814 {
815  //do nothing (says cocotron and gnustep)
816 }
817 
822 - (void)endEditing
823 {
824  //do nothing (says cocotron and gnustep)
825 }
826 
827 @end
828 
829 var CPAttributedStringStringKey = "CPAttributedStringString",
830  CPAttributedStringRangesKey = "CPAttributedStringRanges",
831  CPAttributedStringAttributesKey = "CPAttributedStringAttributes";
832 
834 
835 - (id)initWithCoder:(CPCoder)aCoder
836 {
837  self = [self init];
838 
839  if (self)
840  {
841  _string = [aCoder decodeObjectForKey:CPAttributedStringStringKey];
842  var decodedRanges = [aCoder decodeObjectForKey:CPAttributedStringRangesKey],
843  decodedAttributes = [aCoder decodeObjectForKey:CPAttributedStringAttributesKey];
844 
845  _rangeEntries = [];
846 
847  for (var i = 0, l = decodedRanges.length; i < l; i++)
848  _rangeEntries.push(makeRangeEntry(decodedRanges[i], decodedAttributes[i]));
849  }
850 
851  return self;
852 }
853 
854 - (void)encodeWithCoder:(CPCoder)aCoder
855 {
856  [aCoder encodeObject:_string forKey:CPAttributedStringStringKey];
857 
858  var rangesForEncoding = [],
859  dictsForEncoding = [];
860 
861  for (var i = 0, l = _rangeEntries.length; i < l; i++)
862  {
863  rangesForEncoding.push(_rangeEntries[i].range);
864  dictsForEncoding.push(_rangeEntries[i].attributes);
865  }
866 
867  [aCoder encodeObject:rangesForEncoding forKey:CPAttributedStringRangesKey];
868  [aCoder encodeObject:dictsForEncoding forKey:CPAttributedStringAttributesKey];
869 }
870 
871 @end
872 
881 {
882  id __doxygen__;
883 }
884 
885 @end
886 
887 var isEqual = function(a, b)
888 {
889  if (a === b)
890  return YES;
891 
892  if ([a respondsToSelector:@selector(isEqual:)] && [a isEqual:b])
893  return YES;
894 
895  return NO;
896 };
897 
898 var makeRangeEntry = function(/*CPRange*/aRange, /*CPDictionary*/attributes)
899 {
900  return {range:aRange, attributes:[attributes copy]};
901 };
902 
903 var copyRangeEntry = function(/*RangeEntry*/aRangeEntry)
904 {
905  return makeRangeEntry(CPMakeRangeCopy(aRangeEntry.range), [aRangeEntry.attributes copy]);
906 };
907 
908 var splitRangeEntryAtIndex = function(/*RangeEntry*/aRangeEntry, /*unsigned*/anIndex)
909 {
910  var newRangeEntry = copyRangeEntry(aRangeEntry),
911  cachedIndex = CPMaxRange(aRangeEntry.range);
912 
913  aRangeEntry.range.length = anIndex - aRangeEntry.range.location;
914  newRangeEntry.range.location = anIndex;
915  newRangeEntry.range.length = cachedIndex - anIndex;
916  newRangeEntry.attributes = [newRangeEntry.attributes copy];
917 
918  return [aRangeEntry, newRangeEntry];
919 };
var CPAttributedStringRangesKey
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
CPOrderedAscending
Definition: CPObjJRuntime.j:48
function CPUnionRange(lhsRange, rhsRange)
Definition: CPRange.j:106
id init()
Definition: CALayer.j:126
var isEqual
CPOrderedSame
Definition: CPObjJRuntime.j:54
var splitRangeEntryAtIndex
CPDictionary attributesAtIndex:effectiveRange:(CPUInteger anIndex, [effectiveRange] CPRangePointer aRange)
void raise:reason:(CPString aName, [reason] CPString aReason)
Definition: CPException.j:66
void addAttributes:range:(CPDictionary aDictionary, [range] CPRange aRange)
function CPRangeInRange(lhsRange, rhsRange)
Definition: CPRange.j:137
A mutable key-value pair collection.
Definition: CPDictionary.h:2
A mutable character string with attributes.
var CPAttributedStringAttributesKey
function CPMaxRange(aRange)
Definition: CPRange.j:70
An immutable string (collection of characters).
Definition: CPString.h:2
id objectForKey:(id aKey)
Definition: CPDictionary.j:515
BOOL isEqualToAttributedString:(CPAttributedString aString)
CPOrderedDescending
Definition: CPObjJRuntime.j:60
function CPIntersectionRange(lhsRange, rhsRange)
Definition: CPRange.j:120
int length()
Definition: CPString.j:186
id initWithString:attributes:(CPString aString, [attributes] CPDictionary attributes)
var copyRangeEntry
function CPMakeRangeCopy(aRange)
Definition: CPRange.j:48
void insertAttributedString:atIndex:(CPAttributedString aString, [atIndex] CPUInteger anIndex)
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
id valueForKey:(CPString aKey)
CPNotFound
Definition: CPObjJRuntime.j:62
id init()
Definition: CPObject.j:145
var makeRangeEntry
function CPLocationInRange(aLocation, aRange)
Definition: CPRange.j:93
FrameUpdater prototype start
var CPAttributedStringStringKey
CPArray allKeys()
Definition: CPDictionary.j:308
void replaceCharactersInRange:withString:(CPRange aRange, [withString] CPString aString)
void deleteCharactersInRange:(CPRange aRange)
CPRange function CPMakeRange(location, length)
Definition: CPRange.j:37
CPDictionary copy()
Definition: CPDictionary.j:292
id alloc()
Definition: CPObject.j:130