Put NSAutoreleasePool usage around other distributed notification observer methods
[adiumx.git] / Source / AIContactStatusColoringPlugin.m
blobc33114b2b8c66dacecb99c7478f233b0be6950ab
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "AIAbstractListController.h"
18 #import <Adium/AIContactControllerProtocol.h>
19 #import "AIContactStatusColoringPlugin.h"
20 #import <Adium/AIInterfaceControllerProtocol.h>
21 #import <Adium/AIPreferenceControllerProtocol.h>
22 #import "AIListThemeWindowController.h"
23 #import <AIUtilities/AIColorAdditions.h>
26 #import <AIUtilities/AIDictionaryAdditions.h>
27 #import <AIUtilities/AIMutableOwnerArray.h>
28 #import <Adium/AIListContact.h>
29 #import <Adium/AIChat.h>
30 #import <Adium/AIContentTyping.h>
31 #import <Adium/AIListObject.h>
32 #import <Adium/AIMetaContact.h>
34 @interface AIContactStatusColoringPlugin (PRIVATE)
35 - (void)addToFlashSet:(AIListObject *)inObject;
36 - (void)removeFromFlashSet:(AIListObject *)inObject;
37 - (void)preferencesChanged:(NSNotification *)notification;
38 - (void)_applyColorToContact:(AIListContact *)inObject;
39 @end
41 @implementation AIContactStatusColoringPlugin
43 #define OFFLINE_IMAGE_OPACITY   0.5
44 #define FULL_IMAGE_OPACITY              1.0
45 #define OPACITY_REFRESH                 0.2
47 #define CONTACT_STATUS_COLORING_DEFAULT_PREFS   @"ContactStatusColoringDefaults"
49 - (void)installPlugin
51     //init
52     flashingListObjects = [[NSMutableSet alloc] init];
53     awayColor = nil;
54     idleColor = nil;
55     signedOffColor = nil;
56     signedOnColor = nil;
57     typingColor = nil;
58     unviewedContentColor = nil;
59     onlineColor = nil;
60     awayAndIdleColor = nil;
61         offlineColor = nil;
62         
63     awayInvertedColor = nil;
64     idleInvertedColor = nil;
65     signedOffInvertedColor = nil;
66     signedOnInvertedColor = nil;
67     typingInvertedColor = nil;
68     unviewedContentInvertedColor = nil;
69     onlineInvertedColor = nil;
70     awayAndIdleInvertedColor = nil;
71         offlineInvertedColor = nil;
72         
73     awayLabelColor = nil;
74     idleLabelColor = nil;
75     signedOffLabelColor = nil;
76     signedOnLabelColor = nil;
77     typingLabelColor = nil;
78     unviewedContentLabelColor = nil;
79     onlineLabelColor = nil;
80     awayAndIdleLabelColor = nil;
81         offlineLabelColor = nil;
82         
83         offlineImageFading = NO;
84         
85                 
86         interestedKeysSet = [[NSSet alloc] initWithObjects:
87                                                  KEY_TYPING, KEY_UNVIEWED_CONTENT,  @"StatusType", @"IsIdle",
88                                                  @"Online", @"Signed On",  @"Signed Off", @"IsMobile", nil];
90         id<AIPreferenceController> preferenceController = [adium preferenceController];
92     //Setup our preferences
93     [preferenceController registerDefaults:[NSDictionary dictionaryNamed:CONTACT_STATUS_COLORING_DEFAULT_PREFS
94                                                                                                                                                 forClass:[self class]]
95                                                                                   forGroup:PREF_GROUP_LIST_THEME];
96     
97     //Observe preferences and list objects
98         [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_LIST_THEME];
99         [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_CONTACT_LIST];
100         [[adium contactController] registerListObjectObserver:self];
103 - (void)uninstallPlugin
105         [[adium preferenceController] unregisterPreferenceObserver:self];
106         [[adium    contactController] unregisterListObjectObserver:self];
107         [[adium  interfaceController] unregisterFlashObserver:self];    
110 - (void)dealloc
112         [flashingListObjects release];
113         [interestedKeysSet release];
114         
115         [super dealloc];
119 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
121     NSSet               *modifiedAttributes = nil;
123         if ([inObject isKindOfClass:[AIListContact class]]) {
124                 if (inModifiedKeys == nil || [inModifiedKeys intersectsSet:interestedKeysSet]) {
125                         //Update the contact's text color
126                         [self _applyColorToContact:(AIListContact *)inObject];
127                         modifiedAttributes = [NSSet setWithObjects:@"Text Color", @"Inverted Text Color", @"Label Color", nil];
128                 }
129                 
130                 //Update our flash set
131                 if (flashUnviewedContentEnabled &&
132                         (inModifiedKeys == nil || [inModifiedKeys containsObject:KEY_UNVIEWED_CONTENT])) {
133                         int unviewedContent = [inObject integerStatusObjectForKey:KEY_UNVIEWED_CONTENT];
134                         
135                         if (unviewedContent && ![flashingListObjects containsObject:inObject]) { //Start flashing
136                                 [self addToFlashSet:inObject];
137                         } else if (!unviewedContent && [flashingListObjects containsObject:inObject]) { //Stop flashing
138                                 [self removeFromFlashSet:inObject];
139                         }
140                 }
141         }
143     return modifiedAttributes;
146 //Applies the correct color to the passed object
147 - (void)_applyColorToContact:(AIListContact *)inContact
149     NSColor                     *color = nil, *invertedColor = nil, *labelColor = nil;
150     int                         unviewedContent, away;
151     int                         idle;
152         float                   opacity = FULL_IMAGE_OPACITY;
153         BOOL                    isEvent = NO;
155     //Prefetch the value for unviewed content, we need it multiple times below
156     unviewedContent = [inContact integerStatusObjectForKey:KEY_UNVIEWED_CONTENT];
158     //Unviewed content
159     if ((!color && !labelColor) && (unviewedContentEnabled && unviewedContent)) {
160                 /* Use the unviewed content settings if:
161                  *      - we aren't flashing or
162                  *  - every other flash. */
163         if (!flashUnviewedContentEnabled || ([[adium interfaceController] flashState] % 2)) {
164             color = unviewedContentColor;
165             invertedColor = unviewedContentInvertedColor;
166             labelColor = unviewedContentLabelColor;
167                         isEvent = YES;
168         }
169     }
171     //Offline, Signed off, signed on, or typing
172     if ((!color && !labelColor)) {
173                 if (offlineEnabled && (![inContact online] &&
174                                                           ![inContact integerStatusObjectForKey:@"Signed Off"])) {
175                         color = offlineColor;
176                         invertedColor = offlineInvertedColor;
177                         labelColor = offlineLabelColor;
178                         if (offlineImageFading) opacity = OFFLINE_IMAGE_OPACITY;                        
179                         
180                 } else if (signedOffEnabled && ([inContact integerStatusObjectForKey:@"Signed Off"])) {
181             color = signedOffColor;
182             invertedColor = signedOffInvertedColor;
183             labelColor = signedOffLabelColor;
184                         isEvent = YES;
186         } else if (signedOnEnabled && [inContact integerStatusObjectForKey:@"Signed On"]) {
187                         color = signedOnColor;
188             invertedColor = signedOnInvertedColor;
189             labelColor = signedOnLabelColor;
190                         isEvent = YES;
191                         
192         } else if (typingEnabled && ([inContact integerStatusObjectForKey:KEY_TYPING] == AITyping)) {
193             color = typingColor;
194             invertedColor = typingInvertedColor;
195             labelColor = typingLabelColor;
196                         isEvent = YES;
197                         
198         }
199     }
201         if ((!color && !labelColor) && mobileEnabled && [inContact isMobile]) {
202                 color = mobileColor;
203                 invertedColor = mobileInvertedColor;
204                 labelColor = mobileLabelColor;          
205         }
207     if ((!color && !labelColor)) {
208                 AIStatusSummary statusSummary = [inContact statusSummary];
210         //Prefetch these values, we need them multiple times below
211         away = [inContact integerStatusObjectForKey:@"Away" fromAnyContainedObject:NO];
212         idle = [inContact integerStatusObjectForKey:@"Idle" fromAnyContainedObject:NO];
214         //Idle And Away, Away, or Idle
215         if (awayAndIdleEnabled && (statusSummary == AIAwayAndIdleStatus)) {
216             color = awayAndIdleColor;
217             invertedColor = awayAndIdleInvertedColor;
218             labelColor = awayAndIdleLabelColor;
219         } else if (awayEnabled && ((statusSummary == AIAwayStatus) || (statusSummary == AIAwayAndIdleStatus))) {
220             color = awayColor;
221             invertedColor = awayInvertedColor;
222             labelColor = awayLabelColor;
223         } else if (idleEnabled && ((statusSummary == AIIdleStatus) || (statusSummary == AIAwayAndIdleStatus))) {
224             color = idleColor;
225             invertedColor = idleInvertedColor;
226             labelColor = idleLabelColor;
227         }
228     }
230     //Online
231     if ((!color && !labelColor) && onlineEnabled && [inContact online]) {
232         color = onlineColor;
233         invertedColor = onlineInvertedColor;
234         labelColor = onlineLabelColor;
235     }
237     //Apply the color and opacity
238     [[inContact displayArrayForKey:@"Text Color"] setObject:color withOwner:self];
239     [[inContact displayArrayForKey:@"Inverted Text Color"] setObject:invertedColor withOwner:self];
240     [[inContact displayArrayForKey:@"Label Color"] setObject:labelColor withOwner:self];
241         [[inContact displayArrayForKey:@"Image Opacity"] setObject:[NSNumber numberWithFloat:opacity] withOwner:self];
242         [[inContact displayArrayForKey:@"Is Event"] setObject:[NSNumber numberWithBool:isEvent] withOwner:self];
245 //Flash all handles with unviewed content
246 - (void)flash:(int)value
248     NSEnumerator        *enumerator;
249     AIListContact       *object;
251     enumerator = [flashingListObjects objectEnumerator];
252     while ((object = [enumerator nextObject])) {
253         [self _applyColorToContact:object];
254         
255         //Force a redraw
256         [[adium notificationCenter] postNotificationName:ListObject_AttributesChanged 
257                                                                                                   object:object
258                                                                                                 userInfo:[NSDictionary dictionaryWithObject:[NSArray arrayWithObjects:@"Text Color", @"Label Color", @"Inverted Text Color", nil] forKey:@"Keys"]];
259     }
263  * @brief Add a handle to the flash set
264  */
265 - (void)addToFlashSet:(AIListObject *)inObject
267     //Ensure that we're observing the flashing
268     if ([flashingListObjects count] == 0) {
269         [[adium interfaceController] registerFlashObserver:self];
270     }
272     //Add the contact to our flash set
273     [flashingListObjects addObject:inObject];
274     [self flash:[[adium interfaceController] flashState]];
278  * @brief Remove a contact from the flash set
279  */
280 - (void)removeFromFlashSet:(AIListObject *)inObject
282     //Remove the contact from our flash set
283     [flashingListObjects removeObject:inObject];
285     //If we have no more flashing contacts, stop observing the flashes
286     if ([flashingListObjects count] == 0) {
287         [[adium interfaceController] unregisterFlashObserver:self];
288     }
292 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
293                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
295         if ([group isEqualToString:PREF_GROUP_LIST_THEME]) {
296                 //Release the old values..
297                 [signedOffColor release]; signedOffColor = nil;
298                 [signedOnColor release]; signedOnColor = nil;
299                 [awayColor release]; awayColor = nil;
300                 [idleColor release]; idleColor = nil;
301                 [typingColor release]; typingColor = nil;
302                 [unviewedContentColor release]; unviewedContentColor = nil;
303                 [onlineColor release]; onlineColor = nil;
304                 [awayAndIdleColor release]; awayAndIdleColor = nil;
305                 [offlineColor release]; offlineColor = nil;
306                 [mobileColor release]; mobileColor = nil;
307                 
308                 [signedOffInvertedColor release]; signedOffInvertedColor = nil;
309                 [signedOnInvertedColor release]; signedOnInvertedColor = nil;
310                 [awayInvertedColor release]; awayInvertedColor = nil;
311                 [idleInvertedColor release]; idleInvertedColor = nil;
312                 [typingInvertedColor release]; typingInvertedColor = nil;
313                 [unviewedContentInvertedColor release]; unviewedContentInvertedColor = nil;
314                 [onlineInvertedColor release]; onlineInvertedColor = nil;
315                 [awayAndIdleInvertedColor release]; awayAndIdleInvertedColor = nil;
316                 [offlineInvertedColor release]; offlineInvertedColor = nil;
317                 [mobileInvertedColor release]; mobileInvertedColor = nil;
318                 
319                 [awayLabelColor release]; awayLabelColor = nil;
320                 [idleLabelColor release]; idleLabelColor = nil;
321                 [signedOffLabelColor release]; signedOffLabelColor = nil;
322                 [signedOnLabelColor release]; signedOnLabelColor = nil;
323                 [typingLabelColor release]; typingLabelColor = nil;
324                 [unviewedContentLabelColor release]; unviewedContentLabelColor = nil;
325                 [onlineLabelColor release]; onlineLabelColor = nil;
326                 [awayAndIdleLabelColor release]; awayAndIdleLabelColor = nil;
327                 [offlineLabelColor release]; offlineLabelColor = nil;
328                 [mobileLabelColor release]; mobileLabelColor = nil;
329                 
330                 if ((awayEnabled = [[prefDict objectForKey:KEY_AWAY_ENABLED] boolValue])) {
331                         awayColor = [[[prefDict objectForKey:KEY_AWAY_COLOR] representedColor] retain];
332                         awayLabelColor = [[[prefDict objectForKey:KEY_LABEL_AWAY_COLOR] representedColor] retain];
333                         awayInvertedColor = [[awayColor colorWithInvertedLuminance] retain];
334                 }
335         
336                 if ((idleEnabled = [[prefDict objectForKey:KEY_IDLE_ENABLED] boolValue])) {
337                         idleColor = [[[prefDict objectForKey:KEY_IDLE_COLOR] representedColor] retain];
338                         idleLabelColor = [[[prefDict objectForKey:KEY_LABEL_IDLE_COLOR] representedColor] retain];
339                         idleInvertedColor = [[idleColor colorWithInvertedLuminance] retain];
340                 }
342                 if ((signedOnEnabled = [[prefDict objectForKey:KEY_SIGNED_ON_ENABLED] boolValue])) {
343                         signedOnColor = [[[prefDict objectForKey:KEY_SIGNED_ON_COLOR] representedColor] retain];        
344                         signedOnLabelColor = [[[prefDict objectForKey:KEY_LABEL_SIGNED_ON_COLOR] representedColor] retain];
345                         signedOnInvertedColor = [[signedOnColor colorWithInvertedLuminance] retain];
346                 }
347                 
348                 if ((signedOffEnabled = [[prefDict objectForKey:KEY_SIGNED_OFF_ENABLED] boolValue])) {
349                         signedOffColor = [[[prefDict objectForKey:KEY_SIGNED_OFF_COLOR] representedColor] retain];
350                         signedOffLabelColor = [[[prefDict objectForKey:KEY_LABEL_SIGNED_OFF_COLOR] representedColor] retain];
351                         signedOffInvertedColor = [[signedOffColor colorWithInvertedLuminance] retain];
352                 }               
353                 
354                 if ((typingEnabled = [[prefDict objectForKey:KEY_TYPING_ENABLED] boolValue])) {
355                         typingColor = [[[prefDict objectForKey:KEY_TYPING_COLOR] representedColor] retain];
356                         typingLabelColor = [[[prefDict objectForKey:KEY_LABEL_TYPING_COLOR] representedColor] retain];                  
357                         typingInvertedColor = [[typingColor colorWithInvertedLuminance] retain];
358                 }
359                 
360                 if ((unviewedContentEnabled = [[prefDict objectForKey:KEY_UNVIEWED_ENABLED] boolValue])) {
361                         unviewedContentColor = [[[prefDict objectForKey:KEY_UNVIEWED_COLOR] representedColor] retain];
362                         unviewedContentLabelColor = [[[prefDict objectForKey:KEY_LABEL_UNVIEWED_COLOR] representedColor] retain];
363                         unviewedContentInvertedColor = [[unviewedContentColor colorWithInvertedLuminance] retain];                      
364                 }
365                 
366                 if ((onlineEnabled = [[prefDict objectForKey:KEY_ONLINE_ENABLED] boolValue])) {
367                         onlineColor = [[[prefDict objectForKey:KEY_ONLINE_COLOR] representedColor] retain];
368                         onlineLabelColor = [[[prefDict objectForKey:KEY_LABEL_ONLINE_COLOR] representedColor] retain];
369                         onlineInvertedColor = [[onlineColor colorWithInvertedLuminance] retain];
370                 }
372                 if ((awayAndIdleEnabled = [[prefDict objectForKey:KEY_IDLE_AWAY_ENABLED] boolValue])) {
373                         awayAndIdleColor = [[[prefDict objectForKey:KEY_IDLE_AWAY_COLOR] representedColor] retain];
374                         awayAndIdleLabelColor = [[[prefDict objectForKey:KEY_LABEL_IDLE_AWAY_COLOR] representedColor] retain];
375                         awayAndIdleInvertedColor = [[awayAndIdleColor colorWithInvertedLuminance] retain];                      
376                 }
377                 
378                 if ((offlineEnabled = [[prefDict objectForKey:KEY_OFFLINE_ENABLED] boolValue])) {
379                         offlineColor = [[[prefDict objectForKey:KEY_OFFLINE_COLOR] representedColor] retain];
380                         offlineLabelColor = [[[prefDict objectForKey:KEY_LABEL_OFFLINE_COLOR] representedColor] retain];
381                         offlineInvertedColor = [[offlineColor colorWithInvertedLuminance] retain];                      
382                 }
383                 
384                 if ((mobileEnabled = [[prefDict objectForKey:KEY_MOBILE_ENABLED] boolValue])) {
385                         mobileColor = [[[prefDict objectForKey:KEY_MOBILE_COLOR] representedColor] retain];             
386                         mobileLabelColor = [[[prefDict objectForKey:KEY_LABEL_MOBILE_COLOR] representedColor] retain];
387                         mobileInvertedColor = [[mobileColor colorWithInvertedLuminance] retain];                        
388                 }
390                 offlineImageFading = [[prefDict objectForKey:KEY_LIST_THEME_FADE_OFFLINE_IMAGES] boolValue];
392                 //Update all objects
393                 if (!firstTime) {
394                         [[adium contactController] updateAllListObjectsForObserver:self];
395                 }
397         } else if ([group isEqualToString:PREF_GROUP_CONTACT_LIST]) {
398                 BOOL oldFlashUnviewedContentEnabled = flashUnviewedContentEnabled;
399                 
400                 flashUnviewedContentEnabled = [[prefDict objectForKey:KEY_CL_FLASH_UNVIEWED_CONTENT] boolValue];
402                 if (oldFlashUnviewedContentEnabled && !flashUnviewedContentEnabled) {
403                         //Clear our flash set if we aren't flashing for unviewed content now but we were before
404                         NSEnumerator    *enumerator = [[[flashingListObjects copy] autorelease] objectEnumerator];
405                         AIListContact   *listContact;
407                         while ((listContact = [enumerator nextObject])) {
408                                 [self removeFromFlashSet:listContact];
409                         }
410                         
411                         //Make our colors end up right (if we were on an off-flash) by updating all list objects
412                         [[adium contactController] updateAllListObjectsForObserver:self];
413                 } else if (!oldFlashUnviewedContentEnabled && flashUnviewedContentEnabled) {
414                         if (!firstTime) {
415                                 //Update all list objects so we start flashing
416                                 [[adium contactController] updateAllListObjectsForObserver:self];
417                         }
418                 }
419         }
422 @end