William Bowling is sharing code with you

Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.

Don't show this again

wbowling / adium (fork of adium / adium)

Fork of Adium for patches/improvements

Clone this repository (size: 338.7 MB): HTTPS / SSH
hg clone https://bitbucket.org/wbowling/adium
hg clone ssh://hg@bitbucket.org/wbowling/adium

adium / Source / ESGlobalEventsPreferences.m

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
/* 
 * Adium is the legal property of its developers, whose names are listed in the copyright file included
 * with this source distribution.
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#import "AISoundController.h"
#import "Adium/ESContactAlertsViewController.h"
#import <Adium/AIContactAlertsControllerProtocol.h>
#import "ESGlobalEventsPreferences.h"
#import "ESGlobalEventsPreferencesPlugin.h"
#import <Adium/ESPresetManagementController.h>
#import <Adium/ESPresetNameSheetController.h>
#import <Adium/AISoundSet.h>
#import <AIUtilities/AIMenuAdditions.h>
#import <AIUtilities/AIPopUpButtonAdditions.h>
#import <AIUtilities/AIStringAdditions.h>
#import <AIUtilities/AIApplicationAdditions.h>
#import <AIUtilities/AIVariableHeightOutlineView.h>
#import <AIUtilities/AIVerticallyCenteredTextCell.h>
#import <AIUtilities/AIAttributedStringAdditions.h>
#import <AIUtilities/AIArrayAdditions.h>
#import <AIUtilities/AIImageAdditions.h>
#import <AIUtilities/AIImageTextCell.h>

#define PREF_GROUP_EVENT_PRESETS        @"Event Presets"
#define CUSTOM_TITLE                            AILocalizedString(@"Custom",nil)
#define COPY_IN_PARENTHESIS                     AILocalizedString(@"(Copy)","Copy, in parenthesis, as a noun indicating that the preceding item is a duplicate")

#define VOLUME_SOUND_PATH   [NSString pathWithComponents:[NSArray arrayWithObjects: \
        @"/", @"System", @"Library", @"LoginPlugins", \
        [@"BezelServices" stringByAppendingPathExtension:@"loginPlugin"], \
        @"Contents", @"Resources", \
        [@"volume" stringByAppendingPathExtension:@"aiff"], \
        nil]]

@interface ESGlobalEventsPreferences ()
- (void)popUp:(NSPopUpButton *)inPopUp shouldShowCustom:(BOOL)showCustom;
- (void)xtrasChanged:(NSNotification *)notification;
- (void)contactAlertsDidChangeForActionID:(NSString *)actionID;
- (NSMenu *)eventPresetsMenu;
- (IBAction)selectSoundSet:(id)sender;
- (NSMenu *)_soundSetMenu;
- (NSString *)_localizedTitle:(NSString *)englishTitle;
- (void)saveCurrentEventPreset;
- (void)setAndConfigureEventPresetsMenu;
- (void)updateSoundSetSelection;
- (void)updateSoundSetSelectionForSoundSet:(AISoundSet *)soundSet;
@end

@implementation ESGlobalEventsPreferences
- (NSString *)paneIdentifier
{
        return @"Events";
}
- (NSString *)paneName{ 
    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.");
}
/*!
 * @brief Nib name
 */
- (NSString *)nibName{
    return @"GlobalEventsPreferences";
}
- (NSImage *)paneIcon
{
        return [NSImage imageNamed:@"pref-events" forClass:[self class]];
}

- (BOOL)resizableHorizontally
{
        return YES;
}

/*!
 * @brief Configure the preference view
 */
- (void)viewDidLoad
{
        //Configure our global contact alerts view controller
        [contactAlertsViewController setConfigureForGlobal:YES];
        [contactAlertsViewController setDelegate:self];
        [contactAlertsViewController setShowEventsInEditSheet:NO];
        
        //Observe for installation of new sound sets and set up the sound set menu
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                                   selector:@selector(xtrasChanged:)
                                                                           name:AIXtrasDidChangeNotification
                                                                         object:nil];

        //This will build the sound set menu
        [self xtrasChanged:nil];        

        //Presets menu
        [self setAndConfigureEventPresetsMenu];

        //And event presets to update our presets menu
        [adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_EVENT_PRESETS];

        //Ensure the correct sound set is selected
        [self updateSoundSetSelection];
        
        //Volume
        [slider_volume setDoubleValue:[[adium.preferenceController preferenceForKey:KEY_SOUND_CUSTOM_VOLUME_LEVEL
                                                                                                                                                   group:PREF_GROUP_SOUNDS] doubleValue]];      
}

- (void)localizePane
{
        [[button_minvolume cell] accessibilitySetOverrideValue:AILocalizedString(@"Set minimum volume", "Accessibility label for button to set to the minimum sound volume")
                                                                           forAttribute:NSAccessibilityDescriptionAttribute];
        [[button_maxvolume cell] accessibilitySetOverrideValue:AILocalizedString(@"Set maximum volume", "Accessibility label for button to set to the maximum sound volume")
                                                                           forAttribute:NSAccessibilityDescriptionAttribute];
        [[slider_volume cell] accessibilitySetOverrideValue:AILocalizedString(@"Volume", "Accessibility label for the sound volume slider")
                                                                                   forAttribute:NSAccessibilityDescriptionAttribute];
         
        [label_eventPreset setLocalizedString:AILocalizedString(@"Event preset:",nil)];
        [label_soundSet setLocalizedString:AILocalizedString(@"Sound set:",nil)];
}

/*!
 * @brief Preference view is closing
 */
- (void)viewWillClose
{
        [contactAlertsViewController viewWillClose];
        [contactAlertsViewController release]; contactAlertsViewController = nil;

        [adium.preferenceController unregisterPreferenceObserver:self];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

/*!
 * @brief PREF_GROUP_CONTACT_ALERTS changed; update our summary data
 */
- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
                                                        object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
{
        if ([group isEqualToString:PREF_GROUP_EVENT_PRESETS]) {
                if (!key || [key isEqualToString:@"Event Presets"]) {
                        //Update when the available event presets change
                        [self setAndConfigureEventPresetsMenu];
                }
        }
}

/*!
 * @brief Set if a popup should have a "Custom" menu item
 */
- (void)popUp:(NSPopUpButton *)inPopUp shouldShowCustom:(BOOL)showCustom
{
        NSMenuItem      *lastItem = [inPopUp lastItem];
        BOOL            customIsShowing = (lastItem && (![lastItem representedObject] &&
                                                                                                [[lastItem title] isEqualToString:CUSTOM_TITLE]));
        if (showCustom && !customIsShowing) {
                //Add 'custom' then select it
                [[inPopUp menu] addItem:[NSMenuItem separatorItem]];
                [[inPopUp menu] addItemWithTitle:CUSTOM_TITLE
                                                                  target:nil
                                                                  action:nil
                                                   keyEquivalent:@""];
                [inPopUp selectItem:[inPopUp lastItem]];

        } else if (!showCustom && customIsShowing) {
                //If it currently has a 'custom' item listed, remove it and the separator above it
                [inPopUp removeItemAtIndex:([inPopUp numberOfItems]-1)];
                [inPopUp removeItemAtIndex:([inPopUp numberOfItems]-1)];
        }
}

/*!
 * @brief Update our soundset menu if a new sound set is instaled
 */
- (void)xtrasChanged:(NSNotification *)notification
{
        if (!notification || [[notification object] caseInsensitiveCompare:@"AdiumSoundset"] == NSOrderedSame) {                
                //Build the soundset menu
                [popUp_soundSet setMenu:[self _soundSetMenu]];          
        }
}

#pragma mark Event presets

/*!
 * @brief Buld and return the event presets menu
 *
 * The menu will have built in presets, a divider, user-set presets, a divider, and then the preset management item(s)
 */
- (NSMenu *)eventPresetsMenu
{
        NSMenu                  *eventPresetsMenu = [[NSMenu allocWithZone:[NSMenu zone]] init];
        NSEnumerator    *enumerator;
        NSDictionary    *eventPreset;
        NSMenuItem              *menuItem;
        
        //Built in event presets
        enumerator = [[plugin builtInEventPresetsArray] objectEnumerator];
        while ((eventPreset = [enumerator nextObject])) {
                NSString                *name = [eventPreset objectForKey:@"Name"];
                
                //Add a menu item for the set
                menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[self _localizedTitle:name]
                                                                                                                                                 target:self
                                                                                                                                                 action:@selector(selectEventPreset:)
                                                                                                                                  keyEquivalent:@""] autorelease];
                [menuItem setRepresentedObject:eventPreset];
                [eventPresetsMenu addItem:menuItem];
        }
        
        NSArray *storedEventPresetsArray = [plugin storedEventPresetsArray];
        
        if ([storedEventPresetsArray count]) {
                [eventPresetsMenu addItem:[NSMenuItem separatorItem]];
                
                for (eventPreset in storedEventPresetsArray) {
                        NSString                *name = [eventPreset objectForKey:@"Name"];
                        
                        //Add a menu item for the set
                        menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
                                                                                                                                                         target:self
                                                                                                                                                         action:@selector(selectEventPreset:)
                                                                                                                                          keyEquivalent:@""] autorelease];
                        [menuItem setRepresentedObject:eventPreset];
                        [eventPresetsMenu addItem:menuItem];
                }
        }
        
        //Edit Presets
        [eventPresetsMenu addItem:[NSMenuItem separatorItem]];

        menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Add New Preset",nil) stringByAppendingEllipsis]
                                                                                                                                         target:self
                                                                                                                                         action:@selector(addNewPreset:)
                                                                                                                          keyEquivalent:@""] autorelease];
        [eventPresetsMenu addItem:menuItem];
        
        menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Edit Presets",nil) stringByAppendingEllipsis]
                                                                                                                                         target:self
                                                                                                                                         action:@selector(editPresets:)
                                                                                                                          keyEquivalent:@""] autorelease];
        [eventPresetsMenu addItem:menuItem];
                
        return [eventPresetsMenu autorelease];
}

- (void)selectActiveEventInPopUp
{
        NSString        *activeEventSetName = [adium.preferenceController preferenceForKey:KEY_ACTIVE_EVENT_SET
                                                                                                                                                           group:PREF_GROUP_EVENT_PRESETS];

        //First try to set the localized version
        [popUp_eventPreset selectItemWithTitle:[self _localizedTitle:activeEventSetName]];
        //If that fails, look for one exactly matching
        if (![popUp_eventPreset selectedItem]) [popUp_eventPreset selectItemWithTitle:activeEventSetName];
        //And if that fails, select the first item (something went wrong, we should at least have a selection)
        if (![popUp_eventPreset selectedItem]) [popUp_eventPreset selectItemAtIndex:0]; 
}

- (void)setAndConfigureEventPresetsMenu
{
        [popUp_eventPreset setMenu:[self eventPresetsMenu]];
        [self selectActiveEventInPopUp];
}

/*!
 * @brief Selected an event preset
 *
 * Pass it to the plugin, which will perform necessary changes to our contact alerts
 */
- (void)selectEventPreset:(id)sender
{
        NSDictionary    *eventPreset = [sender representedObject];
        [plugin setEventPreset:eventPreset];

        [self updateSoundSetSelection];
}

/*
 * Add a new preset
 *
 * Called by the "Add New preset..." menu item.  Functions the same as duplicate from the preset management, duplicating
 * the current event set with a new name.
 */
- (void)addNewPreset:(id)sender
{
        NSString        *defaultName;
        NSString        *explanatoryText;
        
        defaultName = [NSString stringWithFormat:@"%@ %@",
                [self _localizedTitle:[adium.preferenceController preferenceForKey:KEY_ACTIVE_EVENT_SET
                                                                                                                                           group:PREF_GROUP_EVENT_PRESETS]],
                COPY_IN_PARENTHESIS];
        explanatoryText = AILocalizedString(@"Enter a unique name for this new event set.",nil);

        [ESPresetNameSheetController showPresetNameSheetWithDefaultName:defaultName
                                                                                                        explanatoryText:explanatoryText
                                                                                                                   onWindow:[[self view] window]
                                                                                                        notifyingTarget:self
                                                                                                                   userInfo:nil];

        //Get our event presets menu back to its proper selection
        [self selectActiveEventInPopUp];
}

/*!
 * @brief Manage presets
 *
 * Called by the "Edit Presets..." menu item
 */
- (void)editPresets:(id)sender
{
        [ESPresetManagementController managePresets:[plugin storedEventPresetsArray]
                                                                         namedByKey:@"Name"
                                                                           onWindow:[[self view] window]
                                                                   withDelegate:self];

        //Get our event presets menu back to its proper selection
        [self selectActiveEventInPopUp];
}

- (BOOL)allowDeleteOfPreset:(NSDictionary *)preset
{
        NSString        *name = [preset objectForKey:@"Name"];
        NSString        *localizedTitle;
        
        localizedTitle = [self _localizedTitle:[adium.preferenceController preferenceForKey:KEY_ACTIVE_EVENT_SET
                                                                                                                                                                        group:PREF_GROUP_EVENT_PRESETS]];
        //Don't allow the active preset to be deleted
        return (![localizedTitle isEqualToString:name]);
}

- (NSArray *)renamePreset:(NSDictionary *)preset toName:(NSString *)newName inPresets:(NSArray *)presets renamedPreset:(id *)renamedPreset
{
        NSString                                *oldPresetName = [preset objectForKey:@"Name"];
        NSMutableDictionary             *newPreset = [preset mutableCopy];
        NSString                                *localizedCurrentName = [self _localizedTitle:[adium.preferenceController preferenceForKey:KEY_ACTIVE_EVENT_SET
                                                                                                                                                                                                                                   group:PREF_GROUP_EVENT_PRESETS]];
        [newPreset setObject:newName
                                  forKey:@"Name"];

        //Mark the newly created (but still functionally identical) event set as active if the old one was active
        if ([localizedCurrentName isEqualToString:oldPresetName]) {
                [adium.preferenceController setPreference:newName
                                                                                         forKey:KEY_ACTIVE_EVENT_SET
                                                                                          group:PREF_GROUP_EVENT_PRESETS];
        }
        
        //Remove the original one from the array, and add the newly-renamed one
        [plugin deleteEventPreset:preset];
        [plugin saveEventPreset:newPreset];
        
        if (renamedPreset) *renamedPreset = newPreset;

        //Return an updated presets array
        return [plugin storedEventPresetsArray];
}

- (NSArray *)duplicatePreset:(NSDictionary *)preset inPresets:(NSArray *)presets createdDuplicate:(id *)duplicatePreset
{
        NSMutableDictionary     *newEventPreset = [preset mutableCopy];
        NSString                        *newName = [NSString stringWithFormat:@"%@ %@", [preset objectForKey:@"Name"], COPY_IN_PARENTHESIS];
        [newEventPreset setObject:newName
                                           forKey:@"Name"];
        
        //Remove the original preset's order index
        [newEventPreset removeObjectForKey:@"OrderIndex"];
        
        //Now save the new preset
        [plugin saveEventPreset:newEventPreset];

        //Return the created duplicate by reference
        if (duplicatePreset != NULL) *duplicatePreset = [[newEventPreset retain] autorelease];
        
        //Cleanup
        [newEventPreset release];

        //Return an updated presets array
        return [plugin storedEventPresetsArray];
}

- (NSArray *)deletePreset:(NSDictionary *)preset inPresets:(NSArray *)presets
{
        //Remove the preset
        [plugin deleteEventPreset:preset];
        
        //Return an updated presets array
        return [plugin storedEventPresetsArray];
}

- (NSArray *)movePreset:(NSDictionary *)preset toIndex:(NSUInteger)index inPresets:(NSArray *)presets presetAfterMove:(id *)presetAfterMove
{
        NSMutableDictionary     *newEventPreset = [preset mutableCopy];
        CGFloat newOrderIndex;
        if (index == 0) {               
                newOrderIndex = [[[presets objectAtIndex:0] objectForKey:@"OrderIndex"] doubleValue] / 2.0;

        } else if (index < [presets count]) {
                CGFloat above = [[[presets objectAtIndex:index-1] objectForKey:@"OrderIndex"] doubleValue];
                CGFloat below = [[[presets objectAtIndex:index] objectForKey:@"OrderIndex"] doubleValue];
                newOrderIndex = ((above + below) / 2.0);

        } else {
                newOrderIndex = [plugin nextOrderIndex];
        }
        
        [newEventPreset setObject:[NSNumber numberWithDouble:newOrderIndex]
                                           forKey:@"OrderIndex"];
                         
        //Now save the new preset
        [plugin saveEventPreset:newEventPreset];
        if (presetAfterMove != NULL) *presetAfterMove = [[newEventPreset retain] autorelease];
        [newEventPreset release];

        //Return an updated presets array
        return [plugin storedEventPresetsArray];
}

#pragma mark Contact alerts changed by user
- (void)contactAlertsViewController:(ESContactAlertsViewController *)inController
                                           updatedAlert:(NSDictionary *)newAlert
                                                   oldAlert:(NSDictionary *)oldAlert
{       
        [self contactAlertsDidChangeForActionID:[newAlert objectForKey:KEY_ACTION_ID]];
}

- (void)contactAlertsViewController:(ESContactAlertsViewController *)inController
                                           deletedAlert:(NSDictionary *)deletedAlert
{
        [self contactAlertsDidChangeForActionID:[deletedAlert objectForKey:KEY_ACTION_ID]];     
}

/*!
 * @brief Contact alerts were changed by the user
 */
- (void)contactAlertsDidChangeForActionID:(NSString *)actionID
{
        if (!actionID ||
                [actionID isEqualToString:SOUND_ALERT_IDENTIFIER]) {
                
                NSArray                 *alertsArray = [adium.contactAlertsController alertsForListObject:nil
                                                                                                                                                                withEventID:nil
                                                                                                                                                                   actionID:SOUND_ALERT_IDENTIFIER];
                NSMenuItem              *soundMenuItem;
                
                if (![alertsArray count]) {
                        //We can select "None" if there are no sounds
                        soundMenuItem = (NSMenuItem *)[popUp_soundSet itemWithTitle:@"None"];

                } else {
                        /* Otherwise, check to see if we remain in our proper soundset.
                         * Note that this won't detect if we return to a soundset, but that'd be an expensive search.
                         */
                        soundMenuItem = (NSMenuItem *)[popUp_soundSet selectedItem];

                        AISoundSet              *soundSet = [soundMenuItem representedObject];
                        NSEnumerator    *enumerator;
                        NSString                *key;
                        NSDictionary    *sounds = [soundSet sounds];

                        if ([alertsArray count] && ![sounds count]) {
                                //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.
                                soundMenuItem = nil;

                        } else {
                                //First, check to see if any sounds which are present within this sound set have been changed
                                enumerator = [sounds keyEnumerator];
                                while ((key = [enumerator nextObject])) {
                                        NSDictionary *soundAlert = [ESGlobalEventsPreferencesPlugin soundAlertForKey:key
                                                                                                                                                                        inSoundsDict:sounds];
                                        if (![alertsArray containsObject:soundAlert]) {
                                                soundMenuItem = nil;
                                                break;
                                        }
                                }
                                
                                //Next, see if any sounds not present within this sound set have been added
                                if (soundMenuItem) {
                                        NSDictionary    *alertDict;
                                        for (alertDict in alertsArray) {
                                                if ([[alertDict objectForKey:KEY_ACTION_ID] isEqualToString:SOUND_ALERT_IDENTIFIER]) {
                                                        NSString *englishEvent = [adium.contactAlertsController eventIDForEnglishDisplayName:key];
                                                        /*
                                                         * If the sounds dictionary has no action for this event, or it has one but
                                                         * it is for a different sound than specified, the sound set has been changed
                                                         */
                                                        if (![sounds objectForKey:englishEvent] ||
                                                                ![[[alertDict objectForKey:KEY_ACTION_DETAILS] objectForKey:KEY_ALERT_SOUND_PATH] isEqualToString:[sounds objectForKey:englishEvent]]) {
                                                                soundMenuItem = nil;
                                                                break;
                                                        }
                                                }
                                        }
                                }

                        }
                }

                [self selectSoundSet:([soundMenuItem representedObject] ? soundMenuItem : nil)];

        } else {
                [self saveCurrentEventPreset];
        }
}

#pragma mark Sound sets
/*!
 * @brief Called when an item in the sound set popUp is selected.
 *
 * Also called after the user changes sounds manually, by -[ESGlobalEventsPreferences contactAlertsDidChangeForActionID].
 */
- (IBAction)selectSoundSet:(id)sender
{
        //Apply the sound set so its events are in the current alerts.
        if (sender) {
                [plugin applySoundSet:[sender representedObject]];
        }

        /* Update the selection, which will select Custom as appropriate.  This must be done before saving the event
         * preset so the menu is on the correct sound set to save.
         */
        [self updateSoundSetSelectionForSoundSet:[sender representedObject]];

        /* Save the preset which is now updated to have the appropriate sounds; 
         * in saving, the name of the soundset, or @"", will also be saved.
         */
        [self saveCurrentEventPreset];
}

/*!
 * @brief Revert the event set to how it was before the last attempted operation
 */
- (void)revertToSavedEventSet
{
        NSDictionary            *eventPreset;

        [self selectActiveEventInPopUp];
        eventPreset = [[popUp_eventPreset selectedItem] representedObject];

        [plugin setEventPreset:eventPreset];
        
        //Ensure the correct sound set is selected
        [self updateSoundSetSelection];
}

/*!
 * @brief Build and return the event set as it should be saved
 */
- (NSMutableDictionary *)currentEventSetForSaving
{
        NSDictionary            *eventPreset = [[popUp_eventPreset selectedItem] representedObject];
        NSMutableDictionary     *currentEventSetForSaving = [[eventPreset mutableCopy] autorelease];
        
        //Set the sound set, which is just stored here for ease of preference pane display
        NSString                        *soundSetName = [[[popUp_soundSet selectedItem] representedObject] name];
        if (soundSetName) {
                [currentEventSetForSaving setObject:soundSetName
                                                                         forKey:KEY_EVENT_SOUND_SET];
        } else {
                [currentEventSetForSaving removeObjectForKey:KEY_EVENT_SOUND_SET];
        }
        
        //Get and store the alerts array
        NSArray                         *alertsArray = [adium.contactAlertsController alertsForListObject:nil
                                                                                                                                                                withEventID:nil
                                                                                                                                                                   actionID:nil];
        [currentEventSetForSaving setObject:alertsArray forKey:@"Events"];

        //Ensure this set doesn't claim to be built in.
        [currentEventSetForSaving removeObjectForKey:@"Built In"];
        
        return currentEventSetForSaving;
}

#pragma mark Volume
//New value selected on the volume slider or chosen by clicking a volume icon
- (IBAction)selectVolume:(id)sender
{
    CGFloat                     volume, oldVolume;
        
        if (sender == slider_volume) {
                volume = [slider_volume doubleValue];
        } else if (sender == button_maxvolume) {
                volume = [slider_volume maxValue];
                [slider_volume setDoubleValue:volume];
        } else if (sender == button_minvolume) {
                volume = [slider_volume minValue];
                [slider_volume setDoubleValue:volume];
        } else {
                volume = 0;
        }
        
        NSNumber *oldVolumeValue = [adium.preferenceController preferenceForKey:KEY_SOUND_CUSTOM_VOLUME_LEVEL
                                                                                                                                                group:PREF_GROUP_SOUNDS];
        oldVolume = (oldVolumeValue ? [oldVolumeValue doubleValue] : -1.0);
        
    //Volume
    if (volume != oldVolume) {
        [adium.preferenceController setPreference:[NSNumber numberWithDouble:volume]
                                             forKey:KEY_SOUND_CUSTOM_VOLUME_LEVEL
                                              group:PREF_GROUP_SOUNDS];
                
                //Play a sample sound
        [adium.soundController playSoundAtPath:VOLUME_SOUND_PATH];
    }
}

#pragma mark Preset saving

/*!
 * @brief Save the current event preset
 *
 * Called after each event change to immediately update the current preset.
 * If a built-in preset is currently selected, this method will prompt for a new name before saving.
 */
- (void)saveCurrentEventPreset
{
        NSDictionary            *eventPreset = [[popUp_eventPreset selectedItem] representedObject];

        if ([eventPreset objectForKey:@"Built In"] && [[eventPreset objectForKey:@"Built In"] boolValue]) {
                /* Perform after a delay so that if we got here as a result of a sheet-based add or edit of an event
                 * the sheet will close before we try to open a new one. */
                [self performSelector:@selector(showPresetCopySheet:)
                                   withObject:[self _localizedTitle:[eventPreset objectForKey:@"Name"]]
                                   afterDelay:0];
        } else {        
                //Now save the current settings
                [plugin saveEventPreset:[self currentEventSetForSaving]];
        }               
}

/*!
 * @brief Show the sheet for naming the preset created by an attempt to modify a built-in set
 *
 * @param originalPresetName The name of the original set, used as a base for the new name.
 */
- (void)showPresetCopySheet:(NSString *)originalPresetName
{
        NSString        *defaultName;
        NSString        *explanatoryText;
        
        defaultName = [NSString stringWithFormat:@"%@ %@", originalPresetName, COPY_IN_PARENTHESIS];
        explanatoryText = AILocalizedString(@"You are editing a default event set.  Please enter a unique name for your modified set.",nil);
        
        [ESPresetNameSheetController showPresetNameSheetWithDefaultName:defaultName
                                                                                                        explanatoryText:explanatoryText
                                                                                                                   onWindow:[[self view] window]
                                                                                                        notifyingTarget:self
                                                                                                                   userInfo:nil];
}

- (BOOL)presetNameSheetController:(ESPresetNameSheetController *)controller
                          shouldAcceptNewName:(NSString *)newName
                                                 userInfo:(id)userInfo
{
        return (![[[plugin builtInEventPresets] allKeys] containsObject:newName] &&
                   ![[[plugin storedEventPresets] allKeys] containsObject:newName]);
}
        
- (void)presetNameSheetControllerDidEnd:(ESPresetNameSheetController *)controller 
                                                         returnCode:(ESPresetNameSheetReturnCode)returnCode
                                                                newName:(NSString *)newName
                                                           userInfo:(id)userInfo
{
        switch (returnCode) {
                case ESPresetNameSheetOkayReturn:
                {
                        //XXX error if overwriting existing set?
                        NSMutableDictionary     *newEventPreset = [self currentEventSetForSaving];
                        [newEventPreset setObject:newName
                                                           forKey:@"Name"];
                        
                        //Now save the current settings
                        [plugin saveEventPreset:newEventPreset];
                        
                        //Presets menu
                        [adium.preferenceController setPreference:newName
                                                                                                 forKey:KEY_ACTIVE_EVENT_SET
                                                                                                  group:PREF_GROUP_EVENT_PRESETS];
                        [popUp_eventPreset setMenu:[self eventPresetsMenu]];
                        [popUp_eventPreset selectItemWithTitle:newName];
                        
                        break;
                }
                case ESPresetNameSheetCancelReturn:
                {
                        [self revertToSavedEventSet];
                        break;
                }
        }
}
                
/*!
 * @brief Called when the OK button on the preset copy sheet is pressed
 *
 * Save the current event set under the name specified by [textField_name stringValue].
 * Set the name of the active event set to this new name, and ensure our menu is up to date.
 *
 * Also, close the sheet.
 */
- (IBAction)selectedNameForPresetCopy:(id)sender
{
        
}

- (void)updateSoundSetSelectionForSoundSet:(AISoundSet *)soundSet
{
        if (soundSet) {
                [popUp_soundSet selectItemWithRepresentedObject:soundSet];
                
                [self popUp:popUp_soundSet shouldShowCustom:NO];
                
        } else {
                [self popUp:popUp_soundSet shouldShowCustom:YES];
        }
}

- (void)updateSoundSetSelection
{
        NSEnumerator    *enumerator = [[adium.soundController soundSets] objectEnumerator];
    AISoundSet          *soundSet;
        NSString                *name;

        name = [[[popUp_eventPreset selectedItem] representedObject] objectForKey:KEY_EVENT_SOUND_SET];
        name = [[name lastPathComponent] stringByDeletingPathExtension];

    while ((soundSet = [enumerator nextObject])) {
                if ([[soundSet name] isEqualToString:name]) break;
        }

        [self updateSoundSetSelectionForSoundSet:soundSet];
}

#define NONE AILocalizedString(@"None",nil)
/*!
 * @brief Build and return a menu of sound set choices
 *
 * The menu items have an action of -[self selectSoundSet:].
 */
- (NSMenu *)_soundSetMenu
{
    NSMenu                      *soundSetMenu = [[NSMenu alloc] init];
    NSEnumerator        *enumerator = [[adium.soundController soundSets] objectEnumerator];
        NSMutableArray  *menuItemArray = [NSMutableArray array];
    AISoundSet          *soundSet;
    NSMenuItem          *menuItem, *noneMenuItem = nil;

    while ((soundSet = [enumerator nextObject])) {
                menuItem = [[NSMenuItem alloc] initWithTitle:[self _localizedTitle:[soundSet name]]
                                                                                          target:self
                                                                                          action:@selector(selectSoundSet:)
                                                                           keyEquivalent:@""
                                                                   representedObject:soundSet];
                
                if ([[menuItem title] isEqualToString:NONE]) {
                        noneMenuItem = menuItem;

                } else {
                        [menuItemArray addObject:menuItem];
                        [menuItem release];
                }
        }
        
        [menuItemArray sortUsingSelector:@selector(titleCompare:)];
        
        for (menuItem in menuItemArray) {
                [soundSetMenu addItem:menuItem];
        }

        if (noneMenuItem) {
                [soundSetMenu addItem:[NSMenuItem separatorItem]];
                [soundSetMenu addItem:noneMenuItem];
                [noneMenuItem release];
        }
        
    return [soundSetMenu autorelease];
}

#pragma mark Common menu methods
/*!
 * @brief Localized a menu item title for global events preferences
 *
 * @result The equivalent localized title if available; otherwise, the passed English title
 */
- (NSString *)_localizedTitle:(NSString *)englishTitle
{
        NSString        *localizedTitle = nil;
        
        if ([englishTitle isEqualToString:@"None"])
                localizedTitle = NONE;
        else if ([englishTitle isEqualToString:@"Default Notifications"])
                localizedTitle = AILocalizedString(@"Default Notifications",nil);
        else if ([englishTitle isEqualToString:@"Visual Notifications"])
                localizedTitle = AILocalizedString(@"Visual Notifications",nil);
        else if ([englishTitle isEqualToString:@"Audio Notifications"])
                localizedTitle = AILocalizedString(@"Audio Notifications",nil);

        return (localizedTitle ? localizedTitle : englishTitle);
}

@end