2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
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.
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.
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.
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;
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"
52 flashingListObjects = [[NSMutableSet alloc] init];
58 unviewedContentColor = nil;
60 awayAndIdleColor = nil;
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;
75 signedOffLabelColor = nil;
76 signedOnLabelColor = nil;
77 typingLabelColor = nil;
78 unviewedContentLabelColor = nil;
79 onlineLabelColor = nil;
80 awayAndIdleLabelColor = nil;
81 offlineLabelColor = nil;
83 offlineImageFading = NO;
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];
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];
112 [flashingListObjects release];
113 [interestedKeysSet release];
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];
130 //Update our flash set
131 if (flashUnviewedContentEnabled &&
132 (inModifiedKeys == nil || [inModifiedKeys containsObject:KEY_UNVIEWED_CONTENT])) {
133 int unviewedContent = [inObject integerStatusObjectForKey:KEY_UNVIEWED_CONTENT];
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];
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;
152 float opacity = FULL_IMAGE_OPACITY;
155 //Prefetch the value for unviewed content, we need it multiple times below
156 unviewedContent = [inContact integerStatusObjectForKey:KEY_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;
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;
180 } else if (signedOffEnabled && ([inContact integerStatusObjectForKey:@"Signed Off"])) {
181 color = signedOffColor;
182 invertedColor = signedOffInvertedColor;
183 labelColor = signedOffLabelColor;
186 } else if (signedOnEnabled && [inContact integerStatusObjectForKey:@"Signed On"]) {
187 color = signedOnColor;
188 invertedColor = signedOnInvertedColor;
189 labelColor = signedOnLabelColor;
192 } else if (typingEnabled && ([inContact integerStatusObjectForKey:KEY_TYPING] == AITyping)) {
194 invertedColor = typingInvertedColor;
195 labelColor = typingLabelColor;
201 if ((!color && !labelColor) && mobileEnabled && [inContact isMobile]) {
203 invertedColor = mobileInvertedColor;
204 labelColor = mobileLabelColor;
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))) {
221 invertedColor = awayInvertedColor;
222 labelColor = awayLabelColor;
223 } else if (idleEnabled && ((statusSummary == AIIdleStatus) || (statusSummary == AIAwayAndIdleStatus))) {
225 invertedColor = idleInvertedColor;
226 labelColor = idleLabelColor;
231 if ((!color && !labelColor) && onlineEnabled && [inContact online]) {
233 invertedColor = onlineInvertedColor;
234 labelColor = onlineLabelColor;
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];
256 [[adium notificationCenter] postNotificationName:ListObject_AttributesChanged
258 userInfo:[NSDictionary dictionaryWithObject:[NSArray arrayWithObjects:@"Text Color", @"Label Color", @"Inverted Text Color", nil] forKey:@"Keys"]];
263 * @brief Add a handle to the flash set
265 - (void)addToFlashSet:(AIListObject *)inObject
267 //Ensure that we're observing the flashing
268 if ([flashingListObjects count] == 0) {
269 [[adium interfaceController] registerFlashObserver:self];
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
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];
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;
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;
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;
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];
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];
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];
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];
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];
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];
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];
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];
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];
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];
390 offlineImageFading = [[prefDict objectForKey:KEY_LIST_THEME_FADE_OFFLINE_IMAGES] boolValue];
394 [[adium contactController] updateAllListObjectsForObserver:self];
397 } else if ([group isEqualToString:PREF_GROUP_CONTACT_LIST]) {
398 BOOL oldFlashUnviewedContentEnabled = flashUnviewedContentEnabled;
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];
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) {
415 //Update all list objects so we start flashing
416 [[adium contactController] updateAllListObjectsForObserver:self];