2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 #import "AIAppearancePreferences.h"
18 #import "AIAppearancePreferencesPlugin.h"
19 #import "AIDockIconSelectionSheet.h"
20 #import "AIEmoticonPack.h"
21 #import "AIEmoticonPreferences.h"
22 #import "AIListLayoutWindowController.h"
23 #import "AIListThemeWindowController.h"
24 #import <AIUtilities/AIMenuAdditions.h>
25 #import <AIUtilities/AIImageAdditions.h>
26 #import <AIUtilities/AIPopUpButtonAdditions.h>
27 #import <AIUtilities/AIStringAdditions.h>
28 #import <Adium/AIAbstractListController.h>
29 #import <Adium/AIDockControllerProtocol.h>
30 #import <Adium/AIEmoticonControllerProtocol.h>
31 #import <Adium/AIIconState.h>
32 #import <Adium/AIServiceIcons.h>
33 #import <Adium/AIStatusIcons.h>
34 #import <Adium/ESPresetManagementController.h>
35 #import <Adium/ESPresetNameSheetController.h>
36 #import <AIMenuBarIcons.h>
38 #define OLD_LIST_SETTINGS_PATH [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"OldListXtras"]
41 AIEmoticonMenuNone = 1,
42 AIEmoticonMenuMultiple
45 @interface AIAppearancePreferences (PRIVATE)
46 - (NSMenu *)_windowStyleMenu;
47 - (NSMenu *)_dockIconMenu;
48 - (NSMenu *)_emoticonPackMenu;
49 - (NSMenu *)_statusIconsMenu;
50 - (NSMenu *)_serviceIconsMenu;
51 - (NSMenu *)_listLayoutMenu;
52 - (NSMenu *)_colorThemeMenu;
53 - (void)_rebuildEmoticonMenuAndSelectActivePack;
54 - (NSMenu *)_iconPackMenuForPacks:(NSArray *)packs class:(Class)iconClass;
55 - (void)_addWindowStyleOption:(NSString *)option withTag:(int)tag toMenu:(NSMenu *)menu;
56 - (void)_updateSliderValues;
57 - (void)xtrasChanged:(NSNotification *)notification;
59 - (void)configureDockIconMenu;
60 - (void)configureStatusIconsMenu;
61 - (void)configureServiceIconsMenu;
62 - (void)configureMenuBarIconsMenu;
65 @implementation AIAppearancePreferences
68 * @brief Preference pane properties
70 - (NSString *)paneIdentifier
74 - (NSString *)paneName{
75 return AILocalizedString(@"Appearance","Appearance preferences label");
77 - (NSString *)nibName{
78 return @"AppearancePrefs";
82 return [NSImage imageNamed:@"pref-appearance" forClass:[self class]];
86 * @brief Configure the preference view
91 [popUp_windowStyle setMenu:[self _windowStyleMenu]];
93 //Observe preference changes
94 [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_EMOTICONS];
95 [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE];
97 //Observe xtras changes
98 [[adium notificationCenter] addObserver:self
99 selector:@selector(xtrasChanged:)
100 name:AIXtrasDidChangeNotification
102 [self xtrasChanged:nil];
106 * @brief View will close
108 - (void)viewWillClose
110 [[adium preferenceController] unregisterPreferenceObserver:self];
112 [[adium notificationCenter] removeObserver:self];
116 * @brief Xtras changed, update our menus to reflect the new Xtras
118 - (void)xtrasChanged:(NSNotification *)notification
120 NSString *type = [[notification object] lowercaseString];
122 if (!type || [type isEqualToString:@"adiumemoticonset"]) {
123 [self _rebuildEmoticonMenuAndSelectActivePack];
126 if (!type || [type isEqualToString:@"adiumicon"]) {
127 [self configureDockIconMenu];
130 if (!type || [type isEqualToString:@"adiumserviceicons"]) {
131 [self configureServiceIconsMenu];
134 if (!type || [type isEqualToString:@"adiumstatusicons"]) {
135 [self configureStatusIconsMenu];
138 if (!type || [type isEqualToString:@"adiummenubaricons"]) {
139 [self configureMenuBarIconsMenu];
142 if (!type || [type isEqualToString:@"listtheme"]) {
143 [popUp_colorTheme setMenu:[self _colorThemeMenu]];
144 [popUp_colorTheme selectItemWithRepresentedObject:[[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME
145 group:PREF_GROUP_APPEARANCE]];
148 if (!type || [type isEqualToString:@"listlayout"]) {
149 [popUp_listLayout setMenu:[self _listLayoutMenu]];
150 [popUp_listLayout selectItemWithRepresentedObject:[[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME
151 group:PREF_GROUP_APPEARANCE]];
156 * @brief Preferences changed
158 * Update controls in our view to reflect the changed preferences
160 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object
161 preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
164 if ([group isEqualToString:PREF_GROUP_EMOTICONS] && !firstTime) {
165 [self _rebuildEmoticonMenuAndSelectActivePack];
169 if ([group isEqualToString:PREF_GROUP_APPEARANCE]) {
171 //Horizontal resizing label
173 [key isEqualToString:KEY_LIST_LAYOUT_WINDOW_STYLE] ||
174 [key isEqualToString:KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE]) {
176 int windowMode = [[prefDict objectForKey:KEY_LIST_LAYOUT_WINDOW_STYLE] intValue];
177 BOOL horizontalAutosize = [[prefDict objectForKey:KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE] boolValue];
179 if (windowMode == AIContactListWindowStyleStandard) {
180 //In standard mode, disable the horizontal autosizing slider if horiztonal autosizing is off
181 [textField_horizontalWidthText setLocalizedString:AILocalizedString(@"Maximum Width:",nil)];
182 [slider_horizontalWidth setEnabled:horizontalAutosize];
185 //In all the borderless transparent modes, the horizontal autosizing slider becomes the
186 //horizontal sizing slider when autosizing is off
187 if (horizontalAutosize) {
188 [textField_horizontalWidthText setLocalizedString:AILocalizedString(@"Maximum Width:",nil)];
190 [textField_horizontalWidthText setLocalizedString:AILocalizedString(@"Width:",nil)];
192 [slider_horizontalWidth setEnabled:YES];
197 //Selected menu items
198 if (firstTime || [key isEqualToString:KEY_STATUS_ICON_PACK]) {
199 [popUp_statusIcons selectItemWithTitle:[prefDict objectForKey:KEY_STATUS_ICON_PACK]];
201 //If the prefDict's item isn't present, we're using the default, so select that one
202 if (![popUp_statusIcons selectedItem]) {
203 [popUp_statusIcons selectItemWithTitle:[[adium preferenceController] defaultPreferenceForKey:KEY_STATUS_ICON_PACK
204 group:PREF_GROUP_APPEARANCE
208 if (firstTime || [key isEqualToString:KEY_SERVICE_ICON_PACK]) {
209 [popUp_serviceIcons selectItemWithTitle:[prefDict objectForKey:KEY_SERVICE_ICON_PACK]];
211 //If the prefDict's item isn't present, we're using the default, so select that one
212 if (![popUp_serviceIcons selectedItem]) {
213 [popUp_serviceIcons selectItemWithTitle:[[adium preferenceController] defaultPreferenceForKey:KEY_SERVICE_ICON_PACK
214 group:PREF_GROUP_APPEARANCE
218 if (firstTime || [key isEqualToString:KEY_MENU_BAR_ICONS]) {
219 [popUp_menuBarIcons selectItemWithTitle:[prefDict objectForKey:KEY_MENU_BAR_ICONS]];
221 //If the prefDict's item isn't present, we're using the default, so select that one
222 if (![popUp_menuBarIcons selectedItem]) {
223 [popUp_menuBarIcons selectItemWithTitle:[[adium preferenceController] defaultPreferenceForKey:KEY_MENU_BAR_ICONS
224 group:PREF_GROUP_APPEARANCE
228 if (firstTime || [key isEqualToString:KEY_LIST_LAYOUT_NAME]) {
229 [popUp_listLayout selectItemWithRepresentedObject:[prefDict objectForKey:KEY_LIST_LAYOUT_NAME]];
231 if (firstTime || [key isEqualToString:KEY_LIST_THEME_NAME]) {
232 [popUp_colorTheme selectItemWithRepresentedObject:[prefDict objectForKey:KEY_LIST_THEME_NAME]];
234 if (firstTime || [key isEqualToString:KEY_ACTIVE_DOCK_ICON]) {
235 [popUp_dockIcon selectItemWithRepresentedObject:[prefDict objectForKey:KEY_ACTIVE_DOCK_ICON]];
238 [popUp_windowStyle compatibleSelectItemWithTag:[[prefDict objectForKey:KEY_LIST_LAYOUT_WINDOW_STYLE] intValue]];
239 [checkBox_verticalAutosizing setState:[[prefDict objectForKey:KEY_LIST_LAYOUT_VERTICAL_AUTOSIZE] boolValue]];
240 [checkBox_horizontalAutosizing setState:[[prefDict objectForKey:KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE] boolValue]];
241 [slider_windowOpacity setFloatValue:([[prefDict objectForKey:KEY_LIST_LAYOUT_WINDOW_OPACITY] floatValue] * 100.0)];
242 [slider_horizontalWidth setIntValue:[[prefDict objectForKey:KEY_LIST_LAYOUT_HORIZONTAL_WIDTH] intValue]];
243 [self _updateSliderValues];
249 * @brief Rebuild the emoticon menu
251 - (void)_rebuildEmoticonMenuAndSelectActivePack
253 [popUp_emoticons setMenu:[self _emoticonPackMenu]];
255 //Update the selected pack
256 NSArray *activeEmoticonPacks = [[adium emoticonController] activeEmoticonPacks];
257 int numActivePacks = [activeEmoticonPacks count];
259 if (numActivePacks == 0) {
260 [popUp_emoticons compatibleSelectItemWithTag:AIEmoticonMenuNone];
261 } else if (numActivePacks > 1) {
262 [popUp_emoticons compatibleSelectItemWithTag:AIEmoticonMenuMultiple];
264 [popUp_emoticons selectItemWithRepresentedObject:[activeEmoticonPacks objectAtIndex:0]];
269 * @brief Save changed preferences
271 - (IBAction)changePreference:(id)sender
273 if (sender == popUp_statusIcons) {
274 [[adium preferenceController] setPreference:[[sender selectedItem] title]
275 forKey:KEY_STATUS_ICON_PACK
276 group:PREF_GROUP_APPEARANCE];
278 } else if (sender == popUp_serviceIcons) {
279 [[adium preferenceController] setPreference:[[sender selectedItem] title]
280 forKey:KEY_SERVICE_ICON_PACK
281 group:PREF_GROUP_APPEARANCE];
282 } else if (sender == popUp_menuBarIcons) {
283 [[adium preferenceController] setPreference:[[sender selectedItem] title]
284 forKey:KEY_MENU_BAR_ICONS
285 group:PREF_GROUP_APPEARANCE];
286 } else if (sender == popUp_dockIcon) {
287 [[adium preferenceController] setPreference:[[sender selectedItem] representedObject]
288 forKey:KEY_ACTIVE_DOCK_ICON
289 group:PREF_GROUP_APPEARANCE];
291 } else if (sender == popUp_listLayout) {
292 [[adium preferenceController] setPreference:[[sender selectedItem] title]
293 forKey:KEY_LIST_LAYOUT_NAME
294 group:PREF_GROUP_APPEARANCE];
296 } else if (sender == popUp_colorTheme) {
297 [[adium preferenceController] setPreference:[[sender selectedItem] title]
298 forKey:KEY_LIST_THEME_NAME
299 group:PREF_GROUP_APPEARANCE];
301 } else if (sender == popUp_windowStyle) {
302 [[adium preferenceController] setPreference:[NSNumber numberWithInt:[[sender selectedItem] tag]]
303 forKey:KEY_LIST_LAYOUT_WINDOW_STYLE
304 group:PREF_GROUP_APPEARANCE];
306 } else if (sender == checkBox_verticalAutosizing) {
307 [[adium preferenceController] setPreference:[NSNumber numberWithBool:[sender state]]
308 forKey:KEY_LIST_LAYOUT_VERTICAL_AUTOSIZE
309 group:PREF_GROUP_APPEARANCE];
311 } else if (sender == checkBox_horizontalAutosizing) {
312 [[adium preferenceController] setPreference:[NSNumber numberWithBool:[sender state]]
313 forKey:KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE
314 group:PREF_GROUP_APPEARANCE];
316 } else if (sender == slider_windowOpacity) {
317 [[adium preferenceController] setPreference:[NSNumber numberWithFloat:([sender floatValue] / 100.0)]
318 forKey:KEY_LIST_LAYOUT_WINDOW_OPACITY
319 group:PREF_GROUP_APPEARANCE];
320 [self _updateSliderValues];
322 } else if (sender == slider_horizontalWidth) {
323 int newValue = [sender intValue];
324 int oldValue = [[[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_HORIZONTAL_WIDTH
325 group:PREF_GROUP_APPEARANCE] intValue];
326 if (newValue != oldValue) {
327 [[adium preferenceController] setPreference:[NSNumber numberWithInt:newValue]
328 forKey:KEY_LIST_LAYOUT_HORIZONTAL_WIDTH
329 group:PREF_GROUP_APPEARANCE];
330 [self _updateSliderValues];
333 } else if (sender == popUp_emoticons) {
334 if ([[sender selectedItem] tag] != AIEmoticonMenuMultiple) {
335 //Disable all active emoticons
336 NSArray *activePacks = [[[[adium emoticonController] activeEmoticonPacks] mutableCopy] autorelease];
337 NSEnumerator *enumerator = [activePacks objectEnumerator];
338 AIEmoticonPack *pack, *selectedPack;
340 selectedPack = [[sender selectedItem] representedObject];
342 [[adium preferenceController] delayPreferenceChangedNotifications:YES];
344 while ((pack = [enumerator nextObject])) {
345 [[adium emoticonController] setEmoticonPack:pack enabled:NO];
348 //Enable the selected pack
349 if (selectedPack) [[adium emoticonController] setEmoticonPack:selectedPack enabled:YES];
351 [[adium preferenceController] delayPreferenceChangedNotifications:NO];
359 - (void)_updateSliderValues
361 [textField_windowOpacity setStringValue:[NSString stringWithFormat:@"%i%%", (int)[slider_windowOpacity floatValue]]];
362 [textField_horizontalWidthIndicator setStringValue:[NSString stringWithFormat:@"%ipx",[slider_horizontalWidth intValue]]];
366 //Emoticons ------------------------------------------------------------------------------------------------------------
367 #pragma mark Emoticons
371 - (IBAction)customizeEmoticons:(id)sender
373 [AIEmoticonPreferences showEmoticionCustomizationOnWindow:[[self view] window]];
379 - (NSMenu *)_emoticonPackMenu
381 NSMenu *menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
382 NSEnumerator *enumerator = [[[adium emoticonController] availableEmoticonPacks] objectEnumerator];
383 AIEmoticonPack *pack;
384 NSMenuItem *menuItem;
386 //Add the "No Emoticons" option
387 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"None",nil)
390 keyEquivalent:@""] autorelease];
391 [menuItem setImage:[NSImage imageNamed:@"emoticonBlank" forClass:[self class]]];
392 [menuItem setTag:AIEmoticonMenuNone];
393 [menu addItem:menuItem];
395 //Add the "Multiple packs selected" option
396 if ([[[adium emoticonController] activeEmoticonPacks] count] > 1) {
397 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Multiple Packs Selected",nil)
400 keyEquivalent:@""] autorelease];
401 [menuItem setImage:[NSImage imageNamed:@"emoticonBlank" forClass:[self class]]];
402 [menuItem setTag:AIEmoticonMenuMultiple];
403 [menu addItem:menuItem];
407 [menu addItem:[NSMenuItem separatorItem]];
410 while ((pack = [enumerator nextObject])) {
411 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[pack name]
414 keyEquivalent:@""] autorelease];
415 [menuItem setRepresentedObject:pack];
416 [menuItem setImage:[pack menuPreviewImage]];
417 [menu addItem:menuItem];
420 return [menu autorelease];
424 //Contact list options -------------------------------------------------------------------------------------------------
425 #pragma mark Contact list options
429 - (NSMenu *)_windowStyleMenu
431 NSMenu *menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
433 [self _addWindowStyleOption:AILocalizedString(@"Regular Window",nil)
434 withTag:AIContactListWindowStyleStandard
436 [menu addItem:[NSMenuItem separatorItem]];
437 [self _addWindowStyleOption:AILocalizedString(@"Borderless Window",nil)
438 withTag:AIContactListWindowStyleBorderless
440 [self _addWindowStyleOption:AILocalizedString(@"Group Bubbles",nil)
441 withTag:AIContactListWindowStyleGroupBubbles
443 [self _addWindowStyleOption:AILocalizedString(@"Contact Bubbles",nil)
444 withTag:AIContactListWindowStyleContactBubbles
446 [self _addWindowStyleOption:AILocalizedString(@"Contact Bubbles (To Fit)",nil)
447 withTag:AIContactListWindowStyleContactBubbles_Fitted
450 return [menu autorelease];
452 - (void)_addWindowStyleOption:(NSString *)option withTag:(int)tag toMenu:(NSMenu *)menu{
453 NSMenuItem *menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:option
456 keyEquivalent:@""] autorelease];
457 [menuItem setTag:tag];
458 [menu addItem:menuItem];
462 //Contact list layout & theme ----------------------------------------------------------------------------------------
463 #pragma mark Contact list layout & theme
465 + (void) migrateOldListSettingsIfNeeded
467 id<AIPreferenceController> prefController = [[AIObject sharedAdiumInstance] preferenceController];
468 NSFileManager *manager = [NSFileManager defaultManager];
469 NSString *theme = [NSString stringWithFormat:@"%@/%@/%@.%@",
470 [[NSBundle mainBundle] resourcePath],
472 [prefController preferenceForKey:KEY_LIST_THEME_NAME group:PREF_GROUP_APPEARANCE],
473 LIST_THEME_EXTENSION];
474 if(![manager fileExistsAtPath:theme])
476 NSString *oldTheme = [OLD_LIST_SETTINGS_PATH stringByAppendingPathComponent:[theme lastPathComponent]];
477 if([manager fileExistsAtPath:oldTheme])
478 [manager movePath:oldTheme toPath:theme handler:nil];
480 NSString *layout = [NSString stringWithFormat:@"%@/%@/%@.%@",
481 [[NSBundle mainBundle] resourcePath],
483 [prefController preferenceForKey:KEY_LIST_LAYOUT_NAME group:PREF_GROUP_APPEARANCE],
485 if(![manager fileExistsAtPath:layout])
487 NSString *oldLayout = [OLD_LIST_SETTINGS_PATH stringByAppendingPathComponent:[layout lastPathComponent]];
488 if([manager fileExistsAtPath:oldLayout])
489 [manager movePath:oldLayout toPath:layout handler:nil];
494 * @brief Create a new theme
496 - (IBAction)createListTheme:(id)sender
498 NSString *theme = [[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME group:PREF_GROUP_APPEARANCE];
500 [ESPresetNameSheetController showPresetNameSheetWithDefaultName:[theme stringByAppendingString:@" Copy"]
501 explanatoryText:AILocalizedString(@"Enter a unique name for this new theme.",nil)
502 onWindow:[[self view] window]
508 * @brief Customize the active theme
510 - (IBAction)customizeListTheme:(id)sender
512 NSString *theme = [[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME group:PREF_GROUP_APPEARANCE];
514 //Allow alpha in our color pickers
515 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
517 [AIListThemeWindowController editListThemeWithName:theme
518 onWindow:[[self view] window]
519 notifyingTarget:self];
523 * @brief Save (or revert) changes made when editing a theme
525 - (void)listThemeEditorWillCloseWithChanges:(BOOL)saveChanges forThemeNamed:(NSString *)name
528 //Update the modified theme
529 if ([plugin createSetFromPreferenceGroup:PREF_GROUP_LIST_THEME
531 extension:LIST_THEME_EXTENSION
532 inFolder:LIST_THEME_FOLDER]) {
534 [[adium preferenceController] setPreference:name
535 forKey:KEY_LIST_THEME_NAME
536 group:PREF_GROUP_APPEARANCE];
540 //Revert back to selected theme
541 NSString *theme = [[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME group:PREF_GROUP_APPEARANCE];
543 [plugin applySetWithName:theme
544 extension:LIST_THEME_EXTENSION
545 inFolder:LIST_THEME_FOLDER
546 toPreferenceGroup:PREF_GROUP_LIST_THEME];
550 //No longer allow alpha in our color pickers
551 [[NSColorPanel sharedColorPanel] setShowsAlpha:NO];
555 * @brief Manage available themes
557 - (void)manageListThemes:(id)sender
559 _listThemes = [plugin availableThemeSets];
560 [ESPresetManagementController managePresets:_listThemes
562 onWindow:[[self view] window]
565 [popUp_colorTheme selectItemWithRepresentedObject:[[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME
566 group:PREF_GROUP_APPEARANCE]];
570 * @brief Create a new layout
572 - (IBAction)createListLayout:(id)sender
574 NSString *layout = [[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME group:PREF_GROUP_APPEARANCE];
576 [ESPresetNameSheetController showPresetNameSheetWithDefaultName:[layout stringByAppendingString:@" Copy"]
577 explanatoryText:AILocalizedString(@"Enter a unique name for this new layout.",nil)
578 onWindow:[[self view] window]
584 * @brief Customize the active layout
586 - (IBAction)customizeListLayout:(id)sender
588 NSString *theme = [[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME group:PREF_GROUP_APPEARANCE];
590 //Allow alpha in our color pickers
591 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
593 [AIListLayoutWindowController editListLayoutWithName:theme
594 onWindow:[[self view] window]
595 notifyingTarget:self];
599 * @brief Save (or revert) changes made when editing a layout
601 - (void)listLayoutEditorWillCloseWithChanges:(BOOL)saveChanges forLayoutNamed:(NSString *)name
604 //Update the modified layout
605 if ([plugin createSetFromPreferenceGroup:PREF_GROUP_LIST_LAYOUT
607 extension:LIST_LAYOUT_EXTENSION
608 inFolder:LIST_LAYOUT_FOLDER]) {
610 [[adium preferenceController] setPreference:name
611 forKey:KEY_LIST_LAYOUT_NAME
612 group:PREF_GROUP_APPEARANCE];
616 //Revert back to selected layout
617 NSString *layout = [[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME group:PREF_GROUP_APPEARANCE];
619 [plugin applySetWithName:layout
620 extension:LIST_LAYOUT_EXTENSION
621 inFolder:LIST_LAYOUT_FOLDER
622 toPreferenceGroup:PREF_GROUP_LIST_LAYOUT];
625 //No longer allow alpha in our color pickers
626 [[NSColorPanel sharedColorPanel] setShowsAlpha:NO];
630 * @brief Manage available layouts
632 - (void)manageListLayouts:(id)sender
634 _listLayouts = [plugin availableLayoutSets];
635 [ESPresetManagementController managePresets:_listLayouts
637 onWindow:[[self view] window]
640 [popUp_listLayout selectItemWithRepresentedObject:[[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME
641 group:PREF_GROUP_APPEARANCE]];
645 * @brief Validate a layout or theme name to ensure it is unique
647 - (BOOL)presetNameSheetController:(ESPresetNameSheetController *)controller
648 shouldAcceptNewName:(NSString *)newName
649 userInfo:(id)userInfo
651 NSEnumerator *enumerator;
652 NSDictionary *presetDict;
654 //Scan the correct presets to ensure this name doesn't already exist
655 if ([userInfo isEqualToString:@"theme"]) {
656 enumerator = [[plugin availableThemeSets] objectEnumerator];
658 enumerator = [[plugin availableLayoutSets] objectEnumerator];
661 while ((presetDict = [enumerator nextObject])) {
662 if ([newName isEqualToString:[presetDict objectForKey:@"name"]]) return NO;
669 * @brief Create a new theme with the user supplied name, activate and edit it
671 - (void)presetNameSheetControllerDidEnd:(ESPresetNameSheetController *)controller
672 returnCode:(ESPresetNameSheetReturnCode)returnCode
673 newName:(NSString *)newName
674 userInfo:(id)userInfo
676 switch (returnCode) {
677 case ESPresetNameSheetOkayReturn:
678 if ([userInfo isEqualToString:@"theme"]) {
679 [self performSelector:@selector(_editListThemeWithName:) withObject:newName afterDelay:0.00001];
681 [self performSelector:@selector(_editListLayoutWithName:) withObject:newName afterDelay:0.00001];
685 case ESPresetNameSheetCancelReturn:
690 - (void)_editListThemeWithName:(NSString *)name{
691 [AIListThemeWindowController editListThemeWithName:name
692 onWindow:[[self view] window]
693 notifyingTarget:self];
695 - (void)_editListLayoutWithName:(NSString *)name{
696 [AIListLayoutWindowController editListLayoutWithName:name
697 onWindow:[[self view] window]
698 notifyingTarget:self];
704 - (NSArray *)renamePreset:(NSDictionary *)preset toName:(NSString *)newName inPresets:(NSArray *)presets renamedPreset:(id *)renamedPreset
708 if (presets == _listLayouts) {
709 [plugin renameSetWithName:[preset objectForKey:@"name"]
710 extension:LIST_LAYOUT_EXTENSION
711 inFolder:LIST_LAYOUT_FOLDER
713 _listLayouts = [plugin availableLayoutSets];
714 newPresets = _listLayouts;
716 } else if (presets == _listThemes) {
717 [plugin renameSetWithName:[preset objectForKey:@"name"]
718 extension:LIST_THEME_EXTENSION
719 inFolder:LIST_THEME_FOLDER
721 _listThemes = [plugin availableThemeSets];
722 newPresets = _listThemes;
728 //Return the new duplicate by reference for the preset controller
730 NSEnumerator *enumerator = [newPresets objectEnumerator];
731 NSDictionary *aPreset;
733 while ((aPreset = [enumerator nextObject])) {
734 if ([newName isEqualToString:[aPreset objectForKey:@"name"]]) {
735 *renamedPreset = aPreset;
747 - (NSArray *)duplicatePreset:(NSDictionary *)preset inPresets:(NSArray *)presets createdDuplicate:(id *)duplicatePreset
749 NSString *newName = [NSString stringWithFormat:@"%@ (%@)", [preset objectForKey:@"name"], AILocalizedString(@"Copy",nil)];
750 NSArray *newPresets = nil;
752 if (presets == _listLayouts) {
753 [plugin duplicateSetWithName:[preset objectForKey:@"name"]
754 extension:LIST_LAYOUT_EXTENSION
755 inFolder:LIST_LAYOUT_FOLDER
757 _listLayouts = [plugin availableLayoutSets];
758 newPresets = _listLayouts;
760 } else if (presets == _listThemes) {
761 [plugin duplicateSetWithName:[preset objectForKey:@"name"]
762 extension:LIST_THEME_EXTENSION
763 inFolder:LIST_THEME_FOLDER
765 _listThemes = [plugin availableThemeSets];
766 newPresets = _listThemes;
769 //Return the new duplicate by reference for the preset controller
770 if (duplicatePreset) {
771 NSEnumerator *enumerator = [newPresets objectEnumerator];
772 NSDictionary *aPreset;
774 while ((aPreset = [enumerator nextObject])) {
775 if ([newName isEqualToString:[aPreset objectForKey:@"name"]]) {
776 *duplicatePreset = aPreset;
788 - (NSArray *)deletePreset:(NSDictionary *)preset inPresets:(NSArray *)presets
790 if (presets == _listLayouts) {
791 [plugin deleteSetWithName:[preset objectForKey:@"name"]
792 extension:LIST_LAYOUT_EXTENSION
793 inFolder:LIST_LAYOUT_FOLDER];
794 _listLayouts = [plugin availableLayoutSets];
798 } else if (presets == _listThemes) {
799 [plugin deleteSetWithName:[preset objectForKey:@"name"]
800 extension:LIST_THEME_EXTENSION
801 inFolder:LIST_THEME_FOLDER];
802 _listThemes = [plugin availableThemeSets];
814 - (NSMenu *)_listLayoutMenu
816 NSMenu *menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
817 NSEnumerator *enumerator = [[plugin availableLayoutSets] objectEnumerator];
819 NSMenuItem *menuItem;
823 while ((set = [enumerator nextObject])) {
824 name = [set objectForKey:@"name"];
825 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
828 keyEquivalent:@""] autorelease];
829 [menuItem setRepresentedObject:name];
830 [menu addItem:menuItem];
834 [menu addItem:[NSMenuItem separatorItem]];
837 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Add New Layout",nil) stringByAppendingEllipsis]
839 action:@selector(createListLayout:)
840 keyEquivalent:@""] autorelease];
841 [menu addItem:menuItem];
843 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Edit Layouts",nil) stringByAppendingEllipsis]
845 action:@selector(manageListLayouts:)
846 keyEquivalent:@""] autorelease];
847 [menu addItem:menuItem];
855 - (NSMenu *)_colorThemeMenu
857 NSMenu *menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
858 NSEnumerator *enumerator = [[plugin availableThemeSets] objectEnumerator];
860 NSMenuItem *menuItem;
864 while ((set = [enumerator nextObject])) {
865 name = [set objectForKey:@"name"];
866 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
869 keyEquivalent:@""] autorelease];
870 [menuItem setRepresentedObject:name];
871 [menu addItem:menuItem];
875 [menu addItem:[NSMenuItem separatorItem]];
878 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Add New Theme",nil) stringByAppendingEllipsis]
880 action:@selector(createListTheme:)
881 keyEquivalent:@""] autorelease];
882 [menu addItem:menuItem];
884 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Edit Themes",nil) stringByAppendingEllipsis]
886 action:@selector(manageListThemes:)
887 keyEquivalent:@""] autorelease];
888 [menu addItem:menuItem];
894 //Dock icons -----------------------------------------------------------------------------------------------------------
895 #pragma mark Dock icons
899 - (IBAction)showAllDockIcons:(id)sender
901 [AIDockIconSelectionSheet showDockIconSelectorOnWindow:[[self view] window]];
905 * @brief Return the menu item for a dock icon
907 - (NSMenuItem *)meuItemForDockIconPackAtPath:(NSString *)packPath
909 NSMenuItem *menuItem;
910 NSString *name = nil;
911 NSString *packName = [[packPath lastPathComponent] stringByDeletingPathExtension];
912 AIIconState *preview = nil;
914 [[adium dockController] getName:&name
915 previewState:&preview
916 forIconPackAtPath:packPath];
922 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
925 keyEquivalent:@""] autorelease];
926 [menuItem setRepresentedObject:packName];
927 [menuItem setImage:[[preview image] imageByScalingForMenuItem]];
933 * @brief Returns an array of menu items of all dock icon packs
935 - (NSArray *)_dockIconMenuArray
937 NSMutableArray *menuItemArray = [NSMutableArray array];
938 NSEnumerator *enumerator;
941 enumerator = [[[adium dockController] availableDockIconPacks] objectEnumerator];
942 while ((packPath = [enumerator nextObject])) {
943 [menuItemArray addObject:[self meuItemForDockIconPackAtPath:packPath]];
946 [menuItemArray sortUsingSelector:@selector(titleCompare:)];
948 return menuItemArray;
952 * @brief Configure the dock icon meu initially or after the xtras change
954 * Initially, the dock icon menu just has the currently selected icon; the others will be generated lazily if the icon is displayed, in menuNeedsUpdate:
956 - (void)configureDockIconMenu
958 NSMenu *tempMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
960 NSString *activePackName = [[adium preferenceController] preferenceForKey:KEY_ACTIVE_DOCK_ICON
961 group:PREF_GROUP_APPEARANCE];
962 iconPath = [adium pathOfPackWithName:activePackName
963 extension:@"AdiumIcon"
964 resourceFolderName:FOLDER_DOCK_ICONS];
966 [tempMenu addItem:[self meuItemForDockIconPackAtPath:iconPath]];
967 [tempMenu setDelegate:self];
968 [tempMenu setTitle:@"Temporary Dock Icon Menu"];
970 [popUp_dockIcon setMenu:tempMenu];
971 [popUp_dockIcon selectItemWithRepresentedObject:activePackName];
974 //Status, Service and Menu Bar icons ---------------------------------------------------------------------------------------------
975 #pragma mark Status, service and menu bar icons
976 - (NSMenuItem *)menuItemForIconPackAtPath:(NSString *)packPath class:(Class)iconClass
978 NSString *name = [[packPath lastPathComponent] stringByDeletingPathExtension];
979 NSMenuItem *menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
982 keyEquivalent:@""] autorelease];
983 [menuItem setRepresentedObject:name];
984 [menuItem setImage:[iconClass previewMenuImageForIconPackAtPath:packPath]];
990 * @brief Builds and returns an icon pack menu
992 * @param packs NSArray of icon pack file paths
993 * @param iconClass The controller class (AIStatusIcons, AIServiceIcons) for icon pack previews
995 - (NSArray *)_iconPackMenuArrayForPacks:(NSArray *)packs class:(Class)iconClass
997 NSMutableArray *menuItemArray = [NSMutableArray array];
998 NSEnumerator *enumerator = [packs objectEnumerator];
1001 while ((packPath = [enumerator nextObject])) {
1002 [menuItemArray addObject:[self menuItemForIconPackAtPath:packPath class:iconClass]];
1005 [menuItemArray sortUsingSelector:@selector(titleCompare:)];
1007 return menuItemArray;
1010 - (void)configureStatusIconsMenu
1012 NSMenu *tempMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
1014 NSString *activePackName = [[adium preferenceController] preferenceForKey:KEY_STATUS_ICON_PACK
1015 group:PREF_GROUP_APPEARANCE];
1016 iconPath = [adium pathOfPackWithName:activePackName
1017 extension:@"AdiumStatusIcons"
1018 resourceFolderName:@"Status Icons"];
1021 activePackName = [[adium preferenceController] defaultPreferenceForKey:KEY_STATUS_ICON_PACK
1022 group:PREF_GROUP_APPEARANCE
1025 iconPath = [adium pathOfPackWithName:activePackName
1026 extension:@"AdiumStatusIcons"
1027 resourceFolderName:@"Status Icons"];
1029 [tempMenu addItem:[self menuItemForIconPackAtPath:iconPath class:[AIStatusIcons class]]];
1030 [tempMenu setDelegate:self];
1031 [tempMenu setTitle:@"Temporary Status Icons Menu"];
1033 [popUp_statusIcons setMenu:tempMenu];
1034 [popUp_statusIcons selectItemWithRepresentedObject:activePackName];
1037 - (void)configureServiceIconsMenu
1039 NSMenu *tempMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
1041 NSString *activePackName = [[adium preferenceController] preferenceForKey:KEY_SERVICE_ICON_PACK
1042 group:PREF_GROUP_APPEARANCE];
1043 iconPath = [adium pathOfPackWithName:activePackName
1044 extension:@"AdiumServiceIcons"
1045 resourceFolderName:@"Service Icons"];
1048 activePackName = [[adium preferenceController] defaultPreferenceForKey:KEY_SERVICE_ICON_PACK
1049 group:PREF_GROUP_APPEARANCE
1052 iconPath = [adium pathOfPackWithName:activePackName
1053 extension:@"AdiumServiceIcons"
1054 resourceFolderName:@"Service Icons"];
1056 [tempMenu addItem:[self menuItemForIconPackAtPath:iconPath class:[AIServiceIcons class]]];
1057 [tempMenu setDelegate:self];
1058 [tempMenu setTitle:@"Temporary Service Icons Menu"];
1060 [popUp_serviceIcons setMenu:tempMenu];
1061 [popUp_serviceIcons selectItemWithRepresentedObject:activePackName];
1064 - (void)configureMenuBarIconsMenu
1066 NSMenu *tempMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
1068 NSString *activePackName = [[adium preferenceController] preferenceForKey:KEY_MENU_BAR_ICONS
1069 group:PREF_GROUP_APPEARANCE];
1070 iconPath = [adium pathOfPackWithName:activePackName
1071 extension:@"AdiumMenuBarIcons"
1072 resourceFolderName:@"Menu Bar Icons"];
1075 activePackName = [[adium preferenceController] defaultPreferenceForKey:KEY_MENU_BAR_ICONS
1076 group:PREF_GROUP_APPEARANCE
1079 iconPath = [adium pathOfPackWithName:activePackName
1080 extension:@"AdiumMenuBarIcons"
1081 resourceFolderName:@"Menu Bar Icons"];
1083 [tempMenu addItem:[self menuItemForIconPackAtPath:iconPath class:[AIMenuBarIcons class]]];
1084 [tempMenu setDelegate:self];
1085 [tempMenu setTitle:@"Temporary Menu Bar Icons Menu"];
1087 [popUp_menuBarIcons setMenu:tempMenu];
1088 [popUp_menuBarIcons selectItemWithRepresentedObject:activePackName];
1091 #pragma mark Menu delegate
1092 - (void)menuNeedsUpdate:(NSMenu *)menu
1094 NSString *title =[menu title];
1095 NSString *repObject = nil;
1096 NSArray *menuItemArray = nil;
1097 NSPopUpButton *popUpButton;
1099 if ([title isEqualToString:@"Temporary Dock Icon Menu"]) {
1100 //If the menu has @"Temporary Dock Icon Menu" as its title, we should update it to have all dock icons, not just our selected one
1101 menuItemArray = [self _dockIconMenuArray];
1102 repObject = [[adium preferenceController] preferenceForKey:KEY_ACTIVE_DOCK_ICON
1103 group:PREF_GROUP_APPEARANCE];
1104 popUpButton = popUp_dockIcon;
1106 } else if ([title isEqualToString:@"Temporary Status Icons Menu"]) {
1107 menuItemArray = [self _iconPackMenuArrayForPacks:[adium allResourcesForName:@"Status Icons"
1108 withExtensions:@"AdiumStatusIcons"]
1109 class:[AIStatusIcons class]];
1110 repObject = [[adium preferenceController] preferenceForKey:KEY_STATUS_ICON_PACK
1111 group:PREF_GROUP_APPEARANCE];
1112 popUpButton = popUp_statusIcons;
1114 } else if ([title isEqualToString:@"Temporary Service Icons Menu"]) {
1115 menuItemArray = [self _iconPackMenuArrayForPacks:[adium allResourcesForName:@"Service Icons"
1116 withExtensions:@"AdiumServiceIcons"]
1117 class:[AIServiceIcons class]];
1118 repObject = [[adium preferenceController] preferenceForKey:KEY_SERVICE_ICON_PACK
1119 group:PREF_GROUP_APPEARANCE];
1120 popUpButton = popUp_serviceIcons;
1122 } else if ([title isEqualToString:@"Temporary Menu Bar Icons Menu"]) {
1123 menuItemArray = [self _iconPackMenuArrayForPacks:[adium allResourcesForName:@"Menu Bar Icons"
1124 withExtensions:@"AdiumMenuBarIcons"]
1125 class:[AIMenuBarIcons class]];
1126 repObject = [[adium preferenceController] preferenceForKey:KEY_MENU_BAR_ICONS
1127 group:PREF_GROUP_APPEARANCE];
1128 popUpButton = popUp_menuBarIcons;
1131 if (menuItemArray) {
1132 NSEnumerator *enumerator;
1133 NSMenuItem *menuItem;
1135 //Remove existing items
1136 [menu removeAllItems];
1138 //Clear the title so we know we don't need to do this again
1139 [menu setTitle:@""];
1142 enumerator = [menuItemArray objectEnumerator];
1143 while ((menuItem = [enumerator nextObject])) {
1144 [menu addItem:menuItem];
1147 //Clear the title so we know we don't need to do this again
1148 [menu setTitle:@""];
1150 //Put a checkmark by the appropriate menu item
1151 [popUpButton selectItemWithRepresentedObject:repObject];