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 "AIDockController.h"
20 #import "AIDockIconSelectionSheet.h"
21 #import "AIEmoticonPack.h"
22 #import "AIEmoticonPreferences.h"
23 #import "AIListLayoutWindowController.h"
24 #import "AIListThemeWindowController.h"
25 #import <AIUtilities/AIMenuAdditions.h>
26 #import <AIUtilities/AIImageAdditions.h>
27 #import <AIUtilities/AIPopUpButtonAdditions.h>
28 #import <AIUtilities/AIStringAdditions.h>
29 #import <Adium/AIAbstractListController.h>
30 #import <Adium/AIEmoticonController.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>
37 #define OLD_LIST_SETTINGS_PATH [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"OldListXtras"]
40 AIEmoticonMenuNone = 1,
41 AIEmoticonMenuMultiple
44 @interface AIAppearancePreferences (PRIVATE)
45 - (NSMenu *)_windowStyleMenu;
46 - (NSMenu *)_dockIconMenu;
47 - (NSMenu *)_emoticonPackMenu;
48 - (NSMenu *)_statusIconsMenu;
49 - (NSMenu *)_serviceIconsMenu;
50 - (NSMenu *)_listLayoutMenu;
51 - (NSMenu *)_colorThemeMenu;
52 - (void)_rebuildEmoticonMenuAndSelectActivePack;
53 - (NSMenu *)_iconPackMenuForPacks:(NSArray *)packs class:(Class)iconClass;
54 - (void)_addWindowStyleOption:(NSString *)option withTag:(int)tag toMenu:(NSMenu *)menu;
55 - (void)_updateSliderValues;
56 - (void)xtrasChanged:(NSNotification *)notification;
58 - (void)configureDockIconMenu;
59 - (void)configureStatusIconsMenu;
60 - (void)configureServiceIconsMenu;
63 @implementation AIAppearancePreferences
66 * @brief Preference pane properties
68 - (AIPreferenceCategory)category{
69 return AIPref_Appearance;
72 return AILocalizedString(@"Appearance","Appearance preferences label");
74 - (NSString *)nibName{
75 return @"AppearancePrefs";
79 * @brief Configure the preference view
84 [popUp_windowStyle setMenu:[self _windowStyleMenu]];
86 //Observe preference changes
87 [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_EMOTICONS];
88 [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE];
90 //Observe xtras changes
91 [[adium notificationCenter] addObserver:self
92 selector:@selector(xtrasChanged:)
93 name:Adium_Xtras_Changed
95 [self xtrasChanged:nil];
99 * @brief View will close
101 - (void)viewWillClose
103 [[adium preferenceController] unregisterPreferenceObserver:self];
105 [[adium notificationCenter] removeObserver:self];
109 * @brief Xtras changed, update our menus to reflect the new Xtras
111 - (void)xtrasChanged:(NSNotification *)notification
113 NSString *type = [[notification object] lowercaseString];
115 if (!type || [type isEqualToString:@"adiumemoticonset"]) {
116 [self _rebuildEmoticonMenuAndSelectActivePack];
119 if (!type || [type isEqualToString:@"adiumicon"]) {
120 [self configureDockIconMenu];
123 if (!type || [type isEqualToString:@"adiumserviceicons"]) {
124 [self configureServiceIconsMenu];
127 if (!type || [type isEqualToString:@"adiumstatusicons"]) {
128 [self configureStatusIconsMenu];
131 if (!type || [type isEqualToString:@"listtheme"]) {
132 [popUp_colorTheme setMenu:[self _colorThemeMenu]];
133 [popUp_colorTheme selectItemWithRepresentedObject:[[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME
134 group:PREF_GROUP_APPEARANCE]];
137 if (!type || [type isEqualToString:@"listlayout"]) {
138 [popUp_listLayout setMenu:[self _listLayoutMenu]];
139 [popUp_listLayout selectItemWithRepresentedObject:[[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME
140 group:PREF_GROUP_APPEARANCE]];
145 * @brief Preferences changed
147 * Update controls in our view to reflect the changed preferences
149 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object
150 preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
153 if ([group isEqualToString:PREF_GROUP_EMOTICONS] && !firstTime) {
154 [self _rebuildEmoticonMenuAndSelectActivePack];
158 if ([group isEqualToString:PREF_GROUP_APPEARANCE]) {
160 //Horizontal resizing label
162 [key isEqualToString:KEY_LIST_LAYOUT_WINDOW_STYLE] ||
163 [key isEqualToString:KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE]) {
165 int windowMode = [[prefDict objectForKey:KEY_LIST_LAYOUT_WINDOW_STYLE] intValue];
166 BOOL horizontalAutosize = [[prefDict objectForKey:KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE] boolValue];
168 if (windowMode == AIContactListWindowStyleStandard) {
169 //In standard mode, disable the horizontal autosizing slider if horiztonal autosizing is off
170 [textField_horizontalWidthText setLocalizedString:AILocalizedString(@"Maximum Width:",nil)];
171 [slider_horizontalWidth setEnabled:horizontalAutosize];
174 //In all the borderless transparent modes, the horizontal autosizing slider becomes the
175 //horizontal sizing slider when autosizing is off
176 if (horizontalAutosize) {
177 [textField_horizontalWidthText setLocalizedString:AILocalizedString(@"Maximum Width:",nil)];
179 [textField_horizontalWidthText setLocalizedString:AILocalizedString(@"Width:",nil)];
181 [slider_horizontalWidth setEnabled:YES];
186 //Selected menu items
187 if (firstTime || [key isEqualToString:KEY_STATUS_ICON_PACK]) {
188 [popUp_statusIcons selectItemWithTitle:[prefDict objectForKey:KEY_STATUS_ICON_PACK]];
190 //If the prefDict's item isn't present, we're using the default, so select that one
191 if (![popUp_serviceIcons selectedItem]) {
192 [popUp_serviceIcons selectItemWithTitle:[[adium preferenceController] defaultPreferenceForKey:KEY_STATUS_ICON_PACK
193 group:PREF_GROUP_APPEARANCE
197 if (firstTime || [key isEqualToString:KEY_SERVICE_ICON_PACK]) {
198 [popUp_serviceIcons selectItemWithTitle:[prefDict objectForKey:KEY_SERVICE_ICON_PACK]];
200 //If the prefDict's item isn't present, we're using the default, so select that one
201 if (![popUp_serviceIcons selectedItem]) {
202 [popUp_serviceIcons selectItemWithTitle:[[adium preferenceController] defaultPreferenceForKey:KEY_SERVICE_ICON_PACK
203 group:PREF_GROUP_APPEARANCE
207 if (firstTime || [key isEqualToString:KEY_LIST_LAYOUT_NAME]) {
208 [popUp_listLayout selectItemWithRepresentedObject:[prefDict objectForKey:KEY_LIST_LAYOUT_NAME]];
210 if (firstTime || [key isEqualToString:KEY_LIST_THEME_NAME]) {
211 [popUp_colorTheme selectItemWithRepresentedObject:[prefDict objectForKey:KEY_LIST_THEME_NAME]];
213 if (firstTime || [key isEqualToString:KEY_ACTIVE_DOCK_ICON]) {
214 [popUp_dockIcon selectItemWithRepresentedObject:[prefDict objectForKey:KEY_ACTIVE_DOCK_ICON]];
217 [popUp_windowStyle compatibleSelectItemWithTag:[[prefDict objectForKey:KEY_LIST_LAYOUT_WINDOW_STYLE] intValue]];
218 [checkBox_verticalAutosizing setState:[[prefDict objectForKey:KEY_LIST_LAYOUT_VERTICAL_AUTOSIZE] boolValue]];
219 [checkBox_horizontalAutosizing setState:[[prefDict objectForKey:KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE] boolValue]];
220 [slider_windowOpacity setFloatValue:([[prefDict objectForKey:KEY_LIST_LAYOUT_WINDOW_OPACITY] floatValue] * 100.0)];
221 [slider_horizontalWidth setIntValue:[[prefDict objectForKey:KEY_LIST_LAYOUT_HORIZONTAL_WIDTH] intValue]];
222 [self _updateSliderValues];
228 * @brief Rebuild the emoticon menu
230 - (void)_rebuildEmoticonMenuAndSelectActivePack
232 [popUp_emoticons setMenu:[self _emoticonPackMenu]];
234 //Update the selected pack
235 NSArray *activeEmoticonPacks = [[adium emoticonController] activeEmoticonPacks];
236 int numActivePacks = [activeEmoticonPacks count];
238 if (numActivePacks == 0) {
239 [popUp_emoticons compatibleSelectItemWithTag:AIEmoticonMenuNone];
240 } else if (numActivePacks > 1) {
241 [popUp_emoticons compatibleSelectItemWithTag:AIEmoticonMenuMultiple];
243 [popUp_emoticons selectItemWithRepresentedObject:[activeEmoticonPacks objectAtIndex:0]];
248 * @brief Save changed preferences
250 - (IBAction)changePreference:(id)sender
252 if (sender == popUp_statusIcons) {
253 [[adium preferenceController] setPreference:[[sender selectedItem] title]
254 forKey:KEY_STATUS_ICON_PACK
255 group:PREF_GROUP_APPEARANCE];
257 } else if (sender == popUp_serviceIcons) {
258 [[adium preferenceController] setPreference:[[sender selectedItem] title]
259 forKey:KEY_SERVICE_ICON_PACK
260 group:PREF_GROUP_APPEARANCE];
262 } else if (sender == popUp_dockIcon) {
263 [[adium preferenceController] setPreference:[[sender selectedItem] representedObject]
264 forKey:KEY_ACTIVE_DOCK_ICON
265 group:PREF_GROUP_APPEARANCE];
267 } else if (sender == popUp_listLayout) {
268 [[adium preferenceController] setPreference:[[sender selectedItem] title]
269 forKey:KEY_LIST_LAYOUT_NAME
270 group:PREF_GROUP_APPEARANCE];
272 } else if (sender == popUp_colorTheme) {
273 [[adium preferenceController] setPreference:[[sender selectedItem] title]
274 forKey:KEY_LIST_THEME_NAME
275 group:PREF_GROUP_APPEARANCE];
277 } else if (sender == popUp_windowStyle) {
278 [[adium preferenceController] setPreference:[NSNumber numberWithInt:[[sender selectedItem] tag]]
279 forKey:KEY_LIST_LAYOUT_WINDOW_STYLE
280 group:PREF_GROUP_APPEARANCE];
282 } else if (sender == checkBox_verticalAutosizing) {
283 [[adium preferenceController] setPreference:[NSNumber numberWithBool:[sender state]]
284 forKey:KEY_LIST_LAYOUT_VERTICAL_AUTOSIZE
285 group:PREF_GROUP_APPEARANCE];
287 } else if (sender == checkBox_horizontalAutosizing) {
288 [[adium preferenceController] setPreference:[NSNumber numberWithBool:[sender state]]
289 forKey:KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE
290 group:PREF_GROUP_APPEARANCE];
292 } else if (sender == slider_windowOpacity) {
293 [[adium preferenceController] setPreference:[NSNumber numberWithFloat:([sender floatValue] / 100.0)]
294 forKey:KEY_LIST_LAYOUT_WINDOW_OPACITY
295 group:PREF_GROUP_APPEARANCE];
296 [self _updateSliderValues];
298 } else if (sender == slider_horizontalWidth) {
299 int newValue = [sender intValue];
300 int oldValue = [[[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_HORIZONTAL_WIDTH
301 group:PREF_GROUP_APPEARANCE] intValue];
302 if (newValue != oldValue) {
303 [[adium preferenceController] setPreference:[NSNumber numberWithInt:newValue]
304 forKey:KEY_LIST_LAYOUT_HORIZONTAL_WIDTH
305 group:PREF_GROUP_APPEARANCE];
306 [self _updateSliderValues];
309 } else if (sender == popUp_emoticons) {
310 if ([[sender selectedItem] tag] != AIEmoticonMenuMultiple) {
311 //Disable all active emoticons
312 NSArray *activePacks = [[[[adium emoticonController] activeEmoticonPacks] mutableCopy] autorelease];
313 NSEnumerator *enumerator = [activePacks objectEnumerator];
314 AIEmoticonPack *pack, *selectedPack;
316 selectedPack = [[sender selectedItem] representedObject];
318 [[adium preferenceController] delayPreferenceChangedNotifications:YES];
320 while ((pack = [enumerator nextObject])) {
321 [[adium emoticonController] setEmoticonPack:pack enabled:NO];
324 //Enable the selected pack
325 if (selectedPack) [[adium emoticonController] setEmoticonPack:selectedPack enabled:YES];
327 [[adium preferenceController] delayPreferenceChangedNotifications:NO];
335 - (void)_updateSliderValues
337 [textField_windowOpacity setStringValue:[NSString stringWithFormat:@"%i%%", (int)[slider_windowOpacity floatValue]]];
338 [textField_horizontalWidthIndicator setStringValue:[NSString stringWithFormat:@"%ipx",[slider_horizontalWidth intValue]]];
342 //Emoticons ------------------------------------------------------------------------------------------------------------
343 #pragma mark Emoticons
347 - (IBAction)customizeEmoticons:(id)sender
349 [AIEmoticonPreferences showEmoticionCustomizationOnWindow:[[self view] window]];
355 - (NSMenu *)_emoticonPackMenu
357 NSMenu *menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
358 NSEnumerator *enumerator = [[[adium emoticonController] availableEmoticonPacks] objectEnumerator];
359 AIEmoticonPack *pack;
360 NSMenuItem *menuItem;
362 //Add the "No Emoticons" option
363 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"None",nil)
366 keyEquivalent:@""] autorelease];
367 [menuItem setImage:[NSImage imageNamed:@"emoticonBlank" forClass:[self class]]];
368 [menuItem setTag:AIEmoticonMenuNone];
369 [menu addItem:menuItem];
371 //Add the "Multiple packs selected" option
372 if ([[[adium emoticonController] activeEmoticonPacks] count] > 1) {
373 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Multiple Packs Selected",nil)
376 keyEquivalent:@""] autorelease];
377 [menuItem setImage:[NSImage imageNamed:@"emoticonBlank" forClass:[self class]]];
378 [menuItem setTag:AIEmoticonMenuMultiple];
379 [menu addItem:menuItem];
383 [menu addItem:[NSMenuItem separatorItem]];
386 while ((pack = [enumerator nextObject])) {
387 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[pack name]
390 keyEquivalent:@""] autorelease];
391 [menuItem setRepresentedObject:pack];
392 [menuItem setImage:[pack menuPreviewImage]];
393 [menu addItem:menuItem];
396 return [menu autorelease];
400 //Contact list options -------------------------------------------------------------------------------------------------
401 #pragma mark Contact list options
405 - (NSMenu *)_windowStyleMenu
407 NSMenu *menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
409 [self _addWindowStyleOption:AILocalizedString(@"Regular Window",nil)
410 withTag:AIContactListWindowStyleStandard
412 [menu addItem:[NSMenuItem separatorItem]];
413 [self _addWindowStyleOption:AILocalizedString(@"Borderless Window",nil)
414 withTag:AIContactListWindowStyleBorderless
416 [self _addWindowStyleOption:AILocalizedString(@"Group Bubbles",nil)
417 withTag:AIContactListWindowStyleGroupBubbles
419 [self _addWindowStyleOption:AILocalizedString(@"Contact Bubbles",nil)
420 withTag:AIContactListWindowStyleContactBubbles
422 [self _addWindowStyleOption:AILocalizedString(@"Contact Bubbles (To Fit)",nil)
423 withTag:AIContactListWindowStyleContactBubbles_Fitted
426 return [menu autorelease];
428 - (void)_addWindowStyleOption:(NSString *)option withTag:(int)tag toMenu:(NSMenu *)menu{
429 NSMenuItem *menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:option
432 keyEquivalent:@""] autorelease];
433 [menuItem setTag:tag];
434 [menu addItem:menuItem];
438 //Contact list layout & theme ----------------------------------------------------------------------------------------
439 #pragma mark Contact list layout & theme
441 + (void) migrateOldListSettingsIfNeeded
443 id<AIPreferenceController> prefController = [[AIObject sharedAdiumInstance] preferenceController];
444 NSFileManager *manager = [NSFileManager defaultManager];
445 NSString *theme = [NSString stringWithFormat:@"%@/%@/%@.%@",
446 [[NSBundle mainBundle] resourcePath],
448 [prefController preferenceForKey:KEY_LIST_THEME_NAME group:PREF_GROUP_APPEARANCE],
449 LIST_THEME_EXTENSION];
450 if(![manager fileExistsAtPath:theme])
452 NSString *oldTheme = [OLD_LIST_SETTINGS_PATH stringByAppendingPathComponent:[theme lastPathComponent]];
453 if([manager fileExistsAtPath:oldTheme])
454 [manager movePath:oldTheme toPath:theme handler:nil];
456 NSString *layout = [NSString stringWithFormat:@"%@/%@/%@.%@",
457 [[NSBundle mainBundle] resourcePath],
459 [prefController preferenceForKey:KEY_LIST_LAYOUT_NAME group:PREF_GROUP_APPEARANCE],
461 if(![manager fileExistsAtPath:layout])
463 NSString *oldLayout = [OLD_LIST_SETTINGS_PATH stringByAppendingPathComponent:[layout lastPathComponent]];
464 if([manager fileExistsAtPath:oldLayout])
465 [manager movePath:oldLayout toPath:layout handler:nil];
470 * @brief Create a new theme
472 - (IBAction)createListTheme:(id)sender
474 NSString *theme = [[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME group:PREF_GROUP_APPEARANCE];
476 [ESPresetNameSheetController showPresetNameSheetWithDefaultName:[theme stringByAppendingString:@" Copy"]
477 explanatoryText:AILocalizedString(@"Enter a unique name for this new theme.",nil)
478 onWindow:[[self view] window]
484 * @brief Customize the active theme
486 - (IBAction)customizeListTheme:(id)sender
488 NSString *theme = [[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME group:PREF_GROUP_APPEARANCE];
490 //Allow alpha in our color pickers
491 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
493 [AIListThemeWindowController editListThemeWithName:theme
494 onWindow:[[self view] window]
495 notifyingTarget:self];
499 * @brief Save (or revert) changes made when editing a theme
501 - (void)listThemeEditorWillCloseWithChanges:(BOOL)saveChanges forThemeNamed:(NSString *)name
504 //Update the modified theme
505 if ([plugin createSetFromPreferenceGroup:PREF_GROUP_LIST_THEME
507 extension:LIST_THEME_EXTENSION
508 inFolder:LIST_THEME_FOLDER]) {
510 [[adium preferenceController] setPreference:name
511 forKey:KEY_LIST_THEME_NAME
512 group:PREF_GROUP_APPEARANCE];
516 //Revert back to selected theme
517 NSString *theme = [[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME group:PREF_GROUP_APPEARANCE];
519 [plugin applySetWithName:theme
520 extension:LIST_THEME_EXTENSION
521 inFolder:LIST_THEME_FOLDER
522 toPreferenceGroup:PREF_GROUP_LIST_THEME];
526 //No longer allow alpha in our color pickers
527 [[NSColorPanel sharedColorPanel] setShowsAlpha:NO];
531 * @brief Manage available themes
533 - (void)manageListThemes:(id)sender
535 _listThemes = [plugin availableThemeSets];
536 [ESPresetManagementController managePresets:_listThemes
538 onWindow:[[self view] window]
541 [popUp_colorTheme selectItemWithRepresentedObject:[[adium preferenceController] preferenceForKey:KEY_LIST_THEME_NAME
542 group:PREF_GROUP_APPEARANCE]];
546 * @brief Create a new layout
548 - (IBAction)createListLayout:(id)sender
550 NSString *layout = [[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME group:PREF_GROUP_APPEARANCE];
552 [ESPresetNameSheetController showPresetNameSheetWithDefaultName:[layout stringByAppendingString:@" Copy"]
553 explanatoryText:AILocalizedString(@"Enter a unique name for this new layout.",nil)
554 onWindow:[[self view] window]
560 * @brief Customize the active layout
562 - (IBAction)customizeListLayout:(id)sender
564 NSString *theme = [[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME group:PREF_GROUP_APPEARANCE];
566 //Allow alpha in our color pickers
567 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
569 [AIListLayoutWindowController editListLayoutWithName:theme
570 onWindow:[[self view] window]
571 notifyingTarget:self];
575 * @brief Save (or revert) changes made when editing a layout
577 - (void)listLayoutEditorWillCloseWithChanges:(BOOL)saveChanges forLayoutNamed:(NSString *)name
580 //Update the modified layout
581 if ([plugin createSetFromPreferenceGroup:PREF_GROUP_LIST_LAYOUT
583 extension:LIST_LAYOUT_EXTENSION
584 inFolder:LIST_LAYOUT_FOLDER]) {
586 [[adium preferenceController] setPreference:name
587 forKey:KEY_LIST_LAYOUT_NAME
588 group:PREF_GROUP_APPEARANCE];
592 //Revert back to selected layout
593 NSString *layout = [[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME group:PREF_GROUP_APPEARANCE];
595 [plugin applySetWithName:layout
596 extension:LIST_LAYOUT_EXTENSION
597 inFolder:LIST_LAYOUT_FOLDER
598 toPreferenceGroup:PREF_GROUP_LIST_LAYOUT];
601 //No longer allow alpha in our color pickers
602 [[NSColorPanel sharedColorPanel] setShowsAlpha:NO];
606 * @brief Manage available layouts
608 - (void)manageListLayouts:(id)sender
610 _listLayouts = [plugin availableLayoutSets];
611 [ESPresetManagementController managePresets:_listLayouts
613 onWindow:[[self view] window]
616 [popUp_listLayout selectItemWithRepresentedObject:[[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME
617 group:PREF_GROUP_APPEARANCE]];
621 * @brief Validate a layout or theme name to ensure it is unique
623 - (BOOL)presetNameSheetController:(ESPresetNameSheetController *)controller
624 shouldAcceptNewName:(NSString *)newName
625 userInfo:(id)userInfo
627 NSEnumerator *enumerator;
628 NSDictionary *presetDict;
630 //Scan the correct presets to ensure this name doesn't already exist
631 if ([userInfo isEqualToString:@"theme"]) {
632 enumerator = [[plugin availableThemeSets] objectEnumerator];
634 enumerator = [[plugin availableLayoutSets] objectEnumerator];
637 while ((presetDict = [enumerator nextObject])) {
638 if ([newName isEqualToString:[presetDict objectForKey:@"name"]]) return NO;
645 * @brief Create a new theme with the user supplied name, activate and edit it
647 - (void)presetNameSheetControllerDidEnd:(ESPresetNameSheetController *)controller
648 returnCode:(ESPresetNameSheetReturnCode)returnCode
649 newName:(NSString *)newName
650 userInfo:(id)userInfo
652 switch (returnCode) {
653 case ESPresetNameSheetOkayReturn:
654 if ([userInfo isEqualToString:@"theme"]) {
655 [self performSelector:@selector(_editListThemeWithName:) withObject:newName afterDelay:0.00001];
657 [self performSelector:@selector(_editListLayoutWithName:) withObject:newName afterDelay:0.00001];
661 case ESPresetNameSheetCancelReturn:
666 - (void)_editListThemeWithName:(NSString *)name{
667 [AIListThemeWindowController editListThemeWithName:name
668 onWindow:[[self view] window]
669 notifyingTarget:self];
671 - (void)_editListLayoutWithName:(NSString *)name{
672 [AIListLayoutWindowController editListLayoutWithName:name
673 onWindow:[[self view] window]
674 notifyingTarget:self];
680 - (NSArray *)renamePreset:(NSDictionary *)preset toName:(NSString *)newName inPresets:(NSArray *)presets renamedPreset:(id *)renamedPreset
684 if (presets == _listLayouts) {
685 [plugin renameSetWithName:[preset objectForKey:@"name"]
686 extension:LIST_LAYOUT_EXTENSION
687 inFolder:LIST_LAYOUT_FOLDER
689 _listLayouts = [plugin availableLayoutSets];
690 newPresets = _listLayouts;
692 } else if (presets == _listThemes) {
693 [plugin renameSetWithName:[preset objectForKey:@"name"]
694 extension:LIST_THEME_EXTENSION
695 inFolder:LIST_THEME_FOLDER
697 _listThemes = [plugin availableThemeSets];
698 newPresets = _listThemes;
704 //Return the new duplicate by reference for the preset controller
706 NSEnumerator *enumerator = [newPresets objectEnumerator];
707 NSDictionary *aPreset;
709 while ((aPreset = [enumerator nextObject])) {
710 if ([newName isEqualToString:[aPreset objectForKey:@"name"]]) {
711 *renamedPreset = aPreset;
723 - (NSArray *)duplicatePreset:(NSDictionary *)preset inPresets:(NSArray *)presets createdDuplicate:(id *)duplicatePreset
725 NSString *newName = [NSString stringWithFormat:@"%@ (%@)", [preset objectForKey:@"name"], AILocalizedString(@"Copy",nil)];
726 NSArray *newPresets = nil;
728 if (presets == _listLayouts) {
729 [plugin duplicateSetWithName:[preset objectForKey:@"name"]
730 extension:LIST_LAYOUT_EXTENSION
731 inFolder:LIST_LAYOUT_FOLDER
733 _listLayouts = [plugin availableLayoutSets];
734 newPresets = _listLayouts;
736 } else if (presets == _listThemes) {
737 [plugin duplicateSetWithName:[preset objectForKey:@"name"]
738 extension:LIST_THEME_EXTENSION
739 inFolder:LIST_THEME_FOLDER
741 _listThemes = [plugin availableThemeSets];
742 newPresets = _listThemes;
745 //Return the new duplicate by reference for the preset controller
746 if (duplicatePreset) {
747 NSEnumerator *enumerator = [newPresets objectEnumerator];
748 NSDictionary *aPreset;
750 while ((aPreset = [enumerator nextObject])) {
751 if ([newName isEqualToString:[aPreset objectForKey:@"name"]]) {
752 *duplicatePreset = aPreset;
764 - (NSArray *)deletePreset:(NSDictionary *)preset inPresets:(NSArray *)presets
766 if (presets == _listLayouts) {
767 [plugin deleteSetWithName:[preset objectForKey:@"name"]
768 extension:LIST_LAYOUT_EXTENSION
769 inFolder:LIST_LAYOUT_FOLDER];
770 _listLayouts = [plugin availableLayoutSets];
774 } else if (presets == _listThemes) {
775 [plugin deleteSetWithName:[preset objectForKey:@"name"]
776 extension:LIST_THEME_EXTENSION
777 inFolder:LIST_THEME_FOLDER];
778 _listThemes = [plugin availableThemeSets];
790 - (NSMenu *)_listLayoutMenu
792 NSMenu *menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
793 NSEnumerator *enumerator = [[plugin availableLayoutSets] objectEnumerator];
795 NSMenuItem *menuItem;
799 while ((set = [enumerator nextObject])) {
800 name = [set objectForKey:@"name"];
801 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
804 keyEquivalent:@""] autorelease];
805 [menuItem setRepresentedObject:name];
806 [menu addItem:menuItem];
810 [menu addItem:[NSMenuItem separatorItem]];
813 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Add New Layout",nil) stringByAppendingEllipsis]
815 action:@selector(createListLayout:)
816 keyEquivalent:@""] autorelease];
817 [menu addItem:menuItem];
819 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Edit Layouts",nil) stringByAppendingEllipsis]
821 action:@selector(manageListLayouts:)
822 keyEquivalent:@""] autorelease];
823 [menu addItem:menuItem];
831 - (NSMenu *)_colorThemeMenu
833 NSMenu *menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
834 NSEnumerator *enumerator = [[plugin availableThemeSets] objectEnumerator];
836 NSMenuItem *menuItem;
840 while ((set = [enumerator nextObject])) {
841 name = [set objectForKey:@"name"];
842 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
845 keyEquivalent:@""] autorelease];
846 [menuItem setRepresentedObject:name];
847 [menu addItem:menuItem];
851 [menu addItem:[NSMenuItem separatorItem]];
854 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Add New Theme",nil) stringByAppendingEllipsis]
856 action:@selector(createListTheme:)
857 keyEquivalent:@""] autorelease];
858 [menu addItem:menuItem];
860 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[AILocalizedString(@"Edit Themes",nil) stringByAppendingEllipsis]
862 action:@selector(manageListThemes:)
863 keyEquivalent:@""] autorelease];
864 [menu addItem:menuItem];
870 //Dock icons -----------------------------------------------------------------------------------------------------------
871 #pragma mark Dock icons
875 - (IBAction)showAllDockIcons:(id)sender
877 [AIDockIconSelectionSheet showDockIconSelectorOnWindow:[[self view] window]];
881 * @brief Return the menu item for a dock icon
883 - (NSMenuItem *)meuItemForDockIconPackAtPath:(NSString *)packPath
885 NSMenuItem *menuItem;
886 NSString *name = nil;
887 NSString *packName = [[packPath lastPathComponent] stringByDeletingPathExtension];
888 AIIconState *preview = nil;
890 [[adium dockController] getName:&name
891 previewState:&preview
892 forIconPackAtPath:packPath];
898 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
901 keyEquivalent:@""] autorelease];
902 [menuItem setRepresentedObject:packName];
903 [menuItem setImage:[[preview image] imageByScalingForMenuItem]];
909 * @brief Returns an array of menu items of all dock icon packs
911 - (NSArray *)_dockIconMenuArray
913 NSMutableArray *menuItemArray = [NSMutableArray array];
914 NSEnumerator *enumerator;
917 enumerator = [[[adium dockController] availableDockIconPacks] objectEnumerator];
918 while ((packPath = [enumerator nextObject])) {
919 [menuItemArray addObject:[self meuItemForDockIconPackAtPath:packPath]];
922 [menuItemArray sortUsingSelector:@selector(titleCompare:)];
924 return menuItemArray;
928 * @brief Configure the dock icon meu initially or after the xtras change
930 * Initially, the dock icon menu just has the currently selected icon; the others will be generated lazily if the icon is displayed, in menuNeedsUpdate:
932 - (void)configureDockIconMenu
934 NSMenu *tempMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
936 NSString *activePackName = [[adium preferenceController] preferenceForKey:KEY_ACTIVE_DOCK_ICON
937 group:PREF_GROUP_APPEARANCE];
938 iconPath = [adium pathOfPackWithName:activePackName
939 extension:@"AdiumIcon"
940 resourceFolderName:FOLDER_DOCK_ICONS];
942 [tempMenu addItem:[self meuItemForDockIconPackAtPath:iconPath]];
943 [tempMenu setDelegate:self];
944 [tempMenu setTitle:@"Temporary Dock Icon Menu"];
946 [popUp_dockIcon setMenu:tempMenu];
947 [popUp_dockIcon selectItemWithRepresentedObject:activePackName];
950 //Status and Service icons ---------------------------------------------------------------------------------------------
951 #pragma mark Status and service icons
952 - (NSMenuItem *)meuItemForIconPackAtPath:(NSString *)packPath class:(Class)iconClass
954 NSString *name = [[packPath lastPathComponent] stringByDeletingPathExtension];
955 NSMenuItem *menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:name
958 keyEquivalent:@""] autorelease];
959 [menuItem setRepresentedObject:name];
960 [menuItem setImage:[iconClass previewMenuImageForIconPackAtPath:packPath]];
966 * @brief Builds and returns an icon pack menu
968 * @param packs NSArray of icon pack file paths
969 * @param iconClass The controller class (AIStatusIcons, AIServiceIcons) for icon pack previews
971 - (NSArray *)_iconPackMenuArrayForPacks:(NSArray *)packs class:(Class)iconClass
973 NSMutableArray *menuItemArray = [NSMutableArray array];
974 NSEnumerator *enumerator = [packs objectEnumerator];
977 while ((packPath = [enumerator nextObject])) {
978 [menuItemArray addObject:[self meuItemForIconPackAtPath:packPath class:iconClass]];
981 [menuItemArray sortUsingSelector:@selector(titleCompare:)];
983 return menuItemArray;
986 - (void)configureStatusIconsMenu
988 NSMenu *tempMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
990 NSString *activePackName = [[adium preferenceController] preferenceForKey:KEY_STATUS_ICON_PACK
991 group:PREF_GROUP_APPEARANCE];
992 iconPath = [adium pathOfPackWithName:activePackName
993 extension:@"AdiumStatusIcons"
994 resourceFolderName:@"Status Icons"];
996 activePackName = [[adium preferenceController] defaultPreferenceForKey:KEY_STATUS_ICON_PACK
997 group:PREF_GROUP_APPEARANCE
1000 iconPath = [adium pathOfPackWithName:activePackName
1001 extension:@"AdiumStatusIcons"
1002 resourceFolderName:@"Status Icons"];
1004 [tempMenu addItem:[self meuItemForIconPackAtPath:iconPath class:[AIStatusIcons class]]];
1005 [tempMenu setDelegate:self];
1006 [tempMenu setTitle:@"Temporary Status Icons Menu"];
1008 [popUp_statusIcons setMenu:tempMenu];
1009 [popUp_statusIcons selectItemWithRepresentedObject:activePackName];
1012 - (void)configureServiceIconsMenu
1014 NSMenu *tempMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];
1016 NSString *activePackName = [[adium preferenceController] preferenceForKey:KEY_SERVICE_ICON_PACK
1017 group:PREF_GROUP_APPEARANCE];
1018 iconPath = [adium pathOfPackWithName:activePackName
1019 extension:@"AdiumServiceIcons"
1020 resourceFolderName:@"Service Icons"];
1023 activePackName = [[adium preferenceController] defaultPreferenceForKey:KEY_SERVICE_ICON_PACK
1024 group:PREF_GROUP_APPEARANCE
1027 iconPath = [adium pathOfPackWithName:activePackName
1028 extension:@"AdiumServiceIcons"
1029 resourceFolderName:@"Service Icons"];
1031 [tempMenu addItem:[self meuItemForIconPackAtPath:iconPath class:[AIServiceIcons class]]];
1032 [tempMenu setDelegate:self];
1033 [tempMenu setTitle:@"Temporary Service Icons Menu"];
1035 [popUp_serviceIcons setMenu:tempMenu];
1036 [popUp_serviceIcons selectItemWithRepresentedObject:activePackName];
1039 #pragma mark Menu delegate
1040 - (void)menuNeedsUpdate:(NSMenu *)menu
1042 NSString *title =[menu title];
1043 NSString *repObject = nil;
1044 NSArray *menuItemArray = nil;
1045 NSPopUpButton *popUpButton;
1047 if ([title isEqualToString:@"Temporary Dock Icon Menu"]) {
1048 //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
1049 menuItemArray = [self _dockIconMenuArray];
1050 repObject = [[adium preferenceController] preferenceForKey:KEY_ACTIVE_DOCK_ICON
1051 group:PREF_GROUP_APPEARANCE];
1052 popUpButton = popUp_dockIcon;
1054 } else if ([title isEqualToString:@"Temporary Status Icons Menu"]) {
1055 menuItemArray = [self _iconPackMenuArrayForPacks:[adium allResourcesForName:@"Status Icons"
1056 withExtensions:@"AdiumStatusIcons"]
1057 class:[AIStatusIcons class]];
1058 repObject = [[adium preferenceController] preferenceForKey:KEY_STATUS_ICON_PACK
1059 group:PREF_GROUP_APPEARANCE];
1060 popUpButton = popUp_statusIcons;
1062 } else if ([title isEqualToString:@"Temporary Service Icons Menu"]) {
1063 menuItemArray = [self _iconPackMenuArrayForPacks:[adium allResourcesForName:@"Service Icons"
1064 withExtensions:@"AdiumServiceIcons"]
1065 class:[AIServiceIcons class]];
1066 repObject = [[adium preferenceController] preferenceForKey:KEY_SERVICE_ICON_PACK
1067 group:PREF_GROUP_APPEARANCE];
1068 popUpButton = popUp_serviceIcons;
1072 if (menuItemArray) {
1073 NSEnumerator *enumerator;
1074 NSMenuItem *menuItem;
1076 //Remove existing items
1077 [menu removeAllItems];
1079 //Clear the title so we know we don't need to do this again
1080 [menu setTitle:@""];
1083 enumerator = [menuItemArray objectEnumerator];
1084 while ((menuItem = [enumerator nextObject])) {
1085 [menu addItem:menuItem];
1088 //Clear the title so we know we don't need to do this again
1089 [menu setTitle:@""];
1091 //Put a checkmark by the appropriate menu item
1092 [popUpButton selectItemWithRepresentedObject:repObject];