merged [21403]: Fixed an exception thrown when clearing all complete file transfers...
[adiumx.git] / Source / ESContactAlertsController.m
blobea65cd1a54e9974ba03cf468e8de806346b1fc51
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 "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;
28 @end
30 @implementation ESContactAlertsController
32 static  NSMutableDictionary             *eventHandlersByGroup[EVENT_HANDLER_GROUP_COUNT];
33 static  NSMutableDictionary             *globalOnlyEventHandlersByGroup[EVENT_HANDLER_GROUP_COUNT];
35 /*!
36  * @brief Initialize before the class is used
37  */
38 + (void)initialize
40         static BOOL didInitialize = NO;
41         if (!didInitialize) {
42                 for (int i = 0; i < EVENT_HANDLER_GROUP_COUNT; i++) {
43                         eventHandlersByGroup[i] = nil;
44                         globalOnlyEventHandlersByGroup[i] = nil;
45                 }
47                 didInitialize = YES;
48         }
51 /*!
52  * @brief Init
53  */
54 - (id)init
56         if ((self = [super init])) {
57                 globalOnlyEventHandlers = [[NSMutableDictionary alloc] init];
58                 eventHandlers = [[NSMutableDictionary alloc] init];
59                 actionHandlers = [[NSMutableDictionary alloc] init];
60         }
61         
62         return self;
65 - (void)controllerDidLoad
69 - (void)controllerWillClose
71         
74 /*!
75  * @brief Deallocate
76  */
77 - (void)dealloc
79         [globalOnlyEventHandlers release]; globalOnlyEventHandlers = nil;
80         [eventHandlers release]; eventHandlers = nil;
81         [actionHandlers release]; actionHandlers = nil;
82         
83         [super dealloc];
87 //Events ---------------------------------------------------------------------------------------------------------------
88 #pragma mark Events
90 /*!
91  * @brief Register an event
92  *
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.
96  *
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.
101  */
102 - (void)registerEventID:(NSString *)eventID
103                         withHandler:(id <AIEventHandler>)handler
104                                 inGroup:(AIEventHandlerGroupType)inGroup
105                          globalOnly:(BOOL)global
107         if (global) {
108                 [globalOnlyEventHandlers setObject:handler forKey:eventID];
109                 
110                 if (!globalOnlyEventHandlersByGroup[inGroup]) globalOnlyEventHandlersByGroup[inGroup] = [[NSMutableDictionary alloc] init];
111                 [globalOnlyEventHandlersByGroup[inGroup] setObject:handler forKey:eventID];
112                 
113         } else {
114                 [eventHandlers setObject:handler forKey:eventID];
115                 
116                 if (!eventHandlersByGroup[inGroup]) eventHandlersByGroup[inGroup] = [[NSMutableDictionary alloc] init];
117                 [eventHandlersByGroup[inGroup] setObject:handler forKey:eventID];
118         }
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;
130         
131         handler = [eventHandlers objectForKey:eventID];
132         if (!handler) handler = [globalOnlyEventHandlers objectForKey:eventID];
133         
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;
143         NSMenuItem                      *item;
144         NSMenu                          *menu;
146         //Prepare our menu
147         menu = [[NSMenu allocWithZone:[NSMenu zone]] init];
148         [menu setAutoenablesItems:NO];
149         
150         enumerator = [[self arrayOfMenuItemsForEventsWithTarget:target forGlobalMenu:global] objectEnumerator];
151         while ((item = [enumerator nextObject])) {
152                 [menu addItem:item];
153         }
154         
155         return [menu autorelease];
158 - (NSArray *)arrayOfMenuItemsForEventsWithTarget:(id)target forGlobalMenu:(BOOL)global
160         NSMutableArray          *menuItemArray = [NSMutableArray array];
161         BOOL                            addedItems = NO;
162         int                                     i;
163         
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];
169                 
170                 [self addMenuItemsForEventHandlers:eventHandlersByGroup[i]
171                                                                    toArray:groupMenuItemArray
172                                                                 withTarget:target
173                                                          forGlobalMenu:global];
174                 if (global) {
175                         [self addMenuItemsForEventHandlers:globalOnlyEventHandlersByGroup[i]
176                                                                            toArray:groupMenuItemArray
177                                                                         withTarget:target
178                                                                  forGlobalMenu:global];
179                 }
180                 
181                 if ([groupMenuItemArray count]) {
182                         //Add a separator if we are adding a group and we have added before
183                         if (addedItems) {
184                                 [menuItemArray addObject:[NSMenuItem separatorItem]];
185                         } else {
186                                 addedItems = YES;
187                         }
188                         
189                         //Sort the array of menuItems alphabetically by title within this group
190                         [groupMenuItemArray sortUsingSelector:@selector(titleCompare:)];
192                         [menuItemArray addObjectsFromArray:groupMenuItemArray];
193                 }
194         }
195         
196         return menuItemArray;
197 }       
199 - (void)addMenuItemsForEventHandlers:(NSDictionary *)inEventHandlers toArray:(NSMutableArray *)menuItemArray withTarget:(id)target forGlobalMenu:(BOOL)global
200 {       
201         NSEnumerator            *enumerator;
202         NSString                        *eventID;
203         NSMenuItem                      *menuItem;
204         
205         enumerator = [inEventHandlers keyEnumerator];
206         while ((eventID = [enumerator nextObject])) {
207                 id <AIEventHandler>     eventHandler = [inEventHandlers objectForKey:eventID];          
208                 
209         menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:(global ?
210                                                                                                                                                                 [eventHandler globalShortDescriptionForEventID:eventID] :
211                                                                                                                                                                 [eventHandler shortDescriptionForEventID:eventID])
212                                                                                                                                                 target:target 
213                                                                                                                                                 action:@selector(selectEvent:) 
214                                                                                                                                  keyEquivalent:@""];
215         [menuItem setRepresentedObject:eventID];
216                 [menuItemArray addObject:menuItem];
217                 [menuItem release];
218     }
222  * @brief Sort event IDs by group and then by global short description
223  */
224 int eventIDSort(id objectA, id objectB, void *context) {
225         int                                     groupA, groupB;
226         id <AIEventHandler> eventHandlerA;
227         id <AIEventHandler> eventHandlerB;
228         
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];
234                 }
235                 
236                 if (eventHandlerA) break;
237         }
238         
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];
244                 }
245                 
246                 if (eventHandlerB) break;
247         }
248         
249         if (groupA < groupB) {
250                 return NSOrderedAscending;
251                 
252         } else if (groupB < groupA) {
253                 return NSOrderedDescending;
254                 
255         } else {
256                 NSString        *descriptionA = [eventHandlerA globalShortDescriptionForEventID:objectA];
257                 NSString        *descriptionB = [eventHandlerA globalShortDescriptionForEventID:objectB];
258                 
259                 return ([descriptionA caseInsensitiveCompare:descriptionB]);
260         }
264  * @brief Sort an array of event IDs
266  * @brief inArray The array of eventIDs to sort
267  * @return The array sorted by eventIDSort()
268  */
269 - (NSArray *)sortedArrayOfEventIDsFromArray:(NSArray *)inArray
271         return [inArray sortedArrayUsingFunction:eventIDSort context:NULL];
275  * @brief Return the image associated with an event
276  */
277 - (NSImage *)imageForEventID:(NSString *)eventID
279         id <AIEventHandler>     eventHandler;
280         
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
296  */
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;
301         
302         if (alerts && [alerts count]) {
303                 NSEnumerator            *enumerator;
304                 NSDictionary            *alert;
306                 performedActionIDs = (previouslyPerformedActionIDs ?
307                                                           [[previouslyPerformedActionIDs mutableCopy] autorelease]:
308                                                           [NSMutableSet set]);
309                 
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])) {
315                         NSString        *actionID;
316                         id <AIActionHandler>    actionHandler;                  
317                         
318                         actionID = [alert objectForKey:KEY_ACTION_ID];
319                         actionHandler = [actionHandlers objectForKey:actionID];         
320                         
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]) {
327                                         
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];
331                                         }
332                                         
333                                         //We don't want to perform this action again for this event
334                                         [performedActionIDs addObject:actionID];
335                                 }
336                         }
337                 }
338         }
339         
340         [[adium notificationCenter] postNotificationName:eventID
341                                                                                           object:listObject 
342                                                                                         userInfo:userInfo];
343         
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.
346          */
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.
360  */
361 - (NSMutableArray *)appendEventsForObject:(AIListObject *)listObject eventID:(NSString *)eventID toArray:(NSMutableArray *)events
363         NSArray                 *newEvents;
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];
369         
370         if (newEvents && [newEvents count]) {
371                 if (!events) events = [NSMutableArray array];
372                 [events addObjectsFromArray:newEvents];
373         }
375         //Get all events from the contanining object if we have an object
376         if (listObject) {
377                 //If listObject doesn't have a containingObject, this will pass nil
378                 events = [self appendEventsForObject:[listObject containingObject]
379                                                                          eventID:eventID
380                                                                          toArray:events];
381         }
383         return events;
387  * @brief Return the default event ID for a new alert
388  */
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];
395         }
396         
397         return defaultEventID;
401  * @brief Find the eventID associated with an English name
403  * This exists for compatibility with old AdiumXtras...
404  */
405 - (NSString *)eventIDForEnglishDisplayName:(NSString *)displayName
407         NSEnumerator            *enumerator;
408         NSString                        *eventID;
409         
410         enumerator = [eventHandlers keyEnumerator];
411         while ((eventID = [enumerator nextObject])) {
412                 id <AIEventHandler>     eventHandler = [eventHandlers objectForKey:eventID];            
413                 if ([[eventHandler englishGlobalShortDescriptionForEventID:eventID] isEqualToString:displayName]) {
414                         return eventID;
415                 }
416         }
418         enumerator = [globalOnlyEventHandlers keyEnumerator];
419         while ((eventID = [enumerator nextObject])) {
420                 id <AIEventHandler>     eventHandler = [globalOnlyEventHandlers objectForKey:eventID];          
421                 if ([[eventHandler englishGlobalShortDescriptionForEventID:eventID] isEqualToString:displayName]) {
422                         return eventID;
423                 }
424         }
425         
426         return nil;
430  * @brief Return a short description to describe eventID when considered globally
431  */
432 - (NSString *)globalShortDescriptionForEventID:(NSString *)eventID
434         id <AIEventHandler>     eventHandler;
435         
436         eventHandler = [eventHandlers objectForKey:eventID];
437         if (!eventHandler) eventHandler = [globalOnlyEventHandlers objectForKey:eventID];
438         
439         if (eventHandler) {
440                 return [eventHandler globalShortDescriptionForEventID:eventID];
441         }
442         
443         return @"";
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
456  */
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];
467         if (eventHandler) {
468                 return [eventHandler naturalLanguageDescriptionForEventID:eventID
469                                                                                                            listObject:listObject
470                                                                                                                  userInfo:userInfo
471                                                                                                    includeSubject:includeSubject];
472         }
474         return @"";
478 //Actions --------------------------------------------------------------------------------------------------------------
479 #pragma mark 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
490  */
491 - (void)registerActionID:(NSString *)actionID withHandler:(id <AIActionHandler>)handler
493         [actionHandlers setObject:handler forKey:actionID];
497  * @brief Return all available actions
498  */
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
511  */
512 - (NSMenu *)menuOfActionsWithTarget:(id)target
514     NSEnumerator        *enumerator;
515     NSString            *actionID;
516         NSMenuItem              *menuItem;
517         NSMenu                  *menu;
518         NSMutableArray  *menuItemArray;
519         
520         //Prepare our menu
521         menu = [[NSMenu alloc] init];
522         [menu setAutoenablesItems:NO];
523         
524         menuItemArray = [[NSMutableArray alloc] init];
525         
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];            
530                 
531         menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[actionHandler shortDescriptionForActionID:actionID]
532                                                                                                                                                 target:target 
533                                                                                                                                                 action:@selector(selectAction:) 
534                                                                                                                                  keyEquivalent:@""];
535         [menuItem setRepresentedObject:actionID];
536                 [menuItem setImage:[[actionHandler imageForActionID:actionID] imageByScalingForMenuItem]];
537                 
538         [menuItemArray addObject:menuItem];
539                 [menuItem release];
540     }
542         //Sort the array of menuItems alphabetically by title   
543         [menuItemArray sortUsingSelector:@selector(titleCompare:)];
544         
545         enumerator = [menuItemArray objectEnumerator];
546         while ((menuItem = [enumerator nextObject])) {
547                 [menu addItem:menuItem];
548         }
549         
550         [menuItemArray release];
552         return [menu autorelease];
553 }       
556  * @brief Return the default action ID for a new alert
557  */
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];
564         }
565         
566         return defaultActionID;
569 //Alerts ---------------------------------------------------------------------------------------------------------------
570 #pragma mark Alerts
572  * @brief Returns an array of all the alerts of a given list object
574  * @param listObject The object
575  */
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.
587  */
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];
595         if (eventID) {
596                 /* If we have an eventID, just look at the alerts for this eventID */
597                 NSEnumerator    *alertEnumerator;
598                 NSDictionary    *alert;
599                 
600                 alertEnumerator = [[contactAlerts objectForKey:eventID] objectEnumerator];
601                 
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];
606                         }
607                 }
608                 
609         } else {
610                 /* If we don't have an eventID, look at all alerts */
611                 NSEnumerator    *groupEnumerator;
612                 NSString                *anEventID;
613                 
614                 //Flatten the alert dict into an array
615                 groupEnumerator = [contactAlerts keyEnumerator];
616                 while ((anEventID = [groupEnumerator nextObject])) {
617                         NSEnumerator    *alertEnumerator;
618                         NSDictionary    *alert;
619                         
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];
625                                 }
626                         }
627                 }       
628         }
629         
630         return alertArray;      
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
639  */
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;
645         
646         [[adium preferenceController] delayPreferenceChangedNotifications:YES];
647         
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];
653         
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];
657         
658         //Avoid putting the exact same alert into the array twice
659         if ([eventArray indexOfObject:newAlert] == NSNotFound) {
660                 //Add the new alert
661                 [eventArray addObject:newAlert];
662                 
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
668                                                                                          object:listObject];    
669         }
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];     
679         }
680         
681         //Cleanup
682         [contactAlerts release];
683         [eventArray release];
684         
685         [[adium preferenceController] delayPreferenceChangedNotifications:NO];
689  * @brief Add an alert at the global level
690  */
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
701  */
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;
709         
710         //Get the event array containing the victim alert, making a copy so we can modify it
711         eventArray = [[contactAlerts objectForKey:victimEventID] mutableCopy];
712         
713         //Remove the victim
714         [eventArray removeObject:victimAlert];
715         
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];
719         } else {
720                 [contactAlerts removeObjectForKey:victimEventID];       
721         }
722         
723         [[adium preferenceController] setPreference:contactAlerts
724                                                                                  forKey:KEY_CONTACT_ALERTS
725                                                                                   group:PREF_GROUP_CONTACT_ALERTS
726                                                                                  object:listObject];
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 
735  */
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
745  */
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;
754         NSArray                         *eventArray;
755         NSDictionary            *alertDict;
756         
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;
762         
763                 eventArray = [contactAlerts objectForKey:victimEventID];
765                 //Enumerate each alert for this event
766                 alertArrayEnumerator = [eventArray objectEnumerator];
767                 while ((alertDict = [alertArrayEnumerator nextObject])) {
768                         
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];
774                         }
775                 }
776                 
777                 //newEventArray will only be non-nil if we made changes; now that we have enumerated this eventArray, save them
778                 if (newEventArray) {
779                         if ([newEventArray count]) {
780                                 [newContactAlerts setObject:newEventArray forKey:victimEventID];
781                         } else {
782                                 [newContactAlerts removeObjectForKey:victimEventID];    
783                         }
784                         
785                         //Clean up
786                         [newEventArray release];
787                 }
788         }
789         
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
800  */
801 - (void)setAllGlobalAlerts:(NSArray *)allGlobalAlerts
803         NSMutableDictionary     *contactAlerts = [[NSMutableDictionary alloc] init];;
804         NSDictionary            *eventDict;
805         NSEnumerator            *enumerator;
806         
807         [[adium preferenceController] delayPreferenceChangedNotifications:YES];
808         
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.
816                  */
817                 eventArray = [contactAlerts objectForKey:eventID];
818                 if (!eventArray) eventArray = [NSMutableArray array];           
820                 //Add the new alert
821                 [eventArray addObject:eventDict];
822                 
823                 //Put the modified event array back into the contact alert dict
824                 [contactAlerts setObject:eventArray forKey:eventID];            
825         }
826         
827         [[adium preferenceController] setPreference:contactAlerts
828                                                                                  forKey:KEY_CONTACT_ALERTS
829                                                                                   group:PREF_GROUP_CONTACT_ALERTS
830                                                                                  object:nil];
831         [contactAlerts release];
833         [[adium preferenceController] delayPreferenceChangedNotifications:NO];
834         
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
845  */
846 - (void)mergeAndMoveContactAlertsFromListObject:(AIListObject *)oldObject intoListObject:(AIListObject *)newObject
848         NSArray                         *oldAlerts = [self alertsForListObject:oldObject];
849         NSEnumerator            *enumerator = [oldAlerts objectEnumerator];
850         NSDictionary            *alertDict;
851         
852         [[adium preferenceController] delayPreferenceChangedNotifications:YES];
853         
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];
857         }
858         
859         //Remove the alerts from the originating list object
860         [self removeAllAlertsFromListObject:oldObject];
861         
862         [[adium preferenceController] delayPreferenceChangedNotifications:NO];
865 #pragma mark -
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
872  */
873 - (BOOL)isMessageEvent:(NSString *)eventID
875         return ([eventHandlersByGroup[AIMessageEventHandlerGroup] objectForKey:eventID] != nil ||
876                    ([globalOnlyEventHandlersByGroup[AIMessageEventHandlerGroup] objectForKey:eventID] != nil));
879 @end