* Adium is the legal property of its developers, whose names are listed in the copyright file included * with this source distribution. * This program is free software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * You should have received a copy of the GNU General Public License along with this program; if not, * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #import "AIAbstractListController.h" #import <Adium/AIContactControllerProtocol.h> #import "AIContactStatusColoringPlugin.h" #import <Adium/AIInterfaceControllerProtocol.h> #import "AIListThemeWindowController.h" #import <AIUtilities/AIColorAdditions.h> #import <AIUtilities/AIDictionaryAdditions.h> #import <AIUtilities/AIMutableOwnerArray.h> #import <Adium/AIListContact.h> #import <Adium/AIContentTyping.h> #import <Adium/AIListObject.h> #import <Adium/AIMetaContact.h> #import <Adium/AIListBookmark.h> @interface AIContactStatusColoringPlugin () - (void)addToFlashSet:(AIListObject *)inObject; - (void)removeFromFlashSet:(AIListObject *)inObject; - (void)_applyColorToContact:(AIListContact *)inObject; @implementation AIContactStatusColoringPlugin #define OFFLINE_IMAGE_OPACITY 0.5f #define FULL_IMAGE_OPACITY 1.0f #define OPACITY_REFRESH 0.2f #define CONTACT_STATUS_COLORING_DEFAULT_PREFS @"ContactStatusColoringDefaults" flashingListObjects = [[NSMutableSet alloc] init]; unviewedContentColor = nil; signedOffInvertedColor = nil; signedOnInvertedColor = nil; typingInvertedColor = nil; unviewedContentInvertedColor = nil; onlineInvertedColor = nil; awayAndIdleInvertedColor = nil; offlineInvertedColor = nil; signedOffLabelColor = nil; signedOnLabelColor = nil; unviewedContentLabelColor = nil; awayAndIdleLabelColor = nil; interestedKeysSet = [[NSSet alloc] initWithObjects: KEY_TYPING, KEY_UNVIEWED_CONTENT, @"listObjectStatusType", @"isIdle", @"isOnline", @"signedOn", @"signedOff", @"isMobile", nil]; id<AIPreferenceController> preferenceController = adium.preferenceController; [preferenceController registerDefaults:[NSDictionary dictionaryNamed:CONTACT_STATUS_COLORING_DEFAULT_PREFS forGroup:PREF_GROUP_LIST_THEME]; //Observe preferences and list objects [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_LIST_THEME]; [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_CONTACT_LIST]; [[AIContactObserverManager sharedManager] registerListObjectObserver:self]; [adium.preferenceController unregisterPreferenceObserver:self]; [[AIContactObserverManager sharedManager] unregisterListObjectObserver:self]; [adium.interfaceController unregisterFlashObserver:self]; [flashingListObjects release]; [interestedKeysSet release]; - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent NSSet *modifiedAttributes = nil; if ([inObject isKindOfClass:[AIListContact class]] || [inObject isKindOfClass:[AIListBookmark class]]) { if (inModifiedKeys == nil || [inModifiedKeys intersectsSet:interestedKeysSet]) { //Update the contact's text color [self _applyColorToContact:(AIListContact *)inObject]; modifiedAttributes = [NSSet setWithObjects:@"textColor", @"invertedTextColor", @"labelColor", nil]; if (flashUnviewedContentEnabled && (inModifiedKeys == nil || [inModifiedKeys containsObject:KEY_UNVIEWED_CONTENT])) { NSInteger unviewedContent = [inObject integerValueForProperty:KEY_UNVIEWED_CONTENT]; if (unviewedContent && ![flashingListObjects containsObject:inObject]) { //Start flashing [self addToFlashSet:inObject]; } else if (!unviewedContent && [flashingListObjects containsObject:inObject]) { //Stop flashing [self removeFromFlashSet:inObject]; return modifiedAttributes; //Applies the correct color to the passed object - (void)_applyColorToContact:(AIListContact *)inContact NSColor *color = nil, *invertedColor = nil, *labelColor = nil; NSInteger unviewedContent; CGFloat opacity = FULL_IMAGE_OPACITY; // Only in the case the other contact is typing or has unread contact should we apply it to the meta contact as well BOOL applyToMetaToo = NO; //Prefetch the value for unviewed content, we need it multiple times below unviewedContent = [inContact integerValueForProperty:KEY_UNVIEWED_CONTENT]; if ((!color && !labelColor) && (unviewedContentEnabled && unviewedContent)) { /* Use the unviewed content settings if: * - we aren't flashing or * - every other flash. */ if (!flashUnviewedContentEnabled || ([adium.interfaceController flashState] % 2)) { color = unviewedContentColor; invertedColor = unviewedContentInvertedColor; labelColor = unviewedContentLabelColor; //Offline, Signed off, signed on, or typing if ((!color && !labelColor)) { if (offlineEnabled && (!inContact.online && ![inContact boolValueForProperty:@"signedOff"])) { invertedColor = offlineInvertedColor; labelColor = offlineLabelColor; if (offlineImageFading) opacity = OFFLINE_IMAGE_OPACITY; } else if (signedOffEnabled && [inContact boolValueForProperty:@"signedOff"]) { invertedColor = signedOffInvertedColor; labelColor = signedOffLabelColor; } else if (signedOnEnabled && [inContact boolValueForProperty:@"signedOn"]) { invertedColor = signedOnInvertedColor; labelColor = signedOnLabelColor; } else if (typingEnabled && ([inContact intValueForProperty:KEY_TYPING] == AITyping)) { invertedColor = typingInvertedColor; labelColor = typingLabelColor; if ((!color && !labelColor) && mobileEnabled && inContact.isMobile) { invertedColor = mobileInvertedColor; labelColor = mobileLabelColor; if ((!color && !labelColor)) { AIStatusSummary statusSummary = [inContact statusSummary]; //Idle And Away, Away, or Idle if (awayAndIdleEnabled && (statusSummary == AIAwayAndIdleStatus)) { color = awayAndIdleColor; invertedColor = awayAndIdleInvertedColor; labelColor = awayAndIdleLabelColor; } else if (awayEnabled && ((statusSummary == AIAwayStatus) || (statusSummary == AIAwayAndIdleStatus))) { invertedColor = awayInvertedColor; labelColor = awayLabelColor; } else if (idleEnabled && ((statusSummary == AIIdleStatus) || (statusSummary == AIAwayAndIdleStatus))) { invertedColor = idleInvertedColor; labelColor = idleLabelColor; if ((!color && !labelColor) && onlineEnabled && inContact.online) { invertedColor = onlineInvertedColor; labelColor = onlineLabelColor; //Apply the color and opacity [inContact setValue:color [inContact setValue:invertedColor forProperty:@"invertedTextColor" [inContact setValue:labelColor forProperty:@"labelColor" [inContact setValue:[NSNumber numberWithDouble:opacity] forProperty:@"imageOpacity" [inContact setValue:[NSNumber numberWithBool:isEvent] if (applyToMetaToo && [inContact metaContact]) { [[inContact metaContact] setValue:color [[inContact metaContact] setValue:invertedColor forProperty:@"invertedTextColor" [[inContact metaContact] setValue:labelColor forProperty:@"labelColor" [[inContact metaContact] setValue:[NSNumber numberWithDouble:opacity] forProperty:@"imageOpacity" [[inContact metaContact] setValue:[NSNumber numberWithBool:isEvent] //Flash all handles with unviewed content NSSet *modifiedKeys = [NSSet setWithObjects:@"Text Color", @"Label Color", @"Inverted Text Color", nil]; for (object in flashingListObjects) { [self _applyColorToContact:object]; [[AIContactObserverManager sharedManager] listObjectAttributesChanged:object modifiedKeys:modifiedKeys]; * @brief Add a handle to the flash set - (void)addToFlashSet:(AIListObject *)inObject //Ensure that we're observing the flashing if ([flashingListObjects count] == 0) { [adium.interfaceController registerFlashObserver:self]; //Add the contact to our flash set [flashingListObjects addObject:inObject]; [self flash:[adium.interfaceController flashState]]; * @brief Remove a contact from the flash set - (void)removeFromFlashSet:(AIListObject *)inObject //Remove the contact from our flash set [flashingListObjects removeObject:inObject]; //If we have no more flashing contacts, stop observing the flashes if ([flashingListObjects count] == 0) { [adium.interfaceController unregisterFlashObserver:self]; - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime if ([group isEqualToString:PREF_GROUP_LIST_THEME]) { //Release the old values.. [signedOffColor release]; signedOffColor = nil; [signedOnColor release]; signedOnColor = nil; [awayColor release]; awayColor = nil; [idleColor release]; idleColor = nil; [typingColor release]; typingColor = nil; [unviewedContentColor release]; unviewedContentColor = nil; [onlineColor release]; onlineColor = nil; [awayAndIdleColor release]; awayAndIdleColor = nil; [offlineColor release]; offlineColor = nil; [mobileColor release]; mobileColor = nil; [signedOffInvertedColor release]; signedOffInvertedColor = nil; [signedOnInvertedColor release]; signedOnInvertedColor = nil; [awayInvertedColor release]; awayInvertedColor = nil; [idleInvertedColor release]; idleInvertedColor = nil; [typingInvertedColor release]; typingInvertedColor = nil; [unviewedContentInvertedColor release]; unviewedContentInvertedColor = nil; [onlineInvertedColor release]; onlineInvertedColor = nil; [awayAndIdleInvertedColor release]; awayAndIdleInvertedColor = nil; [offlineInvertedColor release]; offlineInvertedColor = nil; [mobileInvertedColor release]; mobileInvertedColor = nil; [awayLabelColor release]; awayLabelColor = nil; [idleLabelColor release]; idleLabelColor = nil; [signedOffLabelColor release]; signedOffLabelColor = nil; [signedOnLabelColor release]; signedOnLabelColor = nil; [typingLabelColor release]; typingLabelColor = nil; [unviewedContentLabelColor release]; unviewedContentLabelColor = nil; [onlineLabelColor release]; onlineLabelColor = nil; [awayAndIdleLabelColor release]; awayAndIdleLabelColor = nil; [offlineLabelColor release]; offlineLabelColor = nil; [mobileLabelColor release]; mobileLabelColor = nil; if ((awayEnabled = [[prefDict objectForKey:KEY_AWAY_ENABLED] boolValue])) { awayColor = [[[prefDict objectForKey:KEY_AWAY_COLOR] representedColor] retain]; awayLabelColor = [[[prefDict objectForKey:KEY_LABEL_AWAY_COLOR] representedColor] retain]; awayInvertedColor = [[awayColor colorWithInvertedLuminance] retain]; if ((idleEnabled = [[prefDict objectForKey:KEY_IDLE_ENABLED] boolValue])) { idleColor = [[[prefDict objectForKey:KEY_IDLE_COLOR] representedColor] retain]; idleLabelColor = [[[prefDict objectForKey:KEY_LABEL_IDLE_COLOR] representedColor] retain]; idleInvertedColor = [[idleColor colorWithInvertedLuminance] retain]; if ((signedOnEnabled = [[prefDict objectForKey:KEY_SIGNED_ON_ENABLED] boolValue])) { signedOnColor = [[[prefDict objectForKey:KEY_SIGNED_ON_COLOR] representedColor] retain]; signedOnLabelColor = [[[prefDict objectForKey:KEY_LABEL_SIGNED_ON_COLOR] representedColor] retain]; signedOnInvertedColor = [[signedOnColor colorWithInvertedLuminance] retain]; if ((signedOffEnabled = [[prefDict objectForKey:KEY_SIGNED_OFF_ENABLED] boolValue])) { signedOffColor = [[[prefDict objectForKey:KEY_SIGNED_OFF_COLOR] representedColor] retain]; signedOffLabelColor = [[[prefDict objectForKey:KEY_LABEL_SIGNED_OFF_COLOR] representedColor] retain]; signedOffInvertedColor = [[signedOffColor colorWithInvertedLuminance] retain]; if ((typingEnabled = [[prefDict objectForKey:KEY_TYPING_ENABLED] boolValue])) { typingColor = [[[prefDict objectForKey:KEY_TYPING_COLOR] representedColor] retain]; typingLabelColor = [[[prefDict objectForKey:KEY_LABEL_TYPING_COLOR] representedColor] retain]; typingInvertedColor = [[typingColor colorWithInvertedLuminance] retain]; if ((unviewedContentEnabled = [[prefDict objectForKey:KEY_UNVIEWED_ENABLED] boolValue])) { unviewedContentColor = [[[prefDict objectForKey:KEY_UNVIEWED_COLOR] representedColor] retain]; unviewedContentLabelColor = [[[prefDict objectForKey:KEY_LABEL_UNVIEWED_COLOR] representedColor] retain]; unviewedContentInvertedColor = [[unviewedContentColor colorWithInvertedLuminance] retain]; if ((onlineEnabled = [[prefDict objectForKey:KEY_ONLINE_ENABLED] boolValue])) { onlineColor = [[[prefDict objectForKey:KEY_ONLINE_COLOR] representedColor] retain]; onlineLabelColor = [[[prefDict objectForKey:KEY_LABEL_ONLINE_COLOR] representedColor] retain]; onlineInvertedColor = [[onlineColor colorWithInvertedLuminance] retain]; if ((awayAndIdleEnabled = [[prefDict objectForKey:KEY_IDLE_AWAY_ENABLED] boolValue])) { awayAndIdleColor = [[[prefDict objectForKey:KEY_IDLE_AWAY_COLOR] representedColor] retain]; awayAndIdleLabelColor = [[[prefDict objectForKey:KEY_LABEL_IDLE_AWAY_COLOR] representedColor] retain]; awayAndIdleInvertedColor = [[awayAndIdleColor colorWithInvertedLuminance] retain]; if ((offlineEnabled = [[prefDict objectForKey:KEY_OFFLINE_ENABLED] boolValue])) { offlineColor = [[[prefDict objectForKey:KEY_OFFLINE_COLOR] representedColor] retain]; offlineLabelColor = [[[prefDict objectForKey:KEY_LABEL_OFFLINE_COLOR] representedColor] retain]; offlineInvertedColor = [[offlineColor colorWithInvertedLuminance] retain]; if ((mobileEnabled = [[prefDict objectForKey:KEY_MOBILE_ENABLED] boolValue])) { mobileColor = [[[prefDict objectForKey:KEY_MOBILE_COLOR] representedColor] retain]; mobileLabelColor = [[[prefDict objectForKey:KEY_LABEL_MOBILE_COLOR] representedColor] retain]; mobileInvertedColor = [[mobileColor colorWithInvertedLuminance] retain]; offlineImageFading = [[prefDict objectForKey:KEY_LIST_THEME_FADE_OFFLINE_IMAGES] boolValue]; [[AIContactObserverManager sharedManager] updateAllListObjectsForObserver:self]; } else if ([group isEqualToString:PREF_GROUP_CONTACT_LIST]) { BOOL oldFlashUnviewedContentEnabled = flashUnviewedContentEnabled; flashUnviewedContentEnabled = [[prefDict objectForKey:KEY_CL_FLASH_UNVIEWED_CONTENT] boolValue]; if (oldFlashUnviewedContentEnabled && !flashUnviewedContentEnabled) { //Clear our flash set if we aren't flashing for unviewed content now but we were before for (AIListContact *listContact in [[flashingListObjects copy] autorelease]) { [self removeFromFlashSet:listContact]; //Make our colors end up right (if we were on an off-flash) by updating all list objects [[AIContactObserverManager sharedManager] updateAllListObjectsForObserver:self]; } else if (!oldFlashUnviewedContentEnabled && flashUnviewedContentEnabled) { //Update all list objects so we start flashing [[AIContactObserverManager sharedManager] updateAllListObjectsForObserver:self];