Plugins/Dock Icon Badging/AIDockNameOverlay.m
changeset 3670 add0c83648a5
child 3688 7e0b0cdfc8ff
equal deleted inserted replaced
3669:7905c08bc4d8 3670:add0c83648a5
       
     1 #import "AIDockNameOverlay.h"
       
     2 #import "AIDockController.h"
       
     3 #import <Adium/AIChatControllerProtocol.h>
       
     4 #import <Adium/AIContactAlertsControllerProtocol.h>
       
     5 #import <Adium/AIStatusControllerProtocol.h>
       
     6 #import <AIUtilities/AIParagraphStyleAdditions.h>
       
     7 #import <AIUtilities/AIImageAdditions.h>
       
     8 #import <Adium/AIChat.h>
       
     9 #import <Adium/AIAccount.h>
       
    10 #import <AIUtilities/AIArrayAdditions.h>
       
    11 #import <Adium/AIAbstractListController.h>
       
    12 #import <AIUtilities/AIColorAdditions.h>
       
    13 
       
    14 
       
    15 #define DOCK_OVERLAY_ALERT_SHORT	AILocalizedString(@"Display name in the dock icon",nil)
       
    16 #define DOCK_OVERLAY_ALERT_LONG		DOCK_OVERLAY_ALERT_SHORT
       
    17 
       
    18 @interface AIDockNameOverlay ()
       
    19 - (void)flushPreferenceColorCache;
       
    20 - (void)drawOverlay;
       
    21 - (void)removeDockOverlay:(NSTimer *)removeTimer;
       
    22 @end
       
    23 
       
    24 @implementation AIDockNameOverlay
       
    25 - (void)installPlugin
       
    26 {
       
    27 	//Install our contact alert
       
    28 	[adium.contactAlertsController registerActionID:DOCK_OVERLAY_ALERT_IDENTIFIER withHandler:self];
       
    29 
       
    30 	overlayObjectsArray = [[NSMutableArray alloc] init];
       
    31 	
       
    32 	//Register as a contact observer (For signed on / signed off)
       
    33 	[[AIContactObserverManager sharedManager] registerListObjectObserver:self];
       
    34 	
       
    35 	[adium.chatController registerChatObserver:self];
       
    36 	
       
    37 	//Observe pref changes
       
    38 	[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE];
       
    39 	[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_LIST_THEME];
       
    40 }
       
    41 
       
    42 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
       
    43 							object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
       
    44 {	
       
    45 	if ([group isEqualToString:PREF_GROUP_LIST_THEME]) {
       
    46 		//Grab colors from status coloring plugin's prefs
       
    47 		[self flushPreferenceColorCache];
       
    48 		signedOffColor = [[[prefDict objectForKey:KEY_SIGNED_OFF_COLOR] representedColor] retain];
       
    49 		signedOnColor = [[[prefDict objectForKey:KEY_SIGNED_ON_COLOR] representedColor] retain];
       
    50 		unviewedContentColor = [[[prefDict objectForKey:KEY_UNVIEWED_COLOR] representedColor] retain];
       
    51 		
       
    52 		backSignedOffColor = [[[prefDict objectForKey:KEY_LABEL_SIGNED_OFF_COLOR] representedColor] retain];
       
    53 		backSignedOnColor = [[[prefDict objectForKey:KEY_LABEL_SIGNED_ON_COLOR] representedColor] retain];
       
    54 		backUnviewedContentColor = [[[prefDict objectForKey:KEY_LABEL_UNVIEWED_COLOR] representedColor] retain];
       
    55 	}
       
    56 }
       
    57 
       
    58 - (void)flushPreferenceColorCache
       
    59 {
       
    60 	[signedOffColor release]; signedOffColor = nil;
       
    61 	[signedOnColor release]; signedOnColor = nil;
       
    62 	[unviewedContentColor release]; unviewedContentColor = nil;
       
    63 	[backSignedOffColor release]; backSignedOffColor = nil;
       
    64 	[backSignedOnColor release]; backSignedOnColor = nil;
       
    65 	[backUnviewedContentColor release]; backUnviewedContentColor = nil;
       
    66 }
       
    67 
       
    68 - (void)uninstallPlugin
       
    69 {
       
    70 	[adium.preferenceController unregisterPreferenceObserver:self];
       
    71 	[adium.chatController unregisterChatObserver:self];
       
    72 }
       
    73 
       
    74 /*!
       
    75  * @brief Short description
       
    76  * @result A short localized description of the action
       
    77  */
       
    78 - (NSString *)shortDescriptionForActionID:(NSString *)actionID
       
    79 {
       
    80 	return DOCK_OVERLAY_ALERT_SHORT;
       
    81 }
       
    82 
       
    83 /*!
       
    84  * @brief Long description
       
    85  * @result A longer localized description of the action which should take into account the details dictionary as appropraite.
       
    86  */
       
    87 - (NSString *)longDescriptionForActionID:(NSString *)actionID withDetails:(NSDictionary *)details
       
    88 {
       
    89 	return DOCK_OVERLAY_ALERT_LONG;
       
    90 }
       
    91 
       
    92 /*!
       
    93  * @brief Image
       
    94  */
       
    95 - (NSImage *)imageForActionID:(NSString *)actionID
       
    96 {
       
    97 	return [NSImage imageNamed:@"DockAlert" forClass:[self class]];
       
    98 }
       
    99 
       
   100 /*!
       
   101  * @brief Details pane
       
   102  * @result An <tt>AIModularPane</tt> to use for configuring this action, or nil if no configuration is possible.
       
   103  */
       
   104 - (AIModularPane *)detailsPaneForActionID:(NSString *)actionID
       
   105 {
       
   106 	return nil;
       
   107 }
       
   108 
       
   109 /*!
       
   110  * @brief Perform an action
       
   111  *
       
   112  * @param actionID The ID of the action to perform
       
   113  * @param listObject The listObject associated with the event triggering the action. It may be nil
       
   114  * @param details If set by the details pane when the action was created, the details dictionary for this particular action
       
   115  * @param eventID The eventID which triggered this action
       
   116  * @param userInfo Additional information associated with the event; userInfo's type will vary with the actionID.
       
   117  */
       
   118 - (BOOL)performActionID:(NSString *)actionID forListObject:(AIListObject *)listObject withDetails:(NSDictionary *)details triggeringEventID:(NSString *)eventID userInfo:(id)userInfo
       
   119 {
       
   120 	BOOL isMessageEvent = [adium.contactAlertsController isMessageEvent:eventID];
       
   121 	
       
   122 	if (isMessageEvent) {
       
   123 		AIChat *chat;
       
   124 		
       
   125 		if ((chat = [userInfo objectForKey:@"AIChat"]) &&
       
   126 			(chat != adium.interfaceController.activeChat)) {
       
   127 			if (![overlayObjectsArray containsObjectIdenticalTo:chat])
       
   128 				[overlayObjectsArray addObject:chat];
       
   129 			
       
   130 			//Wait until the next run loop so that this event is done processing and our unviewed content count is correct
       
   131 			[self performSelector:@selector(drawOverlay)
       
   132 					   withObject:nil
       
   133 					   afterDelay:0];
       
   134 		}
       
   135 		
       
   136 	} else if (listObject) {
       
   137 		NSTimer *removeTimer;
       
   138 		
       
   139 		//Clear any current timer for this object to have its overlay removed
       
   140 		if ((removeTimer = [listObject valueForProperty:@"DockOverlayRemoveTimer"])) [removeTimer invalidate];
       
   141 		
       
   142 		//Add a timer to remove this overlay
       
   143 		removeTimer = [NSTimer scheduledTimerWithTimeInterval:5
       
   144 													   target:self
       
   145 													 selector:@selector(removeDockOverlay:)
       
   146 													 userInfo:listObject
       
   147 													  repeats:NO];
       
   148 		[listObject setValue:removeTimer
       
   149 				 forProperty:@"DockOverlayRemoveTimer"
       
   150 					  notify:NotifyNever];
       
   151 		
       
   152 		if (![overlayObjectsArray containsObjectIdenticalTo:listObject])
       
   153 			[overlayObjectsArray addObject:listObject];
       
   154 		
       
   155 		[self drawOverlay];
       
   156 	}
       
   157 	
       
   158 	return YES;
       
   159 }
       
   160 
       
   161 /*!
       
   162  * @brief Allow multiple actions?
       
   163  *
       
   164  * If this method returns YES, every one of this action associated with the triggering event will be executed.
       
   165  * If this method returns NO, only the first will be.
       
   166  */
       
   167 - (BOOL)allowMultipleActionsWithID:(NSString *)actionID
       
   168 {
       
   169 	return NO;
       
   170 }
       
   171 
       
   172 /*!
       
   173  * @brief When a chat no longer has unviewed content remove it from display
       
   174  */
       
   175 - (NSSet *)updateChat:(AIChat *)inChat keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
       
   176 {
       
   177 	if (inModifiedKeys == nil || [inModifiedKeys containsObject:KEY_UNVIEWED_CONTENT]) {
       
   178 		if (![inChat unviewedContentCount]) {
       
   179 			[overlayObjectsArray removeObjectIdenticalTo:inChat];
       
   180 			[self drawOverlay];
       
   181 		}
       
   182 	}
       
   183 	
       
   184 	return nil;
       
   185 }
       
   186 
       
   187 /*!
       
   188  * @brief When an account signs on or off force an overlay update as its contacts statuses may have silently changed
       
   189  */
       
   190 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
       
   191 {
       
   192 	if ([inObject isKindOfClass:[AIAccount class]]) {
       
   193 		if ([inModifiedKeys containsObject:@"isOnline"]) {
       
   194 			BOOL madeChanges = NO;
       
   195 			
       
   196 			for (AIListObject *listObject in [[overlayObjectsArray copy] autorelease]) {
       
   197 				if (([listObject respondsToSelector:@selector(account)]) &&
       
   198 					([(id)listObject account] == inObject) &&
       
   199 					([overlayObjectsArray containsObjectIdenticalTo:listObject])) {
       
   200 					[overlayObjectsArray removeObjectIdenticalTo:listObject];
       
   201 					madeChanges = YES;
       
   202 				}
       
   203 			}
       
   204 			
       
   205 			if (madeChanges) [self drawOverlay];
       
   206 		}
       
   207 	}
       
   208 	
       
   209 	return nil;
       
   210 }
       
   211 
       
   212 - (void)removeDockOverlay:(NSTimer *)removeTimer
       
   213 {
       
   214 	AIListObject *inObject = [removeTimer userInfo];
       
   215 	[overlayObjectsArray removeObjectIdenticalTo:inObject];
       
   216 	
       
   217 	[inObject setValue:nil
       
   218 		   forProperty:@"DockOverlayRemoveTimer"
       
   219 				notify:NotifyNever];
       
   220 	
       
   221 	[self drawOverlay];
       
   222 }
       
   223 
       
   224 - (void)drawOverlay
       
   225 {
       
   226 	NSFont				*font;
       
   227 	NSParagraphStyle	*paragraphStyle;
       
   228 	CGFloat				iconHeight;
       
   229 	CGFloat				top, bottom;
       
   230 	NSImage				*image = [[NSImage alloc] initWithSize:NSMakeSize(128, 128)];
       
   231 	
       
   232 	iconHeight = 30.0f;
       
   233 	bottom = 2;
       
   234 	top = bottom + iconHeight;
       
   235 	
       
   236 	//Set up the string details
       
   237 	font = [NSFont boldSystemFontOfSize:24.0f];
       
   238 	paragraphStyle = [NSParagraphStyle styleWithAlignment:NSCenterTextAlignment lineBreakMode:NSLineBreakByClipping];
       
   239 	
       
   240 	[image lockFocus];
       
   241 	
       
   242 	//Clear our image
       
   243 	[[NSColor clearColor] set];
       
   244 	NSRectFillUsingOperation(NSMakeRect(0, 0, 128, 128), NSCompositeCopy);
       
   245 	
       
   246 	//Draw overlays for each contact
       
   247 	for (ESObjectWithProperties *object in [overlayObjectsArray reverseObjectEnumerator]) {
       
   248 		if (top >= 128)
       
   249 			break;
       
   250 		
       
   251 		CGFloat			left, right, arcRadius, stringInset;
       
   252 		NSBezierPath	*path;
       
   253 		NSColor			*backColor = nil, *textColor = nil, *borderColor = nil;
       
   254 		
       
   255 		//Create the pill frame
       
   256 		arcRadius = (iconHeight / 2.0f);
       
   257 		stringInset = (iconHeight / 4.0f);
       
   258 		left = 1 + arcRadius;
       
   259 		right = 127 - arcRadius;
       
   260 		
       
   261 		path = [NSBezierPath bezierPath];
       
   262 		[path setLineWidth:((iconHeight/2.0f) * 0.13333f)];
       
   263 		//Top
       
   264 		[path moveToPoint: NSMakePoint(left, top)];
       
   265 		[path lineToPoint: NSMakePoint(right, top)];
       
   266 		
       
   267 		//Right rounded cap
       
   268 		[path appendBezierPathWithArcWithCenter:NSMakePoint(right, top - arcRadius)
       
   269 										 radius:arcRadius
       
   270 									 startAngle:90
       
   271 									   endAngle:0
       
   272 									  clockwise:YES];
       
   273 		[path lineToPoint: NSMakePoint(right + arcRadius, bottom + arcRadius)];
       
   274 		[path appendBezierPathWithArcWithCenter:NSMakePoint(right, bottom + arcRadius)
       
   275 										 radius:arcRadius
       
   276 									 startAngle:0
       
   277 									   endAngle:270
       
   278 									  clockwise:YES];
       
   279 		
       
   280 		//Bottom
       
   281 		[path moveToPoint: NSMakePoint(right, bottom)];
       
   282 		[path lineToPoint: NSMakePoint(left, bottom)];
       
   283 		
       
   284 		//Left rounded cap
       
   285 		[path appendBezierPathWithArcWithCenter:NSMakePoint(left, bottom + arcRadius)
       
   286 										 radius:arcRadius
       
   287 									 startAngle:270
       
   288 									   endAngle:180
       
   289 									  clockwise:YES];
       
   290 		[path lineToPoint: NSMakePoint(left - arcRadius, top - arcRadius)];
       
   291 		[path appendBezierPathWithArcWithCenter:NSMakePoint(left, top - arcRadius)
       
   292 										 radius:arcRadius
       
   293 									 startAngle:180
       
   294 									   endAngle:90
       
   295 									  clockwise:YES];
       
   296 		
       
   297 		if ([object integerValueForProperty:KEY_UNVIEWED_CONTENT]) { //Unviewed
       
   298 			backColor = backUnviewedContentColor;
       
   299 			textColor = unviewedContentColor;
       
   300 		} else if ([object boolValueForProperty:@"signedOn"]) { //Signed on
       
   301 			backColor = backSignedOnColor;
       
   302 			textColor = signedOnColor;
       
   303 		} else if ([object boolValueForProperty:@"signedOff"]) { //Signed off
       
   304 			backColor = backSignedOffColor;
       
   305 			textColor = signedOffColor;
       
   306 		}
       
   307 		
       
   308 		if (!backColor)
       
   309 			backColor = [NSColor whiteColor];
       
   310 		if (!textColor)
       
   311 			textColor = [NSColor blackColor];
       
   312 		
       
   313 		//Lighten/Darken the back color slightly
       
   314 		if ([backColor colorIsDark]) {
       
   315 			backColor = [backColor darkenBy:-0.15f];
       
   316 			borderColor = [backColor darkenBy:-0.3f];
       
   317 		} else {
       
   318 			backColor = [backColor darkenBy:0.15f];
       
   319 			borderColor = [backColor darkenBy:0.3f];
       
   320 		}
       
   321 		
       
   322 		//Draw
       
   323 		[backColor set];
       
   324 		[path fill];
       
   325 		[borderColor set];
       
   326 		[path stroke];
       
   327 		
       
   328 		//Get the object's display name
       
   329 		[object.displayName drawInRect:NSMakeRect(stringInset + 1, bottom - 1, 127 - (stringInset * 2), top - bottom)
       
   330 						withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, paragraphStyle, NSParagraphStyleAttributeName, textColor, NSForegroundColorAttributeName, nil]];
       
   331 		
       
   332 		//Move up to the next pill
       
   333 		bottom = top + 3.0f;
       
   334 		top = bottom + iconHeight;
       
   335 	}
       
   336 	
       
   337 	[image unlockFocus];
       
   338 	
       
   339 	[adium.dockController setOverlay:image];
       
   340 }
       
   341 
       
   342 @end