Put NSAutoreleasePool usage around other distributed notification observer methods
[adiumx.git] / Source / ESGlobalEventsPreferences.m
blob12b30588e16e01f48e91410e6e3165264a604746
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 "AISoundController.h"
18 #import "Adium/ESContactAlertsViewController.h"
19 #import <Adium/AIContactAlertsControllerProtocol.h>
20 #import "ESGlobalEventsPreferences.h"
21 #import "ESGlobalEventsPreferencesPlugin.h"
22 #import <Adium/ESPresetManagementController.h>
23 #import <Adium/ESPresetNameSheetController.h>
24 #import <Adium/AISoundSet.h>
25 #import <AIUtilities/AIMenuAdditions.h>
26 #import <AIUtilities/AIPopUpButtonAdditions.h>
27 #import <AIUtilities/AIStringAdditions.h>
28 #import <AIUtilities/AIApplicationAdditions.h>
29 #import <AIUtilities/AIVariableHeightOutlineView.h>
30 #import <AIUtilities/AIVerticallyCenteredTextCell.h>
31 #import <AIUtilities/AIAttributedStringAdditions.h>
32 #import <AIUtilities/AIArrayAdditions.h>
33 #import <AIUtilities/AIImageAdditions.h>
34 #import <AIUtilities/AIImageTextCell.h>
36 #define PREF_GROUP_EVENT_PRESETS        @"Event Presets"
37 #define CUSTOM_TITLE                            AILocalizedString(@"Custom",nil)
38 #define COPY_IN_PARENTHESIS                     AILocalizedString(@"(Copy)","Copy, in parenthesis, as a noun indicating that the preceding item is a duplicate")
40 #define VOLUME_SOUND_PATH   [NSString pathWithComponents:[NSArray arrayWithObjects: \
41         @"/", @"System", @"Library", @"LoginPlugins", \
42         [@"BezelServices" stringByAppendingPathExtension:@"loginPlugin"], \
43         @"Contents", @"Resources", \
44         [@"volume" stringByAppendingPathExtension:@"aiff"], \
45         nil]]
47 @interface ESGlobalEventsPreferences (PRIVATE)
48 - (void)popUp:(NSPopUpButton *)inPopUp shouldShowCustom:(BOOL)showCustom;
49 - (void)xtrasChanged:(NSNotification *)notification;
50 - (void)contactAlertsDidChangeForActionID:(NSString *)actionID;
52 - (NSMenu *)showMenu;
53 - (NSMenu *)eventPresetsMenu;
55 - (IBAction)selectSoundSet:(id)sender;
56 - (NSMenu *)_soundSetMenu;
58 - (void)editEventsWithEventID:(NSString *)eventID;
60 - (NSString *)_localizedTitle:(NSString *)englishTitle;
62 - (void)saveCurrentEventPreset;
64 - (void)setAndConfigureEventPresetsMenu;
65 - (void)updateSoundSetSelection;
66 - (void)updateSoundSetSelectionForSoundSet:(AISoundSet *)soundSet;
67 @end
69 @implementation ESGlobalEventsPreferences
70 - (NSString *)paneIdentifier
72         return @"Events";
74 - (NSString *)paneName{ 
75     return AILocalizedString(@"Events", "Name of preferences and tab for specifying what Adium should do when events occur - for example, display a Growl alert when John signs on.");
77 /*!
78  * @brief Nib name
79  */
80 - (NSString *)nibName{
81     return @"GlobalEventsPreferences";
83 - (NSImage *)paneIcon
85         return [NSImage imageNamed:@"pref-events" forClass:[self class]];
88 - (BOOL)resizableHorizontally
90         return YES;
93 /*!
94  * @brief Configure the preference view
95  */
96 - (void)viewDidLoad
98         //Configure our global contact alerts view controller
99         [contactAlertsViewController setConfigureForGlobal:YES];
100         [contactAlertsViewController setDelegate:self];
101         [contactAlertsViewController setShowEventsInEditSheet:NO];
102         
103         //Observe for installation of new sound sets and set up the sound set menu
104         [[adium notificationCenter] addObserver:self
105                                                                    selector:@selector(xtrasChanged:)
106                                                                            name:AIXtrasDidChangeNotification
107                                                                          object:nil];
109         //This will build the sound set menu
110         [self xtrasChanged:nil];        
112         //Presets menu
113         [self setAndConfigureEventPresetsMenu];
115         //And event presets to update our presets menu
116         [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_EVENT_PRESETS];
118         //Ensure the correct sound set is selected
119         [self updateSoundSetSelection];
120         
121         //Volume
122         [slider_volume setFloatValue:[[[adium preferenceController] preferenceForKey:KEY_SOUND_CUSTOM_VOLUME_LEVEL
123                                                                                                                                                    group:PREF_GROUP_SOUNDS] floatValue]];       
126 - (void)localizePane
128         [[button_minvolume cell] accessibilitySetOverrideValue:AILocalizedString(@"Set minimum volume", nil)
129                                                                            forAttribute:NSAccessibilityDescriptionAttribute];
130         [[button_maxvolume cell] accessibilitySetOverrideValue:AILocalizedString(@"Set maximum volume", nil)
131                                                                            forAttribute:NSAccessibilityDescriptionAttribute];
132         [[slider_volume cell] accessibilitySetOverrideValue:AILocalizedString(@"Volume", nil)
133                                                                                    forAttribute:NSAccessibilityDescriptionAttribute];
134          
135         [label_eventPreset setLocalizedString:AILocalizedString(@"Event preset:",nil)];
136         [label_soundSet setLocalizedString:AILocalizedString(@"Sound set:",nil)];
140  * @brief Preference view is closing
141  */
142 - (void)viewWillClose
144         [contactAlertsViewController viewWillClose];
145         [contactAlertsViewController release]; contactAlertsViewController = nil;
147         [[adium preferenceController] unregisterPreferenceObserver:self];
148     [[adium notificationCenter] removeObserver:self];
152  * @brief PREF_GROUP_CONTACT_ALERTS changed; update our summary data
153  */
154 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
155                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
157         if ([group isEqualToString:PREF_GROUP_EVENT_PRESETS]) {
158                 if (!key || [key isEqualToString:@"Event Presets"]) {
159                         //Update when the available event presets change
160                         [self setAndConfigureEventPresetsMenu];
161                 }
162         }
166  * @brief Set if a popup should have a "Custom" menu item
167  */
168 - (void)popUp:(NSPopUpButton *)inPopUp shouldShowCustom:(BOOL)showCustom
170         NSMenuItem      *lastItem = [inPopUp lastItem];
171         BOOL            customIsShowing = (lastItem && (![lastItem representedObject] &&
172                                                                                                 [[lastItem title] isEqualToString:CUSTOM_TITLE]));
173         if (showCustom && !customIsShowing) {
174                 //Add 'custom' then select it
175                 [[inPopUp menu] addItem:[NSMenuItem separatorItem]];
176                 [[inPopUp menu] addItemWithTitle:CUSTOM_TITLE
177                                                                   target:nil
178                                                                   action:nil
179                                                    keyEquivalent:@""];
180                 [inPopUp selectItem:[inPopUp lastItem]];
182         } else if (!showCustom && customIsShowing) {
183                 //If it currently has a 'custom' item listed, remove it and the separator above it
184                 [inPopUp removeItemAtIndex:([inPopUp numberOfItems]-1)];
185                 [inPopUp removeItemAtIndex:([inPopUp numberOfItems]-1)];
186         }
190  * @brief Update our soundset menu if a new sound set is instaled
191  */
192 - (void)xtrasChanged:(NSNotification *)notification
194         if (!notification || [[notification object] caseInsensitiveCompare:@"AdiumSoundset"] == NSOrderedSame) {                
195                 //Build the soundset menu
196                 [popUp_soundSet setMenu:[self _soundSetMenu]];          
197         }
200 #pragma mark Event presets
203  * @brief Buld and return the event presets menu
205  * The menu will have built in presets, a divider, user-set presets, a divider, and then the preset management item(s)
206  */
207 - (NSMenu *)eventPresetsMenu
209         NSMenu                  *eventPresetsMenu = [[NSMenu allocWithZone:[NSMenu zone]] init];
210         NSEnumerator    *enumerator;
211         NSDictionary    *eventPreset;
212         NSMenuItem              *menuItem;
213         
214         //Built in event presets
215         enumerator = [[plugin builtInEventPresetsArray] objectEnumerator];
216         while ((eventPreset = [enumerator nextObject])) {
217                 NSString                *name = [eventPreset objectForKey:@"Name"];
218                 
219                 //Add a menu item for the set
220                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[self _localizedTitle:name]
221                                                                                                                                                  target:self
222                                                                                                                                                  action:@selector(selectEventPreset:)
223                                                                                                                                   keyEquivalent:@""] autorelease];
224                 [menuItem setRepresentedObject:eventPreset];
225                 [eventPresetsMenu addItem:menuItem];
226         }
227         
228         NSArray *storedEventPresetsArray = [plugin storedEventPresetsArray];
229         
230         if ([storedEventPresetsArray count]) {
231                 [eventPresetsMenu addItem:[NSMenuItem separatorItem]];
232                 
233                 enumerator = [storedEventPresetsArray objectEnumerator];
234                 while ((eventPreset = [enumerator nextObject])) {
235                         NSString                *name = [eventPreset objectForKey:@"Name"];
236                         
237                         //Add a menu item for the set
238                         menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
239                                                                                                                                                          target:self
240                                                                                                                                                          action:@selector(selectEventPreset:)
241                                                                                                                                           keyEquivalent:@""] autorelease];
242                         [menuItem setRepresentedObject:eventPreset];
243                         [eventPresetsMenu addItem:menuItem];
244                 }
245         }
246         
247         //Edit Presets
248         [eventPresetsMenu addItem:[NSMenuItem separatorItem]];
250         menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Add New Preset",nil) stringByAppendingEllipsis]
251                                                                                                                                          target:self
252                                                                                                                                          action:@selector(addNewPreset:)
253                                                                                                                           keyEquivalent:@""] autorelease];
254         [eventPresetsMenu addItem:menuItem];
255         
256         menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Edit Presets",nil) stringByAppendingEllipsis]
257                                                                                                                                          target:self
258                                                                                                                                          action:@selector(editPresets:)
259                                                                                                                           keyEquivalent:@""] autorelease];
260         [eventPresetsMenu addItem:menuItem];
261                 
262         return [eventPresetsMenu autorelease];
265 - (void)selectActiveEventInPopUp
267         NSString        *activeEventSetName = [[adium preferenceController] preferenceForKey:KEY_ACTIVE_EVENT_SET
268                                                                                                                                                            group:PREF_GROUP_EVENT_PRESETS];
270         //First try to set the localized version
271         [popUp_eventPreset selectItemWithTitle:[self _localizedTitle:activeEventSetName]];
272         //If that fails, look for one exactly matching
273         if (![popUp_eventPreset selectedItem]) [popUp_eventPreset selectItemWithTitle:activeEventSetName];
274         //And if that fails, select the first item (something went wrong, we should at least have a selection)
275         if (![popUp_eventPreset selectedItem]) [popUp_eventPreset selectItemAtIndex:0]; 
278 - (void)setAndConfigureEventPresetsMenu
280         [popUp_eventPreset setMenu:[self eventPresetsMenu]];
281         [self selectActiveEventInPopUp];
285  * @brief Selected an event preset
287  * Pass it to the plugin, which will perform necessary changes to our contact alerts
288  */
289 - (void)selectEventPreset:(id)sender
291         NSDictionary    *eventPreset = [sender representedObject];
292         [plugin setEventPreset:eventPreset];
294         [self updateSoundSetSelection];
298  * Add a new preset
300  * Called by the "Add New preset..." menu item.  Functions the same as duplicate from the preset management, duplicating
301  * the current event set with a new name.
302  */
303 - (void)addNewPreset:(id)sender
305         NSString        *defaultName;
306         NSString        *explanatoryText;
307         
308         defaultName = [NSString stringWithFormat:@"%@ %@",
309                 [self _localizedTitle:[[adium preferenceController] preferenceForKey:KEY_ACTIVE_EVENT_SET
310                                                                                                                                            group:PREF_GROUP_EVENT_PRESETS]],
311                 COPY_IN_PARENTHESIS];
312         explanatoryText = AILocalizedString(@"Enter a unique name for this new event set.",nil);
314         [ESPresetNameSheetController showPresetNameSheetWithDefaultName:defaultName
315                                                                                                         explanatoryText:explanatoryText
316                                                                                                                    onWindow:[[self view] window]
317                                                                                                         notifyingTarget:self
318                                                                                                                    userInfo:nil];
320         //Get our event presets menu back to its proper selection
321         [self selectActiveEventInPopUp];
325  * @brief Manage presets
327  * Called by the "Edit Presets..." menu item
328  */
329 - (void)editPresets:(id)sender
331         [ESPresetManagementController managePresets:[plugin storedEventPresetsArray]
332                                                                          namedByKey:@"Name"
333                                                                            onWindow:[[self view] window]
334                                                                    withDelegate:self];
336         //Get our event presets menu back to its proper selection
337         [self selectActiveEventInPopUp];
340 - (BOOL)allowDeleteOfPreset:(NSDictionary *)preset
342         NSString        *name = [preset objectForKey:@"Name"];
343         NSString        *localizedTitle;
344         
345         localizedTitle = [self _localizedTitle:[[adium preferenceController] preferenceForKey:KEY_ACTIVE_EVENT_SET
346                                                                                                                                                                         group:PREF_GROUP_EVENT_PRESETS]];
347         //Don't allow the active preset to be deleted
348         return (![localizedTitle isEqualToString:name]);
351 - (NSArray *)renamePreset:(NSDictionary *)preset toName:(NSString *)newName inPresets:(NSArray *)presets renamedPreset:(id *)renamedPreset
353         NSString                                *oldPresetName = [preset objectForKey:@"Name"];
354         NSMutableDictionary             *newPreset = [preset mutableCopy];
355         NSString                                *localizedCurrentName = [self _localizedTitle:[[adium preferenceController] preferenceForKey:KEY_ACTIVE_EVENT_SET
356                                                                                                                                                                                                                                    group:PREF_GROUP_EVENT_PRESETS]];
357         [newPreset setObject:newName
358                                   forKey:@"Name"];
360         //Mark the newly created (but still functionally identical) event set as active if the old one was active
361         if ([localizedCurrentName isEqualToString:oldPresetName]) {
362                 [[adium preferenceController] setPreference:newName
363                                                                                          forKey:KEY_ACTIVE_EVENT_SET
364                                                                                           group:PREF_GROUP_EVENT_PRESETS];
365         }
366         
367         //Remove the original one from the array, and add the newly-renamed one
368         [plugin deleteEventPreset:preset];
369         [plugin saveEventPreset:newPreset];
370         
371         if (renamedPreset) *renamedPreset = newPreset;
373         //Return an updated presets array
374         return [plugin storedEventPresetsArray];
377 - (NSArray *)duplicatePreset:(NSDictionary *)preset inPresets:(NSArray *)presets createdDuplicate:(id *)duplicatePreset
379         NSMutableDictionary     *newEventPreset = [preset mutableCopy];
380         NSString                        *newName = [NSString stringWithFormat:@"%@ %@", [preset objectForKey:@"Name"], COPY_IN_PARENTHESIS];
381         [newEventPreset setObject:newName
382                                            forKey:@"Name"];
383         
384         //Remove the original preset's order index
385         [newEventPreset removeObjectForKey:@"OrderIndex"];
386         
387         //Now save the new preset
388         [plugin saveEventPreset:newEventPreset];
390         //Return the created duplicate by reference
391         if (duplicatePreset != NULL) *duplicatePreset = [[newEventPreset retain] autorelease];
392         
393         //Cleanup
394         [newEventPreset release];
396         //Return an updated presets array
397         return [plugin storedEventPresetsArray];
400 - (NSArray *)deletePreset:(NSDictionary *)preset inPresets:(NSArray *)presets
402         //Remove the preset
403         [plugin deleteEventPreset:preset];
404         
405         //Return an updated presets array
406         return [plugin storedEventPresetsArray];
409 - (NSArray *)movePreset:(NSDictionary *)preset toIndex:(int)index inPresets:(NSArray *)presets presetAfterMove:(id *)presetAfterMove
411         NSMutableDictionary     *newEventPreset = [preset mutableCopy];
412         float newOrderIndex;
413         if (index == 0) {               
414                 newOrderIndex = [[[presets objectAtIndex:0] objectForKey:@"OrderIndex"] floatValue] / 2.0;
416         } else if (index < [presets count]) {
417                 float above = [[[presets objectAtIndex:index-1] objectForKey:@"OrderIndex"] floatValue];
418                 float below = [[[presets objectAtIndex:index] objectForKey:@"OrderIndex"] floatValue];
419                 newOrderIndex = ((above + below) / 2.0);
421         } else {
422                 newOrderIndex = [plugin nextOrderIndex];
423         }
424         
425         [newEventPreset setObject:[NSNumber numberWithFloat:newOrderIndex]
426                                            forKey:@"OrderIndex"];
427                          
428         //Now save the new preset
429         [plugin saveEventPreset:newEventPreset];
430         if (presetAfterMove != NULL) *presetAfterMove = [[newEventPreset retain] autorelease];
431         [newEventPreset release];
433         //Return an updated presets array
434         return [plugin storedEventPresetsArray];
437 #pragma mark Contact alerts changed by user
438 - (void)contactAlertsViewController:(ESContactAlertsViewController *)inController
439                                            updatedAlert:(NSDictionary *)newAlert
440                                                    oldAlert:(NSDictionary *)oldAlert
441 {       
442         [self contactAlertsDidChangeForActionID:[newAlert objectForKey:KEY_ACTION_ID]];
445 - (void)contactAlertsViewController:(ESContactAlertsViewController *)inController
446                                            deletedAlert:(NSDictionary *)deletedAlert
448         [self contactAlertsDidChangeForActionID:[deletedAlert objectForKey:KEY_ACTION_ID]];     
452  * @brief Contact alerts were changed by the user
453  */
454 - (void)contactAlertsDidChangeForActionID:(NSString *)actionID
456         if (!actionID ||
457                 [actionID isEqualToString:SOUND_ALERT_IDENTIFIER]) {
458                 
459                 NSArray                 *alertsArray = [[adium contactAlertsController] alertsForListObject:nil
460                                                                                                                                                                 withEventID:nil
461                                                                                                                                                                    actionID:SOUND_ALERT_IDENTIFIER];
462                 NSMenuItem              *soundMenuItem;
463                 
464                 if (![alertsArray count]) {
465                         //We can select "None" if there are no sounds
466                         soundMenuItem = (NSMenuItem *)[popUp_soundSet itemWithTitle:@"None"];
468                 } else {
469                         /* Otherwise, check to see if we remain in our proper soundset.
470                          * Note that this won't detect if we return to a soundset, but that'd be an expensive search.
471                          */
472                         soundMenuItem = (NSMenuItem *)[popUp_soundSet selectedItem];
474                         AISoundSet              *soundSet = [soundMenuItem representedObject];
475                         NSEnumerator    *enumerator;
476                         NSString                *key;
477                         NSDictionary    *sounds = [soundSet sounds];
479                         if ([alertsArray count] && ![sounds count]) {
480                                 //If we have one or more sound alerts and there are no sounds in this sound set ("None" sound set), there's no matching soundSetMenuitem.
481                                 soundMenuItem = nil;
483                         } else {
484                                 //First, check to see if any sounds which are present within this sound set have been changed
485                                 enumerator = [sounds keyEnumerator];
486                                 while ((key = [enumerator nextObject])) {
487                                         NSDictionary *soundAlert = [ESGlobalEventsPreferencesPlugin soundAlertForKey:key
488                                                                                                                                                                         inSoundsDict:sounds];
489                                         if (![alertsArray containsObject:soundAlert]) {
490                                                 soundMenuItem = nil;
491                                                 break;
492                                         }
493                                 }
494                                 
495                                 //Next, see if any sounds not present within this sound set have been added
496                                 if (soundMenuItem) {
497                                         NSDictionary    *alertDict;
498                                         enumerator = [alertsArray objectEnumerator];
499                                         while ((alertDict = [enumerator nextObject])) {
500                                                 if ([[alertDict objectForKey:KEY_ACTION_ID] isEqualToString:SOUND_ALERT_IDENTIFIER]) {
501                                                         NSString *englishEvent = [[[AIObject sharedAdiumInstance] contactAlertsController] eventIDForEnglishDisplayName:key];
502                                                         /*
503                                                          * If the sounds dictionary has no action for this event, or it has one but
504                                                          * it is for a different sound than specified, the sound set has been changed
505                                                          */
506                                                         if (![sounds objectForKey:englishEvent] ||
507                                                                 ![[[alertDict objectForKey:KEY_ACTION_DETAILS] objectForKey:KEY_ALERT_SOUND_PATH] isEqualToString:[sounds objectForKey:englishEvent]]) {
508                                                                 soundMenuItem = nil;
509                                                                 break;
510                                                         }
511                                                 }
512                                         }
513                                 }
515                         }
516                 }
518                 [self selectSoundSet:([soundMenuItem representedObject] ? soundMenuItem : nil)];
520         } else {
521                 [self saveCurrentEventPreset];
522         }
525 #pragma mark Sound sets
527  * @brief Called when an item in the sound set popUp is selected.
529  * Also called after the user changes sounds manually, by -[ESGlobalEventsPreferences contactAlertsDidChangeForActionID].
530  */
531 - (IBAction)selectSoundSet:(id)sender
533         //Apply the sound set so its events are in the current alerts.
534         if (sender) {
535                 [plugin applySoundSet:[sender representedObject]];
536         }
538         /* Update the selection, which will select Custom as appropriate.  This must be done before saving the event
539          * preset so the menu is on the correct sound set to save.
540          */
541         [self updateSoundSetSelectionForSoundSet:[sender representedObject]];
543         /* Save the preset which is now updated to have the appropriate sounds; 
544          * in saving, the name of the soundset, or @"", will also be saved.
545          */
546         [self saveCurrentEventPreset];
550  * @brief Revert the event set to how it was before the last attempted operation
551  */
552 - (void)revertToSavedEventSet
554         NSDictionary            *eventPreset;
556         [self selectActiveEventInPopUp];
557         eventPreset = [[popUp_eventPreset selectedItem] representedObject];
559         [plugin setEventPreset:eventPreset];
560         
561         //Ensure the correct sound set is selected
562         [self updateSoundSetSelection];
566  * @brief Build and return the event set as it should be saved
567  */
568 - (NSMutableDictionary *)currentEventSetForSaving
570         NSDictionary            *eventPreset = [[popUp_eventPreset selectedItem] representedObject];
571         NSMutableDictionary     *currentEventSetForSaving = [[eventPreset mutableCopy] autorelease];
572         
573         //Set the sound set, which is just stored here for ease of preference pane display
574         NSString                        *soundSetName = [[[popUp_soundSet selectedItem] representedObject] name];
575         if (soundSetName) {
576                 [currentEventSetForSaving setObject:soundSetName
577                                                                          forKey:KEY_EVENT_SOUND_SET];
578         } else {
579                 [currentEventSetForSaving removeObjectForKey:KEY_EVENT_SOUND_SET];
580         }
581         
582         //Get and store the alerts array
583         NSArray                         *alertsArray = [[adium contactAlertsController] alertsForListObject:nil
584                                                                                                                                                                 withEventID:nil
585                                                                                                                                                                    actionID:nil];
586         [currentEventSetForSaving setObject:alertsArray forKey:@"Events"];
588         //Ensure this set doesn't claim to be built in.
589         [currentEventSetForSaving removeObjectForKey:@"Built In"];
590         
591         return currentEventSetForSaving;
594 #pragma mark Volume
595 //New value selected on the volume slider or chosen by clicking a volume icon
596 - (IBAction)selectVolume:(id)sender
598     float                       volume, oldVolume;
599         
600         if (sender == slider_volume) {
601                 volume = [slider_volume floatValue];
602         } else if (sender == button_maxvolume) {
603                 volume = [slider_volume maxValue];
604                 [slider_volume setDoubleValue:volume];
605         } else if (sender == button_minvolume) {
606                 volume = [slider_volume minValue];
607                 [slider_volume setDoubleValue:volume];
608         } else {
609                 volume = 0;
610         }
611         
612         NSNumber *oldVolumeValue = [[adium preferenceController] preferenceForKey:KEY_SOUND_CUSTOM_VOLUME_LEVEL
613                                                                                                                                                 group:PREF_GROUP_SOUNDS];
614         oldVolume = (oldVolumeValue ? [oldVolumeValue floatValue] : -1.0);
615         
616     //Volume
617     if (volume != oldVolume) {
618         [[adium preferenceController] setPreference:[NSNumber numberWithFloat:volume]
619                                              forKey:KEY_SOUND_CUSTOM_VOLUME_LEVEL
620                                               group:PREF_GROUP_SOUNDS];
621                 
622                 //Play a sample sound
623         [[adium soundController] playSoundAtPath:VOLUME_SOUND_PATH];
624     }
627 #pragma mark Preset saving
630  * @brief Save the current event preset
632  * Called after each event change to immediately update the current preset.
633  * If a built-in preset is currently selected, this method will prompt for a new name before saving.
634  */
635 - (void)saveCurrentEventPreset
637         NSDictionary            *eventPreset = [[popUp_eventPreset selectedItem] representedObject];
639         if ([eventPreset objectForKey:@"Built In"] && [[eventPreset objectForKey:@"Built In"] boolValue]) {
640                 /* Perform after a delay so that if we got here as a result of a sheet-based add or edit of an event
641                  * the sheet will close before we try to open a new one. */
642                 [self performSelector:@selector(showPresetCopySheet:)
643                                    withObject:[self _localizedTitle:[eventPreset objectForKey:@"Name"]]
644                                    afterDelay:0];
645         } else {        
646                 //Now save the current settings
647                 [plugin saveEventPreset:[self currentEventSetForSaving]];
648         }               
652  * @brief Show the sheet for naming the preset created by an attempt to modify a built-in set
654  * @param originalPresetName The name of the original set, used as a base for the new name.
655  */
656 - (void)showPresetCopySheet:(NSString *)originalPresetName
658         NSString        *defaultName;
659         NSString        *explanatoryText;
660         
661         defaultName = [NSString stringWithFormat:@"%@ %@", originalPresetName, COPY_IN_PARENTHESIS];
662         explanatoryText = AILocalizedString(@"You are editing a default event set.  Please enter a unique name for your modified set.",nil);
663         
664         [ESPresetNameSheetController showPresetNameSheetWithDefaultName:defaultName
665                                                                                                         explanatoryText:explanatoryText
666                                                                                                                    onWindow:[[self view] window]
667                                                                                                         notifyingTarget:self
668                                                                                                                    userInfo:nil];
671 - (BOOL)presetNameSheetController:(ESPresetNameSheetController *)controller
672                           shouldAcceptNewName:(NSString *)newName
673                                                  userInfo:(id)userInfo
675         return (![[[plugin builtInEventPresets] allKeys] containsObject:newName] &&
676                    ![[[plugin storedEventPresets] allKeys] containsObject:newName]);
678         
679 - (void)presetNameSheetControllerDidEnd:(ESPresetNameSheetController *)controller 
680                                                          returnCode:(ESPresetNameSheetReturnCode)returnCode
681                                                                 newName:(NSString *)newName
682                                                            userInfo:(id)userInfo
684         switch (returnCode) {
685                 case ESPresetNameSheetOkayReturn:
686                 {
687                         //XXX error if overwriting existing set?
688                         NSMutableDictionary     *newEventPreset = [self currentEventSetForSaving];
689                         [newEventPreset setObject:newName
690                                                            forKey:@"Name"];
691                         
692                         //Now save the current settings
693                         [plugin saveEventPreset:newEventPreset];
694                         
695                         //Presets menu
696                         [[adium preferenceController] setPreference:newName
697                                                                                                  forKey:KEY_ACTIVE_EVENT_SET
698                                                                                                   group:PREF_GROUP_EVENT_PRESETS];
699                         [popUp_eventPreset setMenu:[self eventPresetsMenu]];
700                         [popUp_eventPreset selectItemWithTitle:newName];
701                         
702                         break;
703                 }
704                 case ESPresetNameSheetCancelReturn:
705                 {
706                         [self revertToSavedEventSet];
707                         break;
708                 }
709         }
711                 
713  * @brief Called when the OK button on the preset copy sheet is pressed
715  * Save the current event set under the name specified by [textField_name stringValue].
716  * Set the name of the active event set to this new name, and ensure our menu is up to date.
718  * Also, close the sheet.
719  */
720 - (IBAction)selectedNameForPresetCopy:(id)sender
722         
725 - (void)updateSoundSetSelectionForSoundSet:(AISoundSet *)soundSet
727         if (soundSet) {
728                 [popUp_soundSet selectItemWithRepresentedObject:soundSet];
729                 
730                 [self popUp:popUp_soundSet shouldShowCustom:NO];
731                 
732         } else {
733                 [self popUp:popUp_soundSet shouldShowCustom:YES];
734         }
737 - (void)updateSoundSetSelection
739         NSEnumerator    *enumerator = [[[adium soundController] soundSets] objectEnumerator];
740     AISoundSet          *soundSet;
741         NSString                *name;
743         name = [[[popUp_eventPreset selectedItem] representedObject] objectForKey:KEY_EVENT_SOUND_SET];
744         name = [[name lastPathComponent] stringByDeletingPathExtension];
746     while ((soundSet = [enumerator nextObject])) {
747                 if ([[soundSet name] isEqualToString:name]) break;
748         }
750         [self updateSoundSetSelectionForSoundSet:soundSet];
753 #define NONE AILocalizedString(@"None",nil)
755  * @brief Build and return a menu of sound set choices
757  * The menu items have an action of -[self selectSoundSet:].
758  */
759 - (NSMenu *)_soundSetMenu
761     NSMenu                      *soundSetMenu = [[NSMenu alloc] init];
762     NSEnumerator        *enumerator = [[[adium soundController] soundSets] objectEnumerator];
763         NSMutableArray  *menuItemArray = [NSMutableArray array];
764     AISoundSet          *soundSet;
765     NSMenuItem          *menuItem, *noneMenuItem = nil;
767     while ((soundSet = [enumerator nextObject])) {
768                 menuItem = [[NSMenuItem alloc] initWithTitle:[self _localizedTitle:[soundSet name]]
769                                                                                           target:self
770                                                                                           action:@selector(selectSoundSet:)
771                                                                            keyEquivalent:@""
772                                                                    representedObject:soundSet];
773                 
774                 if ([[menuItem title] isEqualToString:NONE]) {
775                         noneMenuItem = menuItem;
777                 } else {
778                         [menuItemArray addObject:menuItem];
779                         [menuItem release];
780                 }
781         }
782         
783         [menuItemArray sortUsingSelector:@selector(titleCompare:)];
784         
785         enumerator = [menuItemArray objectEnumerator];
786         while ((menuItem = [enumerator nextObject])) {
787                 [soundSetMenu addItem:menuItem];
788         }
790         if (noneMenuItem) {
791                 [soundSetMenu addItem:[NSMenuItem separatorItem]];
792                 [soundSetMenu addItem:noneMenuItem];
793                 [noneMenuItem release];
794         }
795         
796     return [soundSetMenu autorelease];
799 #pragma mark Common menu methods
801  * @brief Localized a menu item title for global events preferences
803  * @result The equivalent localized title if available; otherwise, the passed English title
804  */
805 - (NSString *)_localizedTitle:(NSString *)englishTitle
807         NSString        *localizedTitle = nil;
808         
809         if ([englishTitle isEqualToString:@"None"])
810                 localizedTitle = NONE;
811         else if ([englishTitle isEqualToString:@"Default Notifications"])
812                 localizedTitle = AILocalizedString(@"Default Notifications",nil);
813         else if ([englishTitle isEqualToString:@"Visual Notifications"])
814                 localizedTitle = AILocalizedString(@"Visual Notifications",nil);
815         else if ([englishTitle isEqualToString:@"Audio Notifications"])
816                 localizedTitle = AILocalizedString(@"Audio Notifications",nil);
818         return (localizedTitle ? localizedTitle : englishTitle);
821 @end