Prevent dragging groups into groups. Fixes #8706
[adiumx.git] / Source / ESContactAlertsController.m
blob4ec526d2f4f1040ff5f78672c515ca9a393b8ac6
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 - (NSArray *)arrayOfMenuItemsForEventsWithTarget:(id)target forGlobalMenu:(BOOL)global;
27 - (NSMutableArray *)appendEventsForObject:(AIListObject *)listObject eventID:(NSString *)eventID toArray:(NSMutableArray *)events;
28 - (void)addMenuItemsForEventHandlers:(NSDictionary *)inEventHandlers toArray:(NSMutableArray *)menuItemArray withTarget:(id)target forGlobalMenu:(BOOL)global;
29 - (void)removeAllAlertsFromListObject:(AIListObject *)listObject;
30 @end
32 @implementation ESContactAlertsController
34 static  NSMutableDictionary             *eventHandlersByGroup[EVENT_HANDLER_GROUP_COUNT];
35 static  NSMutableDictionary             *globalOnlyEventHandlersByGroup[EVENT_HANDLER_GROUP_COUNT];
37 /*!
38  * @brief Initialize before the class is used
39  */
40 + (void)initialize
42         static BOOL didInitialize = NO;
43         if (!didInitialize) {
44                 for (int i = 0; i < EVENT_HANDLER_GROUP_COUNT; i++) {
45                         eventHandlersByGroup[i] = nil;
46                         globalOnlyEventHandlersByGroup[i] = nil;
47                 }
49                 didInitialize = YES;
50         }
53 /*!
54  * @brief Init
55  */
56 - (id)init
58         if ((self = [super init])) {
59                 globalOnlyEventHandlers = [[NSMutableDictionary alloc] init];
60                 eventHandlers = [[NSMutableDictionary alloc] init];
61                 actionHandlers = [[NSMutableDictionary alloc] init];
62         }
63         
64         return self;
67 - (void)controllerDidLoad
71 - (void)controllerWillClose
73         
76 /*!
77  * @brief Deallocate
78  */
79 - (void)dealloc
81         [globalOnlyEventHandlers release]; globalOnlyEventHandlers = nil;
82         [eventHandlers release]; eventHandlers = nil;
83         [actionHandlers release]; actionHandlers = nil;
84         
85         [super dealloc];
89 //Events ---------------------------------------------------------------------------------------------------------------
90 #pragma mark Events
92 /*!
93  * @brief Register an event
94  *
95  * An event must have a unique eventID. handler is responsible for providing information
96  * about the event, such as short and long descriptions. The group determines how the event will be displayed in the events
97  * preferences; events in the same group are displayed together.
98  *
99  * @param eventID Unique event ID
100  * @param handler The handler, which must conform to AIEventHandler
101  * @param inGroup The group
102  * @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.
103  */
104 - (void)registerEventID:(NSString *)eventID
105                         withHandler:(id <AIEventHandler>)handler
106                                 inGroup:(AIEventHandlerGroupType)inGroup
107                          globalOnly:(BOOL)global
109         if (global) {
110                 [globalOnlyEventHandlers setObject:handler forKey:eventID];
111                 
112                 if (!globalOnlyEventHandlersByGroup[inGroup]) globalOnlyEventHandlersByGroup[inGroup] = [[NSMutableDictionary alloc] init];
113                 [globalOnlyEventHandlersByGroup[inGroup] setObject:handler forKey:eventID];
114                 
115         } else {
116                 [eventHandlers setObject:handler forKey:eventID];
117                 
118                 if (!eventHandlersByGroup[inGroup]) eventHandlersByGroup[inGroup] = [[NSMutableDictionary alloc] init];
119                 [eventHandlersByGroup[inGroup] setObject:handler forKey:eventID];
120         }
123 //Return all event IDs for groups/contacts
124 - (NSArray *)allEventIDs
126         return [[eventHandlers allKeys] arrayByAddingObjectsFromArray:[globalOnlyEventHandlers allKeys]];
129 - (NSString *)longDescriptionForEventID:(NSString *)eventID forListObject:(AIListObject *)listObject
131         id <AIEventHandler> handler;
132         
133         handler = [eventHandlers objectForKey:eventID];
134         if (!handler) handler = [globalOnlyEventHandlers objectForKey:eventID];
135         
136         return [handler longDescriptionForEventID:eventID forListObject:listObject];
140  * @brief Returns a menu of all events
141  * 
142  * A menu item's represented object is the dictionary describing the event it represents
144  * @param target The target on which @selector(selectEvent:) will be called on selection.
145  * @param global If YES, the events listed will include global ones (such as Error Occurred) in addition to contact-specific ones.
146  * @result An NSMenu of the events
147  */
148 - (NSMenu *)menuOfEventsWithTarget:(id)target forGlobalMenu:(BOOL)global
150         NSEnumerator            *enumerator;
151         NSMenuItem                      *item;
152         NSMenu                          *menu;
154         //Prepare our menu
155         menu = [[NSMenu allocWithZone:[NSMenu zone]] init];
156         [menu setAutoenablesItems:NO];
157         
158         enumerator = [[self arrayOfMenuItemsForEventsWithTarget:target forGlobalMenu:global] objectEnumerator];
159         while ((item = [enumerator nextObject])) {
160                 [menu addItem:item];
161         }
162         
163         return [menu autorelease];
166 - (NSArray *)arrayOfMenuItemsForEventsWithTarget:(id)target forGlobalMenu:(BOOL)global
168         NSMutableArray          *menuItemArray = [NSMutableArray array];
169         BOOL                            addedItems = NO;
170         int                                     i;
171         
172         for (i = 0; i < EVENT_HANDLER_GROUP_COUNT; i++) {
173                 NSMutableArray          *groupMenuItemArray;
175                 //Create an array of menu items for this group
176                 groupMenuItemArray = [NSMutableArray array];
177                 
178                 [self addMenuItemsForEventHandlers:eventHandlersByGroup[i]
179                                                                    toArray:groupMenuItemArray
180                                                                 withTarget:target
181                                                          forGlobalMenu:global];
182                 if (global) {
183                         [self addMenuItemsForEventHandlers:globalOnlyEventHandlersByGroup[i]
184                                                                            toArray:groupMenuItemArray
185                                                                         withTarget:target
186                                                                  forGlobalMenu:global];
187                 }
188                 
189                 if ([groupMenuItemArray count]) {
190                         //Add a separator if we are adding a group and we have added before
191                         if (addedItems) {
192                                 [menuItemArray addObject:[NSMenuItem separatorItem]];
193                         } else {
194                                 addedItems = YES;
195                         }
196                         
197                         //Sort the array of menuItems alphabetically by title within this group
198                         [groupMenuItemArray sortUsingSelector:@selector(titleCompare:)];
200                         [menuItemArray addObjectsFromArray:groupMenuItemArray];
201                 }
202         }
203         
204         return menuItemArray;
205 }       
207 - (void)addMenuItemsForEventHandlers:(NSDictionary *)inEventHandlers toArray:(NSMutableArray *)menuItemArray withTarget:(id)target forGlobalMenu:(BOOL)global
208 {       
209         NSEnumerator            *enumerator;
210         NSString                        *eventID;
211         NSMenuItem                      *menuItem;
212         
213         enumerator = [inEventHandlers keyEnumerator];
214         while ((eventID = [enumerator nextObject])) {
215                 id <AIEventHandler>     eventHandler = [inEventHandlers objectForKey:eventID];          
216                 
217         menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:(global ?
218                                                                                                                                                                 [eventHandler globalShortDescriptionForEventID:eventID] :
219                                                                                                                                                                 [eventHandler shortDescriptionForEventID:eventID])
220                                                                                                                                                 target:target 
221                                                                                                                                                 action:@selector(selectEvent:) 
222                                                                                                                                  keyEquivalent:@""];
223         [menuItem setRepresentedObject:eventID];
224                 [menuItemArray addObject:menuItem];
225                 [menuItem release];
226     }
230  * @brief Sort event IDs by group and then by global short description
231  */
232 int eventIDSort(id objectA, id objectB, void *context) {
233         int                                     groupA, groupB;
234         id <AIEventHandler> eventHandlerA;
235         id <AIEventHandler> eventHandlerB;
236         
237         //Determine the group of the first event ID
238         for (groupA = 0; groupA < EVENT_HANDLER_GROUP_COUNT; groupA++) {
239                 eventHandlerA = [eventHandlersByGroup[groupA] objectForKey:objectA];
240                 if (!eventHandlerA) {
241                         eventHandlerA = [globalOnlyEventHandlersByGroup[groupA] objectForKey:objectA];
242                 }
243                 
244                 if (eventHandlerA) break;
245         }
246         
247         //Determine the group of the second ID
248         for (groupB = 0; groupB < EVENT_HANDLER_GROUP_COUNT; groupB++) {
249                 eventHandlerB = [eventHandlersByGroup[groupB] objectForKey:objectB];
250                 if (!eventHandlerB) {
251                         eventHandlerB = [globalOnlyEventHandlersByGroup[groupB] objectForKey:objectB];
252                 }
253                 
254                 if (eventHandlerB) break;
255         }
256         
257         if (groupA < groupB) {
258                 return NSOrderedAscending;
259                 
260         } else if (groupB < groupA) {
261                 return NSOrderedDescending;
262                 
263         } else {
264                 NSString        *descriptionA = [eventHandlerA globalShortDescriptionForEventID:objectA];
265                 NSString        *descriptionB = [eventHandlerA globalShortDescriptionForEventID:objectB];
266                 
267                 return ([descriptionA caseInsensitiveCompare:descriptionB]);
268         }
272  * @brief Sort an array of event IDs
274  * @brief inArray The array of eventIDs to sort
275  * @return The array sorted by eventIDSort()
276  */
277 - (NSArray *)sortedArrayOfEventIDsFromArray:(NSArray *)inArray
279         return [inArray sortedArrayUsingFunction:eventIDSort context:NULL];
283  * @brief Return the image associated with an event
284  */
285 - (NSImage *)imageForEventID:(NSString *)eventID
287         id <AIEventHandler>     eventHandler;
288         
289         eventHandler = [eventHandlers objectForKey:eventID];            
290         if (!eventHandler) eventHandler = [globalOnlyEventHandlers objectForKey:eventID];
292         return [eventHandler imageForEventID:eventID];
296  * @brief Generate an event, returning a set of the actionIDs which were performed.
298  * @param eventID The event which occurred
299  * @param listObject The object for which the event occurred
300  * @param userInfo Event-specific user info
301  * @param previouslyPerformedActionIDs If non-nil, a set of actionIDs which should be treated as if they had already been performed in this invocation.
303  * @result The set of actions which were performed, suitable for being passed back in for another event generation via previouslyPerformedActionIDs
304  */
305 - (NSSet *)generateEvent:(NSString *)eventID forListObject:(AIListObject *)listObject userInfo:(id)userInfo previouslyPerformedActionIDs:(NSSet *)previouslyPerformedActionIDs
307         NSArray                 *alerts = [self appendEventsForObject:listObject eventID:eventID toArray:nil];
308         NSMutableSet    *performedActionIDs = nil;
309         
310         if (alerts && [alerts count]) {
311                 NSEnumerator            *enumerator;
312                 NSDictionary            *alert;
314                 performedActionIDs = (previouslyPerformedActionIDs ?
315                                                           [[previouslyPerformedActionIDs mutableCopy] autorelease]:
316                                                           [NSMutableSet set]);
317                 
318                 //We go from contact->group->root; a given action will only fire once for this event
319                 enumerator = [alerts objectEnumerator];
321                 //Process each alert (There may be more than one for an event)
322                 while ((alert = [enumerator nextObject])) {
323                         NSString        *actionID;
324                         id <AIActionHandler>    actionHandler;                  
325                         
326                         actionID = [alert objectForKey:KEY_ACTION_ID];
327                         actionHandler = [actionHandlers objectForKey:actionID];         
328                         
329                         if ((![performedActionIDs containsObject:actionID]) || ([actionHandler allowMultipleActionsWithID:actionID])) {
330                                 if ([actionHandler performActionID:actionID
331                                                                                          forListObject:listObject
332                                                                                            withDetails:[alert objectForKey:KEY_ACTION_DETAILS] 
333                                                                                  triggeringEventID:eventID
334                                                                                   userInfo:userInfo]) {
335                                         
336                                         //If this alert was a single-fire alert, we can delete it now
337                                         if ([[alert objectForKey:KEY_ONE_TIME_ALERT] intValue]) {
338                                                 [self removeAlert:alert fromListObject:listObject];
339                                         }
340                                         
341                                         //We don't want to perform this action again for this event
342                                         [performedActionIDs addObject:actionID];
343                                 }
344                         }
345                 }
346         }
347         
348         [[adium notificationCenter] postNotificationName:eventID
349                                                                                           object:listObject 
350                                                                                         userInfo:userInfo];
351         
352         /* If we generated a new perfromedActionIDs, return it.  If we didn't, return the original
353          * previouslyPerformedActionIDs, which may also be nil or may be actionIDs performed on some previous invocation.
354          */
355         return (performedActionIDs ? performedActionIDs : previouslyPerformedActionIDs);
359  * @brief Append events for the passed object to the specified array.
361  * @param events The array of events so far. Create the array if passed nil.
362  * @param The object for which we'ere retrieving events. If nil, we retrieve the global preferences.
364  * This method is intended to be called recursively; it should generate an array which has alerts from:
365  * contact->metaContact->group->global preferences (skipping any which don't exist).
367  * @result An array which contains the object's own events followed by its containingObject's events.
368  */
369 - (NSMutableArray *)appendEventsForObject:(AIListObject *)listObject eventID:(NSString *)eventID toArray:(NSMutableArray *)events
371         NSArray                 *newEvents;
373         //Add events for this object (replacing any inherited from the containing object so that this object takes precendence)
374         newEvents = [[[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS
375                                                                                                                   group:PREF_GROUP_CONTACT_ALERTS
376                                                                           objectIgnoringInheritance:listObject] objectForKey:eventID];
377         
378         if (newEvents && [newEvents count]) {
379                 if (!events) events = [NSMutableArray array];
380                 [events addObjectsFromArray:newEvents];
381         }
383         //Get all events from the contanining object if we have an object
384         if (listObject) {
385                 //If listObject doesn't have a containingObject, this will pass nil
386                 events = [self appendEventsForObject:[listObject containingObject]
387                                                                          eventID:eventID
388                                                                          toArray:events];
389         }
391         return events;
395  * @brief Return the default event ID for a new alert
396  */
397 - (NSString *)defaultEventID
399         NSString *defaultEventID = [[adium preferenceController] preferenceForKey:KEY_DEFAULT_EVENT_ID
400                                                                                                                                                 group:PREF_GROUP_CONTACT_ALERTS];
401         if (![eventHandlers objectForKey:defaultEventID]) {
402                 defaultEventID = [[eventHandlers allKeys] objectAtIndex:0];
403         }
404         
405         return defaultEventID;
409  * @brief Find the eventID associated with an English name
411  * This exists for compatibility with old AdiumXtras...
412  */
413 - (NSString *)eventIDForEnglishDisplayName:(NSString *)displayName
415         NSEnumerator            *enumerator;
416         NSString                        *eventID;
417         
418         enumerator = [eventHandlers keyEnumerator];
419         while ((eventID = [enumerator nextObject])) {
420                 id <AIEventHandler>     eventHandler = [eventHandlers objectForKey:eventID];            
421                 if ([[eventHandler englishGlobalShortDescriptionForEventID:eventID] isEqualToString:displayName]) {
422                         return eventID;
423                 }
424         }
426         enumerator = [globalOnlyEventHandlers keyEnumerator];
427         while ((eventID = [enumerator nextObject])) {
428                 id <AIEventHandler>     eventHandler = [globalOnlyEventHandlers objectForKey:eventID];          
429                 if ([[eventHandler englishGlobalShortDescriptionForEventID:eventID] isEqualToString:displayName]) {
430                         return eventID;
431                 }
432         }
433         
434         return nil;
438  * @brief Return a short description to describe eventID when considered globally
439  */
440 - (NSString *)globalShortDescriptionForEventID:(NSString *)eventID
442         id <AIEventHandler>     eventHandler;
443         
444         eventHandler = [eventHandlers objectForKey:eventID];
445         if (!eventHandler) eventHandler = [globalOnlyEventHandlers objectForKey:eventID];
446         
447         if (eventHandler) {
448                 return [eventHandler globalShortDescriptionForEventID:eventID];
449         }
450         
451         return @"";
455  * @brief Return a natural language, localized description for an event
457  * This will be suitable for display to the user such as in a message window or a Growl notification
459  * @param eventID The event
460  * @param listObject The object for which the event occurred
461  * @param userInfo Event-specific userInfo
462  * @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.
463  * @result The natural language description
464  */
465 - (NSString *)naturalLanguageDescriptionForEventID:(NSString *)eventID
466                                                                                 listObject:(AIListObject *)listObject
467                                                                                   userInfo:(id)userInfo
468                                                                         includeSubject:(BOOL)includeSubject
470         id <AIEventHandler>     eventHandler;
472         eventHandler = [eventHandlers objectForKey:eventID];
473         if (!eventHandler) eventHandler = [globalOnlyEventHandlers objectForKey:eventID];
475         if (eventHandler) {
476                 return [eventHandler naturalLanguageDescriptionForEventID:eventID
477                                                                                                            listObject:listObject
478                                                                                                                  userInfo:userInfo
479                                                                                                    includeSubject:includeSubject];
480         }
482         return @"";
486 //Actions --------------------------------------------------------------------------------------------------------------
487 #pragma mark Actions
489  * @brief Register an actionID and its handler
491  * When an event occurs -- that is, when the event is generated via
492  * -[ESContactAlertsController generateEvent:forListObject:userInfo:] -- the handler for each action
493  * associated with that event within the appropriate list object's heirarchy (object -> containing group -> global)
494  * will be called as per the AIActionHandler protocol.
496  * @param actionID The actionID
497  * @param handler The handler, which must conform to the AIActionHandler protocol
498  */
499 - (void)registerActionID:(NSString *)actionID withHandler:(id <AIActionHandler>)handler
501         [actionHandlers setObject:handler forKey:actionID];
505  * @brief Return a dictionary whose keys are action IDs and whose objects are objects conforming to AIActionHandler
506  */
507 - (NSDictionary *)actionHandlers
509         return actionHandlers;
513  * @brief Returns a menu of all actions
515  * A menu item's represented object is the dictionary describing the action it represents
517  * @param target The target on which @selector(selectAction:) will be called on selection
518  * @result The NSMenu, which does not send validateMenuItem: messages
519  */
520 - (NSMenu *)menuOfActionsWithTarget:(id)target
522     NSEnumerator        *enumerator;
523     NSString            *actionID;
524         NSMenuItem              *menuItem;
525         NSMenu                  *menu;
526         NSMutableArray  *menuItemArray;
527         
528         //Prepare our menu
529         menu = [[NSMenu alloc] init];
530         [menu setAutoenablesItems:NO];
531         
532         menuItemArray = [[NSMutableArray alloc] init];
533         
534     //Insert a menu item for each available action
535         enumerator = [actionHandlers keyEnumerator];
536         while ((actionID = [enumerator nextObject])) {
537                 id <AIActionHandler> actionHandler = [actionHandlers objectForKey:actionID];            
538                 
539         menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[actionHandler shortDescriptionForActionID:actionID]
540                                                                                                                                                 target:target 
541                                                                                                                                                 action:@selector(selectAction:) 
542                                                                                                                                  keyEquivalent:@""];
543         [menuItem setRepresentedObject:actionID];
544                 [menuItem setImage:[[actionHandler imageForActionID:actionID] imageByScalingForMenuItem]];
545                 
546         [menuItemArray addObject:menuItem];
547                 [menuItem release];
548     }
550         //Sort the array of menuItems alphabetically by title   
551         [menuItemArray sortUsingSelector:@selector(titleCompare:)];
552         
553         enumerator = [menuItemArray objectEnumerator];
554         while ((menuItem = [enumerator nextObject])) {
555                 [menu addItem:menuItem];
556         }
557         
558         [menuItemArray release];
560         return [menu autorelease];
561 }       
564  * @brief Return the default action ID for a new alert
565  */
566 - (NSString *)defaultActionID
568         NSString *defaultActionID = [[adium preferenceController] preferenceForKey:KEY_DEFAULT_ACTION_ID
569                                                                                                                                                  group:PREF_GROUP_CONTACT_ALERTS];
570         if (![actionHandlers objectForKey:defaultActionID]) {
571                 defaultActionID = [[actionHandlers allKeys] objectAtIndex:0];
572         }
573         
574         return defaultActionID;
577 //Alerts ---------------------------------------------------------------------------------------------------------------
578 #pragma mark Alerts
580  * @brief Returns an array of all the alerts of a given list object
582  * @param listObject The object
583  */
584 - (NSArray *)alertsForListObject:(AIListObject *)listObject
586         return [self alertsForListObject:listObject withEventID:nil actionID:nil];
590  * @brief Return an array of all alerts for a list object
592  * @param listObject The object, or nil for global
593  * @param eventID If specified, only return events matching eventID. If nil, don't filter based on events.
594  * @param actionID If specified, only return actions matching actionID. If nil, don't filter based on actionID.
595  */
596 - (NSArray *)alertsForListObject:(AIListObject *)listObject withEventID:(NSString *)eventID actionID:(NSString *)actionID
598         NSDictionary    *contactAlerts = [[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS
599                                                                                                                                                           group:PREF_GROUP_CONTACT_ALERTS
600                                                                                                                   objectIgnoringInheritance:listObject];
601         NSMutableArray  *alertArray = [NSMutableArray array];
603         if (eventID) {
604                 /* If we have an eventID, just look at the alerts for this eventID */
605                 NSEnumerator    *alertEnumerator;
606                 NSDictionary    *alert;
607                 
608                 alertEnumerator = [[contactAlerts objectForKey:eventID] objectEnumerator];
609                 
610                 while ((alert = [alertEnumerator nextObject])) {
611                         //If we don't have a specific actionID, or this one is right, add it
612                         if (!actionID || [actionID isEqualToString:[alert objectForKey:KEY_ACTION_ID]]) {
613                                 [alertArray addObject:alert];
614                         }
615                 }
616                 
617         } else {
618                 /* If we don't have an eventID, look at all alerts */
619                 NSEnumerator    *groupEnumerator;
620                 NSString                *anEventID;
621                 
622                 //Flatten the alert dict into an array
623                 groupEnumerator = [contactAlerts keyEnumerator];
624                 while ((anEventID = [groupEnumerator nextObject])) {
625                         NSEnumerator    *alertEnumerator;
626                         NSDictionary    *alert;
627                         
628                         alertEnumerator = [[contactAlerts objectForKey:anEventID] objectEnumerator];
629                         while ((alert = [alertEnumerator nextObject])) {
630                                 //If we don't have a specific actionID, or this one is right, add it
631                                 if (!actionID || [actionID isEqualToString:[alert objectForKey:KEY_ACTION_ID]]) {
632                                         [alertArray addObject:alert];
633                                 }
634                         }
635                 }       
636         }
637         
638         return alertArray;      
642  * @brief Add an alert (passed as a dictionary) to a list object
644  * @param newAlert The alert to add
645  * @param listObject The object to which to add, or nil for global
646  * @param setAsNewDefaults YES to make the type and details of newAlert be the new default for new alerts
647  */
648 - (void)addAlert:(NSDictionary *)newAlert toListObject:(AIListObject *)listObject setAsNewDefaults:(BOOL)setAsNewDefaults
650         NSString                        *newAlertEventID = [newAlert objectForKey:KEY_EVENT_ID];
651         NSMutableDictionary     *contactAlerts;
652         NSMutableArray          *eventArray;
653         
654         [[adium preferenceController] delayPreferenceChangedNotifications:YES];
655         
656         //Get the alerts for this list object
657         contactAlerts = [[[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS
658                                                                                                                           group:PREF_GROUP_CONTACT_ALERTS
659                                                                                   objectIgnoringInheritance:listObject] mutableCopy];
660         if (!contactAlerts) contactAlerts = [[NSMutableDictionary alloc] init];
661         
662         //Get the event array for the new alert, making a copy so we can modify it
663         eventArray = [[contactAlerts objectForKey:newAlertEventID] mutableCopy];
664         if (!eventArray) eventArray = [[NSMutableArray alloc] init];
665         
666         //Avoid putting the exact same alert into the array twice
667         if ([eventArray indexOfObject:newAlert] == NSNotFound) {
668                 //Add the new alert
669                 [eventArray addObject:newAlert];
670                 
671                 //Put the modified event array back into the contact alert dict, and save our changes
672                 [contactAlerts setObject:eventArray forKey:newAlertEventID];
673                 [[adium preferenceController] setPreference:contactAlerts
674                                                                                          forKey:KEY_CONTACT_ALERTS
675                                                                                           group:PREF_GROUP_CONTACT_ALERTS
676                                                                                          object:listObject];    
677         }
679         //Update the default events if requested
680         if (setAsNewDefaults) {
681                 [[adium preferenceController] setPreference:newAlertEventID
682                                                                                          forKey:KEY_DEFAULT_EVENT_ID
683                                                                                           group:PREF_GROUP_CONTACT_ALERTS];
684                 [[adium preferenceController] setPreference:[newAlert objectForKey:KEY_ACTION_ID]
685                                                                                          forKey:KEY_DEFAULT_ACTION_ID
686                                                                                           group:PREF_GROUP_CONTACT_ALERTS];     
687         }
688         
689         //Cleanup
690         [contactAlerts release];
691         [eventArray release];
692         
693         [[adium preferenceController] delayPreferenceChangedNotifications:NO];
697  * @brief Add an alert at the global level
698  */
699 - (void)addGlobalAlert:(NSDictionary *)newAlert
701         [self addAlert:newAlert toListObject:nil setAsNewDefaults:NO];
705  * @brief Remove an alert from a listObject
707  * @param victimAlert The alert to remove; it will be tested against existing alerts using isEqual: so must be identical
708  * @param listObject The object (or nil, for global) from which to remove victimAlert
709  */
710 - (void)removeAlert:(NSDictionary *)victimAlert fromListObject:(AIListObject *)listObject
712         NSMutableDictionary     *contactAlerts = [[[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS
713                                                                                                                                                                    group:PREF_GROUP_CONTACT_ALERTS
714                                                                                                                            objectIgnoringInheritance:listObject] mutableCopy];
715         NSString                        *victimEventID = [victimAlert objectForKey:KEY_EVENT_ID];
716         NSMutableArray          *eventArray;
717         
718         //Get the event array containing the victim alert, making a copy so we can modify it
719         eventArray = [[contactAlerts objectForKey:victimEventID] mutableCopy];
720         
721         //Remove the victim
722         [eventArray removeObject:victimAlert];
723         
724         //Put the modified event array back into the contact alert dict, and save our changes
725         if ([eventArray count]) {
726                 [contactAlerts setObject:eventArray forKey:victimEventID];
727         } else {
728                 [contactAlerts removeObjectForKey:victimEventID];       
729         }
730         
731         [[adium preferenceController] setPreference:contactAlerts
732                                                                                  forKey:KEY_CONTACT_ALERTS
733                                                                                   group:PREF_GROUP_CONTACT_ALERTS
734                                                                                  object:listObject];
735         [eventArray release];
736         [contactAlerts release];
740  * @brief Remove all alerts which are specifically applied to listObject
742  * This does not affect alerts set at higher (containing object, root) levels 
743  */
744 - (void)removeAllAlertsFromListObject:(AIListObject *)listObject
746         [listObject setPreference:nil
747                                            forKey:KEY_CONTACT_ALERTS
748                                                 group:PREF_GROUP_CONTACT_ALERTS];
752  * @brief Remove all global (root-level) alerts with a given action ID
753  */
754 - (void)removeAllGlobalAlertsWithActionID:(NSString *)actionID
756         NSDictionary            *contactAlerts = [[adium preferenceController] preferenceForKey:KEY_CONTACT_ALERTS 
757                                                                                                                                                                   group:PREF_GROUP_CONTACT_ALERTS];
758         NSMutableDictionary *newContactAlerts = [contactAlerts mutableCopy];
759         NSEnumerator            *enumerator = [contactAlerts keyEnumerator];
760         NSString                        *victimEventID;
761         NSEnumerator            *alertArrayEnumerator;
762         NSArray                         *eventArray;
763         NSDictionary            *alertDict;
764         
765         //The contact alerts preference is a dictionary keyed by event.  Each event key yields an array of dictionaries;
766         //each of these dictionaries represents an alert.  We want to remove all dictionaries which represent alerts with
767         //the passed actionID
768         while ((victimEventID = [enumerator nextObject])) {
769                 NSMutableArray  *newEventArray = nil;
770         
771                 eventArray = [contactAlerts objectForKey:victimEventID];
773                 //Enumerate each alert for this event
774                 alertArrayEnumerator = [eventArray objectEnumerator];
775                 while ((alertDict = [alertArrayEnumerator nextObject])) {
776                         
777                         //We found an alertDict which needs to be removed
778                         if ([[alertDict objectForKey:KEY_ACTION_ID] isEqualToString:actionID]) {
779                                 //If this is the first modification to the current eventArray, make a mutableCopy with which to work
780                                 if (!newEventArray) newEventArray = [eventArray mutableCopy];
781                                 [newEventArray removeObject:alertDict];
782                         }
783                 }
784                 
785                 //newEventArray will only be non-nil if we made changes; now that we have enumerated this eventArray, save them
786                 if (newEventArray) {
787                         if ([newEventArray count]) {
788                                 [newContactAlerts setObject:newEventArray forKey:victimEventID];
789                         } else {
790                                 [newContactAlerts removeObjectForKey:victimEventID];    
791                         }
792                         
793                         //Clean up
794                         [newEventArray release];
795                 }
796         }
797         
798         [[adium preferenceController] setPreference:newContactAlerts
799                                                                                  forKey:KEY_CONTACT_ALERTS
800                                                                                   group:PREF_GROUP_CONTACT_ALERTS];
801         [newContactAlerts release];
805  * @brief Remove all current global alerts and replace them with the alerts in allGlobalAlerts
807  * Used for setting a preset of events
808  */
809 - (void)setAllGlobalAlerts:(NSArray *)allGlobalAlerts
811         NSMutableDictionary     *contactAlerts = [[NSMutableDictionary alloc] init];;
812         NSDictionary            *eventDict;
813         NSEnumerator            *enumerator;
814         
815         [[adium preferenceController] delayPreferenceChangedNotifications:YES];
816         
817         enumerator = [allGlobalAlerts objectEnumerator];
818         while ((eventDict = [enumerator nextObject])) {
819                 NSMutableArray          *eventArray;
820                 NSString                        *eventID = [eventDict objectForKey:KEY_EVENT_ID];
822                 /* Get the event array for this alert. Since we are creating the entire dictionary, we can be sure we are working
823                  * with an NSMutableArray.
824                  */
825                 eventArray = [contactAlerts objectForKey:eventID];
826                 if (!eventArray) eventArray = [NSMutableArray array];           
828                 //Add the new alert
829                 [eventArray addObject:eventDict];
830                 
831                 //Put the modified event array back into the contact alert dict
832                 [contactAlerts setObject:eventArray forKey:eventID];            
833         }
834         
835         [[adium preferenceController] setPreference:contactAlerts
836                                                                                  forKey:KEY_CONTACT_ALERTS
837                                                                                   group:PREF_GROUP_CONTACT_ALERTS
838                                                                                  object:nil];
839         [contactAlerts release];
841         [[adium preferenceController] delayPreferenceChangedNotifications:NO];
842         
846  * @brief Move all contact alerts from oldObject to newObject
848  * This is useful when adding oldObject to the metaContact newObject so that any existing contact alerts for oldObject
849  * are applied at the contact-general level, displayed and handled properly for the new, combined contact.
851  * @param oldObject The object from which to move contact alerts
852  * @param newObject The object to which to we want to add the moved contact alerts
853  */
854 - (void)mergeAndMoveContactAlertsFromListObject:(AIListObject *)oldObject intoListObject:(AIListObject *)newObject
856         NSArray                         *oldAlerts = [self alertsForListObject:oldObject];
857         NSEnumerator            *enumerator = [oldAlerts objectEnumerator];
858         NSDictionary            *alertDict;
859         
860         [[adium preferenceController] delayPreferenceChangedNotifications:YES];
861         
862         //Add each alert to the target (addAlert:toListObject:setAsNewDefaults: will ensure identical alerts aren't added more than once)
863         while ((alertDict  = [enumerator nextObject])) {
864                 [self addAlert:alertDict toListObject:newObject setAsNewDefaults:NO];
865         }
866         
867         //Remove the alerts from the originating list object
868         [self removeAllAlertsFromListObject:oldObject];
869         
870         [[adium preferenceController] delayPreferenceChangedNotifications:NO];
873 #pragma mark -
875  * @brief Is the passed event a message event?
877  * Examples of messages events are "message sent" and "message received."
879  * @result YES if it is a message event
880  */
881 - (BOOL)isMessageEvent:(NSString *)eventID
883         return ([eventHandlersByGroup[AIMessageEventHandlerGroup] objectForKey:eventID] != nil ||
884                    ([globalOnlyEventHandlersByGroup[AIMessageEventHandlerGroup] objectForKey:eventID] != nil));
888  * @brief Is the passed event a contact status event?
890  * Examples of messages events are "contact signed on" and "contact went away."
892  * @result YES if it is a contact status event
893  */
894 - (BOOL)isContactStatusEvent:(NSString *)eventID
896         return ([eventHandlersByGroup[AIContactsEventHandlerGroup] objectForKey:eventID] != nil ||
897                         ([globalOnlyEventHandlersByGroup[AIContactsEventHandlerGroup] objectForKey:eventID] != nil));
900 @end