Adium

Changeset 14454

Show
Ignore:
Timestamp:
12/16/2005 10:37:40 AM (3 years ago)
Author:
evands
Message:

An emoticon now displays if and only if one of the following is true:

  • It begins or ends the string
  • It is bordered by spaces or line breaks on both sides
  • It is bordered by a period on the left and a space or line break the right
  • It is bordered by emoticons on both sides or by an emoticon on the left and a period, space, or line break on the right

No more "sad code!"

Fixes #721.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/Source/AIEmoticonController.m

    r14430 r14454  
    155155} 
    156156 
     157/* 
     158 * @brief Perform a single emoticon replacement 
     159 * 
     160 * This method may call itself recursively to perform additional adjacent emoticon replacements 
     161 * 
     162 * @result The location in messageString of the beginning of the emoticon replaced, or NSNotFound if no replacement was made 
     163 */ 
     164- (unsigned int)replaceAnEmoticonStartingAtLocation:(unsigned *)currentLocation 
     165                                                                                 fromString:(NSString *)messageString 
     166                                                   originalAttributedString:(NSAttributedString *)originalAttributedString 
     167                                                                                 intoString:(NSMutableAttributedString **)newMessage 
     168                                                                   replacementCount:(unsigned *)replacementCount 
     169                                                                 callingRecursively:(BOOL)callingRecursively 
     170                                                                                        context:(id)serviceClassContext 
     171{ 
     172        unsigned int    messageStringLength = [messageString length]; 
     173        unsigned int    originalEmoticonLocation = NSNotFound; 
     174 
     175        //Find the next occurence of a suspected emoticon 
     176        *currentLocation = [messageString rangeOfCharacterFromSet:[self emoticonStartCharacterSet] 
     177                                                                                                          options:NSLiteralSearch 
     178                                                                                                                range:NSMakeRange(*currentLocation,  
     179                                                                                                                                                  messageStringLength - *currentLocation)].location; 
     180        if (*currentLocation != NSNotFound) { 
     181                //Use paired arrays so multiple emoticons can qualify for the same text equivalent 
     182                NSMutableArray  *candidateEmoticons = nil; 
     183                NSMutableArray  *candidateEmoticonTextEquivalents = nil;                 
     184                unichar         currentCharacter = [messageString characterAtIndex:*currentLocation]; 
     185                NSString        *currentCharacterString = [NSString stringWithFormat:@"%C", currentCharacter]; 
     186                NSEnumerator    *emoticonEnumerator; 
     187                AIEmoticon      *emoticon;      
     188 
     189                //Check for the presence of all emoticons starting with this character 
     190                emoticonEnumerator = [[[self emoticonIndex] objectForKey:currentCharacterString] objectEnumerator]; 
     191                while ((emoticon = [emoticonEnumerator nextObject])) { 
     192                        NSEnumerator        *textEnumerator; 
     193                        NSString            *text; 
     194                         
     195                        textEnumerator = [[emoticon textEquivalents] objectEnumerator]; 
     196                        while ((text = [textEnumerator nextObject])) { 
     197                                int     textLength = [text length]; 
     198                                 
     199                                if (textLength != 0) { //Invalid emoticon files may let empty text equivalents sneak in 
     200                                                                           //If there is not enough room in the string for this text, we can skip it 
     201                                        if (*currentLocation + textLength <= messageStringLength) { 
     202                                                if ([messageString compare:text 
     203                                                                                   options:NSLiteralSearch 
     204                                                                                         range:NSMakeRange(*currentLocation, textLength)] == NSOrderedSame) { 
     205                                                        //Ignore emoticons within links 
     206                                                        if ([originalAttributedString attribute:NSLinkAttributeName 
     207                                                                                                                        atIndex:*currentLocation 
     208                                                                                                         effectiveRange:nil] == nil) { 
     209                                                                if (!candidateEmoticons) { 
     210                                                                        candidateEmoticons = [[NSMutableArray alloc] init]; 
     211                                                                        candidateEmoticonTextEquivalents = [[NSMutableArray alloc] init]; 
     212                                                                } 
     213                                                                 
     214                                                                [candidateEmoticons addObject:emoticon]; 
     215                                                                [candidateEmoticonTextEquivalents addObject:text]; 
     216                                                        } 
     217                                                } 
     218                                        } 
     219                                } 
     220                        } 
     221                } 
     222                 
     223                if ([candidateEmoticons count]) { 
     224                        NSString                                        *replacementString; 
     225                        NSMutableAttributedString   *replacement; 
     226                        int                                                     textLength; 
     227                        NSRange                                         emoticonRangeInNewMessage; 
     228                        int                                                     amountToIncreaseCurrentLocation = 0; 
     229 
     230                        originalEmoticonLocation = *currentLocation; 
     231 
     232                        //Use the most appropriate, longest string of those which could be used for the emoticon text we found here 
     233                        emoticon = [self _bestReplacementFromEmoticons:candidateEmoticons 
     234                                                                                   withEquivalents:candidateEmoticonTextEquivalents 
     235                                                                                                   context:serviceClassContext 
     236                                                                                                equivalent:&replacementString 
     237                                                                                  equivalentLength:&textLength]; 
     238                        emoticonRangeInNewMessage = NSMakeRange(*currentLocation - *replacementCount, textLength); 
     239 
     240                        /* We want to show this emoticon if there is: 
     241                         *              It begins or ends the string 
     242                         *              It is bordered by spaces or line breaks on both sides 
     243                         *              It is bordered by a period on the left and a space or line break the right 
     244                         *              It is bordered by emoticons on both sides or by an emoticon on the left and a period, space, or line break on the right 
     245                         */ 
     246                        BOOL    acceptable = NO; 
     247                        if ((messageStringLength == ((originalEmoticonLocation + textLength))) || //Ends the string 
     248                                (originalEmoticonLocation == 0)) { //Begins the string 
     249                                acceptable = YES; 
     250                        } 
     251                        if (!acceptable) { 
     252                                /* Bordered by spaces or line breaks, or by a period on the left and a space or a line break on the right 
     253                                 * If we're being called recursively, we have a potential emoticon to our left;  we only need to check the right. 
     254                                 * This is also true if we're not being called recursively but there's an NSAttachmentAttribute to our left. 
     255                                 *              That will happen if, for example, the string is ":):) ". The first emoticon is at the start of the line and 
     256                                 *              so is immediately acceptable. The second should be acceptable because it is to the right of an emoticon and 
     257                                 *              the left of a space. 
     258                                 */ 
     259                                char    previousCharacter = [messageString characterAtIndex:(originalEmoticonLocation - 1)] ; 
     260                                char    nextCharacter = [messageString characterAtIndex:(originalEmoticonLocation + textLength)] ; 
     261 
     262                                if ((callingRecursively || (previousCharacter == ' ') || (previousCharacter == '\n') || (previousCharacter == '\r') || (previousCharacter == '.') || 
     263                                         (*newMessage && [*newMessage attribute:NSAttachmentAttributeName 
     264                                                                                                        atIndex:(emoticonRangeInNewMessage.location - 1)  
     265                                                                                         effectiveRange:NULL])) && 
     266                                        ((nextCharacter == ' ') || (nextCharacter == '\n') || (nextCharacter == '\r') || (nextCharacter == '.'))) { 
     267                                        acceptable = YES; 
     268 
     269                                        //We can skip the next character 
     270                                        //amountToIncreaseCurrentLocation = 1; 
     271                                } 
     272                        } 
     273                        if (!acceptable) { 
     274                                /* If we still haven't determined it to be acceptable, look ahead. 
     275                                 * If we do a replacement adjacent to this emoticon, we can do this one, too. 
     276                                 */ 
     277                                unsigned int newCurrentLocation = *currentLocation; 
     278                                unsigned int nextEmoticonLocation; 
     279                                                 
     280                                /* Call ourself recursively, starting just after the end of the current emoticon candidate 
     281                                 * If the return value is not NSNotFound, an emoticon was found and replaced ahead of us. Discontinuous searching for the win. 
     282                                 */ 
     283                                newCurrentLocation += textLength; 
     284                                nextEmoticonLocation = [self replaceAnEmoticonStartingAtLocation:&newCurrentLocation 
     285                                                                                                                                          fromString:messageString 
     286                                                                                                                originalAttributedString:originalAttributedString 
     287                                                                                                                                          intoString:newMessage 
     288                                                                                                                                replacementCount:replacementCount 
     289                                                                                                                          callingRecursively:YES 
     290                                                                                                                                                 context:serviceClassContext]; 
     291                                if (nextEmoticonLocation != NSNotFound) { 
     292                                        if (nextEmoticonLocation == (*currentLocation + textLength)) { 
     293                                                /* The next emoticon is immediately after the candidate we're looking at right now. That means 
     294                                                * our current candidate is in fact an emoticon (since it borders another emoticon). 
     295                                                */ 
     296                                                acceptable = YES; 
     297                                        } 
     298                                } 
     299 
     300                                /* Whether the current candidate is acceptable or not, we can now skip ahead to just after the next emoticon if 
     301                                 * there is one. If there isn't, we can skip ahead to the end of the string. 
     302                                 * 
     303                                 * We do -1 because we will do a +1 at the end of the loop no matter what. 
     304                                 */                              
     305                                if (newCurrentLocation != NSNotFound) { 
     306                                        amountToIncreaseCurrentLocation = (newCurrentLocation - *currentLocation) - 1; 
     307                                } else { 
     308                                        amountToIncreaseCurrentLocation = (messageStringLength - *currentLocation) - 1;                                  
     309                                } 
     310                        } 
     311 
     312                        if (acceptable) { 
     313                                replacement = [emoticon attributedStringWithTextEquivalent:replacementString]; 
     314                                 
     315                                //grab the original attributes, to ensure that the background is not lost in a message consisting only of an emoticon 
     316                                [replacement addAttributes:[originalAttributedString attributesAtIndex:originalEmoticonLocation 
     317                                                                                                                                                effectiveRange:nil]  
     318                                                                         range:NSMakeRange(0,1)]; 
     319                                 
     320                                //insert the emoticon 
     321                                if (!(*newMessage)) *newMessage = [originalAttributedString mutableCopy]; 
     322                                [*newMessage replaceCharactersInRange:emoticonRangeInNewMessage 
     323                                                                 withAttributedString:replacement]; 
     324                                 
     325                                //Update where we are in the original and replacement messages 
     326                                *replacementCount += textLength-1; 
     327                                *currentLocation += textLength-1; 
     328                        } else { 
     329                                //Didn't find an acceptable emoticon, so we should return NSNotFound 
     330                                originalEmoticonLocation = NSNotFound; 
     331                        } 
     332                         
     333                        //If appropriate, skip ahead by amountToIncreaseCurrentLocation 
     334                        *currentLocation += amountToIncreaseCurrentLocation; 
     335                } 
     336 
     337                //Always increment the loop 
     338                *currentLocation += 1; 
     339 
     340                [candidateEmoticons release]; 
     341                [candidateEmoticonTextEquivalents release]; 
     342        } 
     343 
     344        return originalEmoticonLocation; 
     345} 
     346 
    157347//Insert graphical emoticons into a string 
    158348- (NSMutableAttributedString *)_convertEmoticonsInMessage:(NSAttributedString *)inMessage context:(id)context 
    159349{ 
    160     NSCharacterSet              *emoticonStartCharacterSet = [self emoticonStartCharacterSet]; 
    161     NSDictionary                *emoticonIndex = [self emoticonIndex]; 
    162350    NSString                    *messageString = [inMessage string]; 
    163351    NSMutableAttributedString   *newMessage = nil; //We avoid creating a new string unless necessary 
     
    180368         
    181369    //Number of characters we've replaced so far (used to calcluate placement in the destination string) 
    182         int                         replacementCount = 0;  
     370        unsigned int   replacementCount = 0;  
    183371 
    184372        messageStringLength = [messageString length]; 
    185373    while (currentLocation != NSNotFound && currentLocation < messageStringLength) { 
    186         //Find the next occurence of a suspected emoticon 
    187         currentLocation = [messageString rangeOfCharacterFromSet:emoticonStartCharacterSet 
    188                                                          options:0  
    189                                                            range:NSMakeRange(currentLocation,  
    190                                                                                                                                                          messageStringLength - currentLocation)].location; 
    191                  
    192                 //Use paired arrays so multiple emoticons can qualify for the same text equivalent 
    193         NSMutableArray  *candidateEmoticons = nil; 
    194                 NSMutableArray  *candidateEmoticonTextEquivalents = nil; 
    195                  
    196         if (currentLocation != NSNotFound) { 
    197             unichar         currentCharacter = [messageString characterAtIndex:currentLocation]; 
    198             NSString        *currentCharacterString = [NSString stringWithFormat:@"%C", currentCharacter]; 
    199             NSEnumerator    *emoticonEnumerator; 
    200             AIEmoticon      *emoticon;      
    201  
    202             //Check for the presence of all emoticons starting with this character 
    203             emoticonEnumerator = [[emoticonIndex objectForKey:currentCharacterString] objectEnumerator]; 
    204             while ((emoticon = [emoticonEnumerator nextObject])) { 
    205                 NSEnumerator        *textEnumerator; 
    206                 NSString            *text; 
    207                  
    208                 textEnumerator = [[emoticon textEquivalents] objectEnumerator]; 
    209                 while ((text = [textEnumerator nextObject])) { 
    210                     int     textLength = [text length]; 
    211  
    212                     if (textLength != 0) { //Invalid emoticon files may let empty text equivalents sneak in 
    213                         //If there is not enough room in the string for this text, we can skip it 
    214                         if (currentLocation + textLength <= messageStringLength) { 
    215                             if ([messageString compare:text options:0 range:NSMakeRange(currentLocation, textLength)] == NSOrderedSame) { 
    216                                 //Ignore emoticons within links 
    217                                 if ([inMessage attribute:NSLinkAttributeName atIndex:currentLocation effectiveRange:nil] == nil) { 
    218                                                                         if (!candidateEmoticons) { 
    219                                                                                 candidateEmoticons = [[NSMutableArray alloc] init]; 
    220                                                                                 candidateEmoticonTextEquivalents = [[NSMutableArray alloc] init]; 
    221                                                                         } 
    222                                                                          
    223                                                                         [candidateEmoticons addObject:emoticon]; 
    224                                                                         [candidateEmoticonTextEquivalents addObject:text]; 
    225                                 } 
    226                             } 
    227                         } 
    228                     } 
    229                 } 
    230             } 
    231                          
    232             if ([candidateEmoticons count]) { 
    233                 NSString                                        *replacementString; 
    234                 AIEmoticon                                      *emoticon; 
    235                 NSMutableAttributedString   *replacement; 
    236                 int                                                     textLength; 
    237                                  
    238                                 //Use the most appropriate, longest string of those which could be used for the emoticon text we found here 
    239                                 emoticon = [self _bestReplacementFromEmoticons:candidateEmoticons 
    240                                                                                            withEquivalents:candidateEmoticonTextEquivalents 
    241                                                                                                            context:serviceClassContext 
    242                                                                                                         equivalent:&replacementString 
    243                                                                                           equivalentLength:&textLength]; 
    244                                 replacement = [emoticon attributedStringWithTextEquivalent:replacementString]; 
    245                                      
    246                 //grab the original attributes, to ensure that the background is not lost in a message consisting only of an emoticon 
    247                 [replacement addAttributes:[inMessage attributesAtIndex:currentLocation  
    248                                                          effectiveRange:nil]  
    249                                                                   range:NSMakeRange(0,1)]; 
    250                                      
    251                 //insert the emoticon 
    252                 if (!newMessage) newMessage = [inMessage mutableCopy]; 
    253                                 [newMessage replaceCharactersInRange:NSMakeRange(currentLocation - replacementCount, textLength) 
    254                                 withAttributedString:replacement]; 
    255                 //Update where we are in the original and replacement messages 
    256                 replacementCount += textLength-1; 
    257                 currentLocation += textLength-1; 
    258          
    259                 //Invalidate the enumerators to stop scanning prematurely 
    260                 //textEnumerator = nil; emoticonEnumerator = nil; 
    261             } 
    262          
    263                         //Move to the next possible location of an emoticon 
    264                         currentLocation++; 
    265         } 
    266  
    267                 [candidateEmoticons release]; 
    268                 [candidateEmoticonTextEquivalents release]; 
    269     } 
    270  
    271     return newMessage ? [newMessage autorelease] : inMessage; 
     374                [self replaceAnEmoticonStartingAtLocation:&currentLocation 
     375                                                                           fromString:messageString 
     376                                                 originalAttributedString:inMessage 
     377                                                                           intoString:&newMessage 
     378                                                                 replacementCount:&replacementCount 
     379                                                           callingRecursively:NO 
     380                                                                                  context:serviceClassContext]; 
     381    } 
     382 
     383    return (newMessage ? [newMessage autorelease] : inMessage); 
    272384} 
    273385