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 "ESContactAlertsController.h"
19 #import <Adium/AIPreferenceControllerProtocol.h>
20 #import <Adium/AIListObject.h>
21 #import <AIUtilities/AIMenuAdditions.h>
22 #import <AIUtilities/AIImageAdditions.h>
24 @interface ESContactAlertsController (PRIVATE)
25 - (NSMutableArray *)appendEventsForObject:(AIListObject *)listObject eventID:(NSString *)eventID toArray:(NSMutableArray *)events;
26 - (void)addMenuItemsForEventHandlers:(NSDictionary *)inEventHandlers toArray:(NSMutableArray *)menuItemArray withTarget:(id)target forGlobalMenu:(BOOL)global;
27 - (void)removeAllAlertsFromListObject:(AIListObject *)listObject;
30 @implementation ESContactAlertsController
32 static NSMutableDictionary *eventHandlersByGroup[EVENT_HANDLER_GROUP_COUNT];
33 static NSMutableDictionary *globalOnlyEventHandlersByGroup[EVENT_HANDLER_GROUP_COUNT];
36 * @brief Initialize before the class is used
40 static BOOL didInitialize = NO;
42 for (int i = 0; i < EVENT_HANDLER_GROUP_COUNT; i++) {
43 eventHandlersByGroup[i] = nil;
44 globalOnlyEventHandlersByGroup[i] = nil;
56 if ((self = [super init])) {
57 globalOnlyEventHandlers = [[NSMutableDictionary alloc] init];
58 eventHandlers = [[NSMutableDictionary alloc] init];
59 actionHandlers = [[NSMutableDictionary alloc] init];
65 - (void)controllerDidLoad
69 - (void)controllerWillClose
79 [globalOnlyEventHandlers release]; globalOnlyEventHandlers = nil;
80 [eventHandlers release]; eventHandlers = nil;
81 [actionHandlers release]; actionHandlers = nil;
87 //Events ---------------------------------------------------------------------------------------------------------------
91 * @brief Register an event
93 * An event must have a unique eventID. handler is responsible for providing information
94 * about the event, such as short and long descriptions. The group determines how the event will be displayed in the events
95 * preferences; events in the same group are displayed together.
97 * @param eventID Unique event ID
98 * @param handler The handler, which must conform to AIEventHandler
99 * @param inGroup The group
100 * @param global If YES, the event will only be displayed in the global Events preferences; if NO, the event is available for contacts and groups via Get Info, as well.
102 - (void)registerEventID:(NSString *)eventID
103 withHandler:(id <AIEventHandler>)handler
104 inGroup:(AIEventHandlerGroupType)inGroup
105 globalOnly:(BOOL)global
108 [globalOnlyEventHandlers setObject:handler forKey:eventID];
110 if (!globalOnlyEventHandlersByGroup[inGroup]) globalOnlyEventHandlersByGroup[inGroup] = [[NSMutableDictionary alloc] init];
111 [globalOnlyEventHandlersByGroup[inGroup] setObject:handler forKey:eventID];
114 [eventHandlers setObject:handler forKey:eventID];
116 if (!eventHandlersByGroup[inGroup]) eventHandlersByGroup[inGroup] = [[NSMutableDictionary alloc] init];
117 [eventHandlersByGroup[inGroup] setObject:handler forKey:eventID];
121 //Return all event IDs for groups/contacts
122 - (NSArray *)allEventIDs
124 return [[eventHandlers allKeys] arrayByAddingObjectsFromArray:[globalOnlyEventHandlers allKeys]];
127 - (NSString *)longDescriptionForEventID:(NSString *)eventID forListObject:(AIListObject *)listObject
129 id <AIEventHandler> handler;
131 handler = [eventHandlers objectForKey:eventID];
132 if (!handler) handler = [globalOnlyEventHandlers objectForKey:eventID];
134 return [handler longDescriptionForEventID:eventID forListObject:listObject];
137 //Returns a menu of all events
138 //- Selector called on event selection is selectEvent:
139 //- A menu item's represented object is the dictionary describing the event it represents
140 - (NSMenu *)menuOfEventsWithTarget:(id)target forGlobalMenu:(BOOL)global
142 NSEnumerator *enumerator;
147 menu = [[NSMenu allocWithZone:[NSMenu zone]] init];
148 [menu setAutoenablesItems:NO];
150 enumerator = [[self arrayOfMenuItemsForEventsWithTarget:target forGlobalMenu:global] objectEnumerator];
151 while ((item = [enumerator nextObject])) {
155 return [menu autorelease];
158 - (NSArray *)arrayOfMenuItemsForEventsWithTarget:(id)target forGlobalMenu:(BOOL)global
160 NSMutableArray *menuItemArray = [NSMutableArray array];
161 BOOL addedItems = NO;
164 for (i = 0; i < EVENT_HANDLER_GROUP_COUNT; i++) {
165 NSMutableArray *groupMenuItemArray;
167 //Create an array of menu items for this group
168 groupMenuItemArray = [NSMutableArray array];
170 [self addMenuItemsForEventHandlers:eventHandlersByGroup[i]
171 toArray:groupMenuItemArray
173 forGlobalMenu:global];
175 [self addMenuItemsForEventHandlers:globalOnlyEventHandlersByGroup[i]
176 toArray:groupMenuItemArray
178 forGlobalMenu:global];
181 if ([groupMenuItemArray count]) {
182 //Add a separator if we are adding a group and we have added before
184 [menuItemArray addObject:[NSMenuItem separatorItem]];
189 //Sort the array of menuItems alphabetically by title within this group
190 [groupMenuItemArray sortUsingSelector:@selector(titleCompare:)];
192 [menuItemArray addObjectsFromArray:groupMenuItemArray];
196 return menuItemArray;
199 - (void)addMenuItemsForEventHandlers:(NSDictionary *)inEventHandlers toArray:(NSMutableArray *)menuItemArray withTarget:(id)target forGlobalMenu:(BOOL)global
201 NSEnumerator *enumerator;
203 NSMenuItem *menuItem;
205 enumerator = [inEventHandlers keyEnumerator];
206 while ((eventID = [enumerator nextObject])) {
207 id <AIEventHandler> eventHandler = [inEventHandlers objectForKey:eventID];
209 menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:(global ?
210 [eventHandler globalShortDescriptionForEventID:eventID] :
211 [eventHandler shortDescriptionForEventID:eventID])
213 action:@selector(selectEvent:)
215 [menuItem setRepresentedObject:eventID];
216 [menuItemArray addObject:menuItem];
222 * @brief Sort event IDs by group and then by global short description
224 int eventIDSort(id objectA, id objectB, void *context) {
226 id <AIEventHandler> eventHandlerA;
227 id <AIEventHandler> eventHandlerB;
229 //Determine the group of the first event ID
230 for (groupA = 0; groupA < EVENT_HANDLER_GROUP_COUNT; groupA++) {
231 eventHandlerA = [eventHandlersByGroup[groupA] objectForKey:objectA];
232 if (!eventHandlerA) {
233 eventHandlerA = [globalOnlyEventHandlersByGroup[groupA] objectForKey:objectA];
236 if (eventHandlerA) break;
239 //Determine the group of the second ID
240 for (groupB = 0; groupB < EVENT_HANDLER_GROUP_COUNT; groupB++) {
241 eventHandlerB = [eventHandlersByGroup[groupB] objectForKey:objectB];
242 if (!eventHandlerB) {
243 eventHandlerB = [globalOnlyEventHandlersByGroup[groupB] objectForKey:objectB];
246 if (eventHandlerB) break;
249 if (groupA < groupB) {
250 return NSOrderedAscending;
252 } else if (groupB < groupA) {
253 return NSOrderedDescending;
256 NSString *descriptionA = [eventHandlerA globalShortDescriptionForEventID:objectA];
257 NSString *descriptionB = [eventHandlerA globalShortDescriptionForEventID:objectB];
259 return ([descriptionA caseInsensitiveCompare:descriptionB]);
264 * @brief Sort an array of event IDs
266 * @brief inArray The array of eventIDs to sort
267 * @return The array sorted by eventIDSort()
269 - (NSArray *)sortedArrayOfEventIDsFromArray:(NSArray *)inArray
271 return [inArray sortedArrayUsingFunction:eventIDSort context:NULL];
275 * @brief Return the image associated with an event
277 - (NSImage *)imageForEventID:(NSString *)eventID
279 id <AIEventHandler> eventHandler;
281 eventHandler = [eventHandlers objectForKey:eventID];
282 if (!eventHandler) eventHandler = [globalOnlyEventHandlers objectForKey:eventID];
284 return [eventHandler imageForEventID:eventID];
288 * @brief Generate an event, returning a set of the actionIDs which were performed.
290 * @param eventID The event which occurred
291 * @param listObject The object for which the event occurred
292 * @param userInfo Event-specific user info
293 * @param previouslyPerformedActionIDs If non-nil, a set of actionIDs which should be treated as if they had already been performed in this invocation.
295 * @result The set of actions which were performed, suitable for being passed back in for another event generation via previouslyPerformedActionIDs
297 - (NSSet *)generateEvent:(NSString *)eventID forListObject:(AIListObject *)listObject userInfo:(id)userInfo previouslyPerformedActionIDs:(NSSet *)previouslyPerformedActionIDs
299 NSArray *alerts = [self appendEventsForObject:listObject eventID:eventID toArray:nil];
300 NSMutableSet *performedActionIDs = nil;
302 if (alerts && [alerts count]) {
303 NSEnumerator *enumerator;
306 performedActionIDs = (previouslyPerformedActionIDs ?
307 [[previouslyPerformedActionIDs mutableCopy] autorelease]:
310 //We go from contact->group->root; a given action will only fire once for this event
311 enumerator = [alerts objectEnumerator];
313 //Process each alert (There may be more than one for an event)
314 while ((alert = [enumerator nextObject])) {
316 id <AIActionHandler> actionHandler;
318 actionID = [alert objectForKey:KEY_ACTION_ID];
319 actionHandler = [actionHandlers objectForKey:actionID];
321 if ((![performedActionIDs containsObject:actionID]) || ([actionHandler allowMultipleActionsWithID:actionID])) {
322 if ([actionHandler performActionID:actionID
323 forListObject:listObject
324 withDetails:[alert objectForKey:KEY_ACTION_DETAILS]
325 triggeringEventID:eventID
326 userInfo:userInfo]) {
328 //If this alert was a single-fire alert, we can delete it now
329 if ([[alert objectForKey:KEY_ONE_TIME_ALERT] intValue]) {
330 [self removeAlert:alert fromListObject:listObject];
333 //We don't want to perform this action again for this event
334 [performedActionIDs addObject:actionID];
340 [[adium notificationCenter] postNotificationName:eventID
344 /* If we generated a new perfromedActionIDs, return it. If we didn't, return the original
345 * previouslyPerformedActionIDs, which may also be nil or may be actionIDs performed on some previous invocation.
347 return (performedActionIDs ? performedActionIDs : previouslyPerformedActionIDs);
351 * @brief Append events for the passed object to the specified array.
353 * @param events The array of events so far. Create the array if passed nil.
354 * @param The object for which we'ere retrieving events. If nil, we retrieve the global preferences.
356 * This method is intended to be called recursively; it should generate an array which has alerts from:
357 * contact->metaContact->group->global preferences (skipping any which don't exist).
359 * @result An array which contains the object's own events followed by its containingObject's events.
361 - (NSMutableArray *)appendEventsForObject:(AIListObject *)listObject eventID:(NSString *)eventID toArray:(NSMutableArray *)events
365 //Add events for this object (replacing any inherited from the containing object so that this object takes precendence)
366 newEvents = [[[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS
367 group:PREF_GROUP_CONTACT_ALERTS
368 objectIgnoringInheritance:listObject] objectForKey:eventID];
370 if (newEvents && [newEvents count]) {
371 if (!events) events = [NSMutableArray array];
372 [events addObjectsFromArray:newEvents];
375 //Get all events from the contanining object if we have an object
377 //If listObject doesn't have a containingObject, this will pass nil
378 events = [self appendEventsForObject:[listObject containingObject]
387 * @brief Return the default event ID for a new alert
389 - (NSString *)defaultEventID
391 NSString *defaultEventID = [[adium preferenceController] preferenceForKey:KEY_DEFAULT_EVENT_ID
392 group:PREF_GROUP_CONTACT_ALERTS];
393 if (![eventHandlers objectForKey:defaultEventID]) {
394 defaultEventID = [[eventHandlers allKeys] objectAtIndex:0];
397 return defaultEventID;
401 * @brief Find the eventID associated with an English name
403 * This exists for compatibility with old AdiumXtras...
405 - (NSString *)eventIDForEnglishDisplayName:(NSString *)displayName
407 NSEnumerator *enumerator;
410 enumerator = [eventHandlers keyEnumerator];
411 while ((eventID = [enumerator nextObject])) {
412 id <AIEventHandler> eventHandler = [eventHandlers objectForKey:eventID];
413 if ([[eventHandler englishGlobalShortDescriptionForEventID:eventID] isEqualToString:displayName]) {
418 enumerator = [globalOnlyEventHandlers keyEnumerator];
419 while ((eventID = [enumerator nextObject])) {
420 id <AIEventHandler> eventHandler = [globalOnlyEventHandlers objectForKey:eventID];
421 if ([[eventHandler englishGlobalShortDescriptionForEventID:eventID] isEqualToString:displayName]) {
430 * @brief Return a short description to describe eventID when considered globally
432 - (NSString *)globalShortDescriptionForEventID:(NSString *)eventID
434 id <AIEventHandler> eventHandler;
436 eventHandler = [eventHandlers objectForKey:eventID];
437 if (!eventHandler) eventHandler = [globalOnlyEventHandlers objectForKey:eventID];
440 return [eventHandler globalShortDescriptionForEventID:eventID];
447 * @brief Return a natural language, localized description for an event
449 * This will be suitable for display to the user such as in a message window or a Growl notification
451 * @param eventID The event
452 * @param listObject The object for which the event occurred
453 * @param userInfo Event-specific userInfo
454 * @param includeSubject If YES, the return value is a complete sentence. If NO, the return value is suitable for display after a name or other identifier.
455 * @result The natural language description
457 - (NSString *)naturalLanguageDescriptionForEventID:(NSString *)eventID
458 listObject:(AIListObject *)listObject
459 userInfo:(id)userInfo
460 includeSubject:(BOOL)includeSubject
462 id <AIEventHandler> eventHandler;
464 eventHandler = [eventHandlers objectForKey:eventID];
465 if (!eventHandler) eventHandler = [globalOnlyEventHandlers objectForKey:eventID];
468 return [eventHandler naturalLanguageDescriptionForEventID:eventID
469 listObject:listObject
471 includeSubject:includeSubject];
478 //Actions --------------------------------------------------------------------------------------------------------------
481 * @brief Register an actionID and its handler
483 * When an event occurs -- that is, when the event is generated via
484 * -[ESContactAlertsController generateEvent:forListObject:userInfo:] -- the handler for each action
485 * associated with that event within the appropriate list object's heirarchy (object -> containing group -> global)
486 * will be called as per the AIActionHandler protocol.
488 * @param actionID The actionID
489 * @param handler The handler, which must conform to the AIActionHandler protocol
491 - (void)registerActionID:(NSString *)actionID withHandler:(id <AIActionHandler>)handler
493 [actionHandlers setObject:handler forKey:actionID];
497 * @brief Return all available actions
499 - (NSDictionary *)actionHandlers
501 return actionHandlers;
505 * @brief Returns a menu of all actions
507 * A menu item's represented object is the dictionary describing the action it represents
509 * @param target The target on which @selector(selectAction:) will be called on selection
510 * @result The NSMenu, which does not send validateMenuItem: messages
512 - (NSMenu *)menuOfActionsWithTarget:(id)target
514 NSEnumerator *enumerator;
516 NSMenuItem *menuItem;
518 NSMutableArray *menuItemArray;
521 menu = [[NSMenu alloc] init];
522 [menu setAutoenablesItems:NO];
524 menuItemArray = [[NSMutableArray alloc] init];
526 //Insert a menu item for each available action
527 enumerator = [actionHandlers keyEnumerator];
528 while ((actionID = [enumerator nextObject])) {
529 id <AIActionHandler> actionHandler = [actionHandlers objectForKey:actionID];
531 menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[actionHandler shortDescriptionForActionID:actionID]
533 action:@selector(selectAction:)
535 [menuItem setRepresentedObject:actionID];
536 [menuItem setImage:[[actionHandler imageForActionID:actionID] imageByScalingForMenuItem]];
538 [menuItemArray addObject:menuItem];
542 //Sort the array of menuItems alphabetically by title
543 [menuItemArray sortUsingSelector:@selector(titleCompare:)];
545 enumerator = [menuItemArray objectEnumerator];
546 while ((menuItem = [enumerator nextObject])) {
547 [menu addItem:menuItem];
550 [menuItemArray release];
552 return [menu autorelease];
556 * @brief Return the default action ID for a new alert
558 - (NSString *)defaultActionID
560 NSString *defaultActionID = [[adium preferenceController] preferenceForKey:KEY_DEFAULT_ACTION_ID
561 group:PREF_GROUP_CONTACT_ALERTS];
562 if (![actionHandlers objectForKey:defaultActionID]) {
563 defaultActionID = [[actionHandlers allKeys] objectAtIndex:0];
566 return defaultActionID;
569 //Alerts ---------------------------------------------------------------------------------------------------------------
572 * @brief Returns an array of all the alerts of a given list object
574 * @param listObject The object
576 - (NSArray *)alertsForListObject:(AIListObject *)listObject
578 return [self alertsForListObject:listObject withEventID:nil actionID:nil];
582 * @brief Return an array of all alerts for a list object
584 * @param listObject The object, or nil for global
585 * @param eventID If specified, only return events matching eventID. If nil, don't filter based on events.
586 * @param actionID If specified, only return actions matching actionID. If nil, don't filter based on actionID.
588 - (NSArray *)alertsForListObject:(AIListObject *)listObject withEventID:(NSString *)eventID actionID:(NSString *)actionID
590 NSDictionary *contactAlerts = [[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS
591 group:PREF_GROUP_CONTACT_ALERTS
592 objectIgnoringInheritance:listObject];
593 NSMutableArray *alertArray = [NSMutableArray array];
596 /* If we have an eventID, just look at the alerts for this eventID */
597 NSEnumerator *alertEnumerator;
600 alertEnumerator = [[contactAlerts objectForKey:eventID] objectEnumerator];
602 while ((alert = [alertEnumerator nextObject])) {
603 //If we don't have a specific actionID, or this one is right, add it
604 if (!actionID || [actionID isEqualToString:[alert objectForKey:KEY_ACTION_ID]]) {
605 [alertArray addObject:alert];
610 /* If we don't have an eventID, look at all alerts */
611 NSEnumerator *groupEnumerator;
614 //Flatten the alert dict into an array
615 groupEnumerator = [contactAlerts keyEnumerator];
616 while ((anEventID = [groupEnumerator nextObject])) {
617 NSEnumerator *alertEnumerator;
620 alertEnumerator = [[contactAlerts objectForKey:anEventID] objectEnumerator];
621 while ((alert = [alertEnumerator nextObject])) {
622 //If we don't have a specific actionID, or this one is right, add it
623 if (!actionID || [actionID isEqualToString:[alert objectForKey:KEY_ACTION_ID]]) {
624 [alertArray addObject:alert];
634 * @brief Add an alert (passed as a dictionary) to a list object
636 * @param newAlert The alert to add
637 * @param listObject The object to which to add, or nil for global
638 * @param setAsNewDefaults YES to make the type and details of newAlert be the new default for new alerts
640 - (void)addAlert:(NSDictionary *)newAlert toListObject:(AIListObject *)listObject setAsNewDefaults:(BOOL)setAsNewDefaults
642 NSString *newAlertEventID = [newAlert objectForKey:KEY_EVENT_ID];
643 NSMutableDictionary *contactAlerts;
644 NSMutableArray *eventArray;
646 [[adium preferenceController] delayPreferenceChangedNotifications:YES];
648 //Get the alerts for this list object
649 contactAlerts = [[[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS
650 group:PREF_GROUP_CONTACT_ALERTS
651 objectIgnoringInheritance:listObject] mutableCopy];
652 if (!contactAlerts) contactAlerts = [[NSMutableDictionary alloc] init];
654 //Get the event array for the new alert, making a copy so we can modify it
655 eventArray = [[contactAlerts objectForKey:newAlertEventID] mutableCopy];
656 if (!eventArray) eventArray = [[NSMutableArray alloc] init];
658 //Avoid putting the exact same alert into the array twice
659 if ([eventArray indexOfObject:newAlert] == NSNotFound) {
661 [eventArray addObject:newAlert];
663 //Put the modified event array back into the contact alert dict, and save our changes
664 [contactAlerts setObject:eventArray forKey:newAlertEventID];
665 [[adium preferenceController] setPreference:contactAlerts
666 forKey:KEY_CONTACT_ALERTS
667 group:PREF_GROUP_CONTACT_ALERTS
671 //Update the default events if requested
672 if (setAsNewDefaults) {
673 [[adium preferenceController] setPreference:newAlertEventID
674 forKey:KEY_DEFAULT_EVENT_ID
675 group:PREF_GROUP_CONTACT_ALERTS];
676 [[adium preferenceController] setPreference:[newAlert objectForKey:KEY_ACTION_ID]
677 forKey:KEY_DEFAULT_ACTION_ID
678 group:PREF_GROUP_CONTACT_ALERTS];
682 [contactAlerts release];
683 [eventArray release];
685 [[adium preferenceController] delayPreferenceChangedNotifications:NO];
689 * @brief Add an alert at the global level
691 - (void)addGlobalAlert:(NSDictionary *)newAlert
693 [self addAlert:newAlert toListObject:nil setAsNewDefaults:NO];
697 * @brief Remove an alert from a listObject
699 * @param victimAlert The alert to remove; it will be tested against existing alerts using isEqual: so must be identical
700 * @param listObject The object (or nil, for global) from which to remove victimAlert
702 - (void)removeAlert:(NSDictionary *)victimAlert fromListObject:(AIListObject *)listObject
704 NSMutableDictionary *contactAlerts = [[[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS
705 group:PREF_GROUP_CONTACT_ALERTS
706 objectIgnoringInheritance:listObject] mutableCopy];
707 NSString *victimEventID = [victimAlert objectForKey:KEY_EVENT_ID];
708 NSMutableArray *eventArray;
710 //Get the event array containing the victim alert, making a copy so we can modify it
711 eventArray = [[contactAlerts objectForKey:victimEventID] mutableCopy];
714 [eventArray removeObject:victimAlert];
716 //Put the modified event array back into the contact alert dict, and save our changes
717 if ([eventArray count]) {
718 [contactAlerts setObject:eventArray forKey:victimEventID];
720 [contactAlerts removeObjectForKey:victimEventID];
723 [[adium preferenceController] setPreference:contactAlerts
724 forKey:KEY_CONTACT_ALERTS
725 group:PREF_GROUP_CONTACT_ALERTS
727 [eventArray release];
728 [contactAlerts release];
732 * @brief Remove all alerts which are specifically applied to listObject
734 * This does not affect alerts set at higher (containing object, root) levels
736 - (void)removeAllAlertsFromListObject:(AIListObject *)listObject
738 [listObject setPreference:nil
739 forKey:KEY_CONTACT_ALERTS
740 group:PREF_GROUP_CONTACT_ALERTS];
744 * @brief Remove all global (root-level) alerts with a given action ID
746 - (void)removeAllGlobalAlertsWithActionID:(NSString *)actionID
748 NSDictionary *contactAlerts = [[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS
749 group:PREF_GROUP_CONTACT_ALERTS];
750 NSMutableDictionary *newContactAlerts = [contactAlerts mutableCopy];
751 NSEnumerator *enumerator = [contactAlerts keyEnumerator];
752 NSString *victimEventID;
753 NSEnumerator *alertArrayEnumerator;
755 NSDictionary *alertDict;
757 //The contact alerts preference is a dictionary keyed by event. Each event key yields an array of dictionaries;
758 //each of these dictionaries represents an alert. We want to remove all dictionaries which represent alerts with
759 //the passed actionID
760 while ((victimEventID = [enumerator nextObject])) {
761 NSMutableArray *newEventArray = nil;
763 eventArray = [contactAlerts objectForKey:victimEventID];
765 //Enumerate each alert for this event
766 alertArrayEnumerator = [eventArray objectEnumerator];
767 while ((alertDict = [alertArrayEnumerator nextObject])) {
769 //We found an alertDict which needs to be removed
770 if ([[alertDict objectForKey:KEY_ACTION_ID] isEqualToString:actionID]) {
771 //If this is the first modification to the current eventArray, make a mutableCopy with which to work
772 if (!newEventArray) newEventArray = [eventArray mutableCopy];
773 [newEventArray removeObject:alertDict];
777 //newEventArray will only be non-nil if we made changes; now that we have enumerated this eventArray, save them
779 if ([newEventArray count]) {
780 [newContactAlerts setObject:newEventArray forKey:victimEventID];
782 [newContactAlerts removeObjectForKey:victimEventID];
786 [newEventArray release];
790 [[adium preferenceController] setPreference:newContactAlerts
791 forKey:KEY_CONTACT_ALERTS
792 group:PREF_GROUP_CONTACT_ALERTS];
793 [newContactAlerts release];
797 * @brief Remove all current global alerts and replace them with the alerts in allGlobalAlerts
799 * Used for setting a preset of events
801 - (void)setAllGlobalAlerts:(NSArray *)allGlobalAlerts
803 NSMutableDictionary *contactAlerts = [[NSMutableDictionary alloc] init];;
804 NSDictionary *eventDict;
805 NSEnumerator *enumerator;
807 [[adium preferenceController] delayPreferenceChangedNotifications:YES];
809 enumerator = [allGlobalAlerts objectEnumerator];
810 while ((eventDict = [enumerator nextObject])) {
811 NSMutableArray *eventArray;
812 NSString *eventID = [eventDict objectForKey:KEY_EVENT_ID];
814 /* Get the event array for this alert. Since we are creating the entire dictionary, we can be sure we are working
815 * with an NSMutableArray.
817 eventArray = [contactAlerts objectForKey:eventID];
818 if (!eventArray) eventArray = [NSMutableArray array];
821 [eventArray addObject:eventDict];
823 //Put the modified event array back into the contact alert dict
824 [contactAlerts setObject:eventArray forKey:eventID];
827 [[adium preferenceController] setPreference:contactAlerts
828 forKey:KEY_CONTACT_ALERTS
829 group:PREF_GROUP_CONTACT_ALERTS
831 [contactAlerts release];
833 [[adium preferenceController] delayPreferenceChangedNotifications:NO];
838 * @brief Move all contact alerts from oldObject to newObject
840 * This is useful when adding oldObject to the metaContact newObject so that any existing contact alerts for oldObject
841 * are applied at the contact-general level, displayed and handled properly for the new, combined contact.
843 * @param oldObject The object from which to move contact alerts
844 * @param newObject The object to which to we want to add the moved contact alerts
846 - (void)mergeAndMoveContactAlertsFromListObject:(AIListObject *)oldObject intoListObject:(AIListObject *)newObject
848 NSArray *oldAlerts = [self alertsForListObject:oldObject];
849 NSEnumerator *enumerator = [oldAlerts objectEnumerator];
850 NSDictionary *alertDict;
852 [[adium preferenceController] delayPreferenceChangedNotifications:YES];
854 //Add each alert to the target (addAlert:toListObject:setAsNewDefaults: will ensure identical alerts aren't added more than once)
855 while ((alertDict = [enumerator nextObject])) {
856 [self addAlert:alertDict toListObject:newObject setAsNewDefaults:NO];
859 //Remove the alerts from the originating list object
860 [self removeAllAlertsFromListObject:oldObject];
862 [[adium preferenceController] delayPreferenceChangedNotifications:NO];
867 * @brief Is the passed event a message event?
869 * Examples of messages events are "message sent" and "message received."
871 * @result YES if it is a message event
873 - (BOOL)isMessageEvent:(NSString *)eventID
875 return ([eventHandlersByGroup[AIMessageEventHandlerGroup] objectForKey:eventID] != nil ||
876 ([globalOnlyEventHandlersByGroup[AIMessageEventHandlerGroup] objectForKey:eventID] != nil));