* 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 "ESContactListAdvancedPreferences.h" #import "AISCLViewPlugin.h" #import "AIAppearancePreferencesPlugin.h" #import <AIUtilities/AIMenuAdditions.h> #import <AIUtilities/AIPopUpButtonAdditions.h> #import <AIUtilities/AIStringAdditions.h> #import "AIPreferenceWindowController.h" #import "AIListWindowController.h" #import "AIListLayoutWindowController.h" #import "AIListThemeWindowController.h" #import <Adium/AIDockControllerProtocol.h> #import <Adium/AIInterfaceControllerProtocol.h> #import <AIUtilities/AIDictionaryAdditions.h> #import <AIUtilities/AIImageAdditions.h> #import <Adium/ESPresetManagementController.h> #import <Adium/ESPresetNameSheetController.h> #import <Adium/AISortController.h> #import "AIHideAccountsWindowController.h" #import <Adium/AIListContact.h> #import <Adium/AIListBookmark.h> #import "AIContactController.h" #import "AIAliasSupportPlugin.h" #import "AIContactObserverManager.h" #import "AIXtrasManager.h" @interface ESContactListAdvancedPreferences () - ( NSMenu * ) _windowStyleMenu ; - ( NSMenu * ) _listLayoutMenu ; - ( NSMenu * ) _colorThemeMenu ; - ( void ) _addWindowStyleOption: ( NSString * ) option withTag: ( NSInteger ) tag toMenu: ( NSMenu * ) menu ; - ( void ) _updateSliderValues ; - ( void ) _editListThemeWithName: ( NSString * ) name ; - ( void ) _editListLayoutWithName: ( NSString * ) name ; - ( void ) xtrasChanged: ( NSNotification * ) notification ; - ( void ) applySetWithName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder toPreferenceGroup: ( NSString * ) preferenceGroup ; - ( BOOL ) createSetFromPreferenceGroup: ( NSString * ) preferenceGroup withName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder ; - ( BOOL ) deleteSetWithName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder ; - ( BOOL ) renameSetWithName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder toName: ( NSString * ) newName ; - ( BOOL ) duplicateSetWithName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder newName: ( NSString * ) newName ; - ( NSArray * ) availableSetsWithExtension: ( NSString * ) extension fromFolder: ( NSString * ) folder ; - ( NSArray * ) availableLayoutSets ; - ( NSArray * ) availableThemeSets ; #define PREF_GROUP_CONTACT_SORTING @"Sorting" #define KEY_CURRENT_SORT_MODE_IDENTIFIER @"Sort Mode" #define CONTACT_NAME_MENU_TITLE AILocalizedString(@"Contact Name Format",nil) #define ALIAS AILocalizedString(@"Alias",nil) #define ALIAS_SCREENNAME AILocalizedString(@"Alias (User Name)",nil) #define SCREENNAME_ALIAS AILocalizedString(@"User Name (Alias)",nil) #define SCREENNAME AILocalizedString(@"User Name",nil) * @class ESContactListAdvancedPreferences * @brief Advanced contact list preferences @implementation ESContactListAdvancedPreferences - ( AIPreferenceCategory ) category { return AIPref_Appearance ; - ( NSString * ) paneIdentifier { return AILocalizedString ( @"Contact List" , "Name of the window which lists contacts" ); return @"Preferences-ContactList" ; return [ NSImage imageNamed : @"pref-contact-list" forClass : [ AIPreferenceWindowController class ]]; * @brief View loaded; configure it for display [ popUp_windowStyle setMenu : [ self _windowStyleMenu ]]; [ popUp_contactNameFormat setMenu : [ self _contactNameMenu ]]; [ popUp_sortContacts setMenu : [ self _sortMenu ]]; //Observe preference changes [ adium . preferenceController registerPreferenceObserver : self forGroup : PREF_GROUP_APPEARANCE ]; [ adium . preferenceController registerPreferenceObserver : self forGroup : PREF_GROUP_DISPLAYFORMAT ]; [ adium . preferenceController registerPreferenceObserver : self forGroup : PREF_GROUP_CONTACT_SORTING ]; [[ NSNotificationCenter defaultCenter ] addObserver : self selector : @selector ( xtrasChanged : ) name : AIXtrasDidChangeNotification [ label_sortContacts setLocalizedString : AILocalizedString ( @"Sort Contacts:" , nil )]; [ label_contactNameFormat setLocalizedString : AILocalizedString ( @"Contact Name Format:" , nil )]; [ label_contactHiding setLocalizedString : AILocalizedString ( @"Hiding:" , nil )]; [ label_contactGroups setLocalizedString : AILocalizedString ( @"Groups:" , nil )]; [ button_sortContacts setLocalizedString : AILocalizedString ( @"Customize…" , nil )]; [ button_hideAccounts setLocalizedString : AILocalizedString ( @"Hide Contacts for Accounts…" , nil )]; [ checkBox_hideCertainContacts setLocalizedString : AILocalizedString ( @"Hide Certain Contacts" , nil )]; [ checkBox_hideOffline setLocalizedString : AILocalizedString ( @"Hide Offline Contacts" , nil )]; [ checkBox_hideIdle setLocalizedString : AILocalizedString ( @"Hide Idle Contacts" , nil )]; [ checkBox_hideMobile setLocalizedString : AILocalizedString ( @"Hide Mobile Contacts" , nil )]; [ checkBox_hideBlocked setLocalizedString : AILocalizedString ( @"Hide Blocked Contacts" , nil )]; [ checkBox_hideAway setLocalizedString : AILocalizedString ( @"Hide Away Contacts" , nil )]; [ checkBox_showGroups setLocalizedString : AILocalizedString ( @"Show Groups" , nil )]; [ checkBox_groupOfflineContacts setLocalizedString : AILocalizedString ( @"Use Offline Group" , nil )]; [ checkBox_showGroupOnlineCount setLocalizedString : AILocalizedString ( @"Show Group Online Count" , nil )]; [ checkBox_showGroupTotalCount setLocalizedString : AILocalizedString ( @"Show Group Total Count" , nil )]; [ label_animation setLocalizedString : AILocalizedString ( @"Animation:" , nil )]; [ label_automaticSizing setLocalizedString : AILocalizedString ( @"Automatic Sizing:" , nil )]; [ label_colorTheme setLocalizedString : AILocalizedString ( @"Color Theme:" , nil )]; [ label_horizontalWidth setLocalizedString : AILocalizedString ( @"Maximum Width:" , nil )]; [ label_verticalHeight setLocalizedString : AILocalizedString ( @"Maximum Height:" , nil )]; [ label_listLayout setLocalizedString : AILocalizedString ( @"List Layout:" , nil )]; [ label_opacity setLocalizedString : AILocalizedString ( @"Opacity:" , nil )]; [ label_tooltips setLocalizedString : AILocalizedString ( @"Tooltips:" , nil )]; [ label_windowStyle setLocalizedString : AILocalizedString ( @"Window Style:" , nil )]; [ button_colorTheme setLocalizedString : AILocalizedString ( @"Customize…" , nil )]; [ button_listLayout setLocalizedString : AILocalizedString ( @"Customize…" , nil )]; [ checkBox_animateChanges setLocalizedString : AILocalizedString ( @"Animate changes" , "This string is under the heading 'Contact List' and refers to changes such as sort order in the contact list being animated rather than occurring instantenously" )]; [ checkBox_flash setLocalizedString : AILocalizedString ( @"Flash names with unviewed messages" , nil )]; [ checkBox_horizontalAutosizing setLocalizedString : AILocalizedString ( @"Size to fit horizontally" , nil )]; [ checkBox_verticalAutosizing setLocalizedString : AILocalizedString ( @"Size to fit vertically" , nil )]; [ checkBox_showTooltips setLocalizedString : AILocalizedString ( @"Show contact information tooltips" , nil )]; [ checkBox_showTooltipsInBackground setLocalizedString : AILocalizedString ( @"While Adium is in the background" , "Checkbox to indicate that something should occur while Adium is not the active application" )]; [ checkBox_verticalAutosizing setLocalizedString : AILocalizedString ( @"Size to fit vertically" , nil )]; [ checkBox_windowHasShadow setLocalizedString : AILocalizedString ( @"Show window shadow" , nil )]; [ checkBox_windowHasShadow setToolTip : @"Stay close to the Vorlon." ]; * @brief Xtras changed, update our menus to reflect the new Xtras - ( void ) xtrasChanged: ( NSNotification * ) notification NSString * filenameExtension = [ notification object ]; //Convert our filename extension into a Uniform Type Identifier so that we can robustly determine what type of Xtra this is. CFStringRef type = ( CFStringRef )[( NSString * ) UTTypeCreatePreferredIdentifierForTag ( kUTTagClassFilenameExtension , ( CFStringRef ) filenameExtension , /*inConformingToUTI*/ NULL ) autorelease ]; if ( ! type || UTTypeEqual ( type , CFSTR ( "com.adiumx.contactlisttheme" ))) { [ popUp_colorTheme setMenu : [ self _colorThemeMenu ]]; [ popUp_colorTheme selectItemWithRepresentedObject : [ adium . preferenceController preferenceForKey : KEY_LIST_THEME_NAME group : PREF_GROUP_APPEARANCE ]]; if ( ! type || UTTypeEqual ( type , CFSTR ( "com.adiumx.contactlistlayout" ))) { [ popUp_listLayout setMenu : [ self _listLayoutMenu ]]; [ popUp_listLayout selectItemWithRepresentedObject : [ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_NAME group : PREF_GROUP_APPEARANCE ]]; * @brief Preferences changed * Update controls in our view to reflect the changed preferences - ( void ) preferencesChangedForGroup: ( NSString * ) group key: ( NSString * ) key object: ( AIListObject * ) object preferenceDict :( NSDictionary * ) prefDict firstTime: ( BOOL ) firstTime if ([ group isEqualToString : PREF_GROUP_APPEARANCE ]) { [ popUp_windowStyle selectItemWithTag : [[ prefDict objectForKey : KEY_LIST_LAYOUT_WINDOW_STYLE ] integerValue ]]; [ slider_windowOpacity setDoubleValue : ([[ prefDict objectForKey : KEY_LIST_LAYOUT_WINDOW_OPACITY ] doubleValue ] * 100.0 )]; [ slider_horizontalWidth setIntegerValue : [[ prefDict objectForKey : KEY_LIST_LAYOUT_HORIZONTAL_WIDTH ] integerValue ]]; [ slider_verticalHeight setIntegerValue : [[ prefDict objectForKey : KEY_LIST_LAYOUT_VERTICAL_HEIGHT ] integerValue ]]; [ self _updateSliderValues ]; //Horizontal resizing label [ key isEqualToString : KEY_LIST_LAYOUT_WINDOW_STYLE ] || [ key isEqualToString : KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE ] || [ key isEqualToString : KEY_LIST_LAYOUT_VERTICAL_AUTOSIZE ]) { AIContactListWindowStyle windowStyle = [[ prefDict objectForKey : KEY_LIST_LAYOUT_WINDOW_STYLE ] intValue ]; BOOL horizontalAutosize = [[ prefDict objectForKey : KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE ] boolValue ]; BOOL verticalAutosize = [[ prefDict objectForKey : KEY_LIST_LAYOUT_VERTICAL_AUTOSIZE ] boolValue ]; [ label_horizontalWidth setLocalizedString : AILocalizedString ( @"Maximum Width:" , nil )]; [ label_verticalHeight setLocalizedString : AILocalizedString ( @"Maximum Height:" , nil )]; if ( windowStyle == AIContactListWindowStyleStandard ) { //In standard mode, disable the autosizing sliders if their respective autosize is off [ label_horizontalWidth setLocalizedString : AILocalizedString ( @"Maximum Width:" , nil )]; [ slider_horizontalWidth setEnabled : horizontalAutosize ]; [ slider_verticalHeight setEnabled : verticalAutosize ]; //In all the borderless transparent modes, the autosizing sliders control the fixed size [ label_horizontalWidth setLocalizedString : AILocalizedString ( @"Width:" , nil )]; [ label_verticalHeight setLocalizedString : AILocalizedString ( @"Height:" , nil )]; [ slider_horizontalWidth setEnabled : YES ]; [ slider_verticalHeight setEnabled : YES ]; //Configure the silders' appearance. AIListWindowController must match this behavior for this to make sense. case AIContactListWindowStyleStandard : case AIContactListWindowStyleBorderless : case AIContactListWindowStyleGroupChat : //Standard and borderless don't have to vertically autosize [ checkBox_verticalAutosizing setEnabled : YES ]; [ checkBox_verticalAutosizing setState : [[ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_VERTICAL_AUTOSIZE group : PREF_GROUP_APPEARANCE ] integerValue ]]; [ checkBox_horizontalAutosizing setEnabled : YES ]; [ checkBox_horizontalAutosizing setState : [[ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE group : PREF_GROUP_APPEARANCE ] integerValue ]]; case AIContactListWindowStyleGroupBubbles : case AIContactListWindowStyleContactBubbles : case AIContactListWindowStyleContactBubbles_Fitted : //The bubbles styles don't show a window; force them to autosize [ checkBox_verticalAutosizing setEnabled : NO ]; [ checkBox_verticalAutosizing setState : YES ]; [ checkBox_horizontalAutosizing setEnabled : NO ]; [ checkBox_horizontalAutosizing setState : YES ]; if ( firstTime || [ key isEqualToString : KEY_LIST_LAYOUT_NAME ]) { [ popUp_listLayout selectItemWithRepresentedObject : [ prefDict objectForKey : KEY_LIST_LAYOUT_NAME ]]; if ( firstTime || [ key isEqualToString : KEY_LIST_THEME_NAME ]) { [ popUp_colorTheme selectItemWithRepresentedObject : [ prefDict objectForKey : KEY_LIST_THEME_NAME ]]; } else if ([ group isEqualToString : PREF_GROUP_DISPLAYFORMAT ]) { NSInteger displayFormat = [[ prefDict objectForKey : @"Long Display Format" ] integerValue ]; [ popUp_contactNameFormat selectItemWithTag : displayFormat ]; } else if ([ group isEqualToString : PREF_GROUP_CONTACT_SORTING ]) { AISortController * activeSortController = [ AISortController activeSortController ]; [ popUp_sortContacts selectItemWithRepresentedObject : activeSortController ]; //Disable customize sort button if ([ activeSortController configureSortWindowTitle ]) [ button_sortContacts setEnabled : YES ]; [ button_sortContacts setEnabled : NO ]; * @brief Save changed preferences - ( IBAction ) changePreference: ( id ) sender if ( sender == popUp_listLayout ) { [ adium . preferenceController setPreference : [[ sender selectedItem ] title ] forKey : KEY_LIST_LAYOUT_NAME group : PREF_GROUP_APPEARANCE ]; } else if ( sender == popUp_colorTheme ) { [ adium . preferenceController setPreference : [[ sender selectedItem ] title ] forKey : KEY_LIST_THEME_NAME group : PREF_GROUP_APPEARANCE ]; } else if ( sender == popUp_windowStyle ) { [ adium . preferenceController setPreference : [ NSNumber numberWithInteger : [[ sender selectedItem ] tag ]] forKey : KEY_LIST_LAYOUT_WINDOW_STYLE group : PREF_GROUP_APPEARANCE ]; } else if ( sender == checkBox_verticalAutosizing ) { [ adium . preferenceController setPreference : [ NSNumber numberWithBool : [ sender state ]] forKey : KEY_LIST_LAYOUT_VERTICAL_AUTOSIZE group : PREF_GROUP_APPEARANCE ]; } else if ( sender == checkBox_horizontalAutosizing ) { [ adium . preferenceController setPreference : [ NSNumber numberWithBool : [ sender state ]] forKey : KEY_LIST_LAYOUT_HORIZONTAL_AUTOSIZE group : PREF_GROUP_APPEARANCE ]; } else if ( sender == slider_windowOpacity ) { [ adium . preferenceController setPreference : [ NSNumber numberWithDouble : ([ sender doubleValue ] / 100.0 )] forKey : KEY_LIST_LAYOUT_WINDOW_OPACITY group : PREF_GROUP_APPEARANCE ]; [ self _updateSliderValues ]; } else if ( sender == slider_horizontalWidth ) { NSInteger newValue = [ sender integerValue ]; NSInteger oldValue = [[ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_HORIZONTAL_WIDTH group : PREF_GROUP_APPEARANCE ] integerValue ]; if ( newValue != oldValue ) { [ adium . preferenceController setPreference : [ NSNumber numberWithInteger : newValue ] forKey : KEY_LIST_LAYOUT_HORIZONTAL_WIDTH group : PREF_GROUP_APPEARANCE ]; [ self _updateSliderValues ]; } else if ( sender == slider_verticalHeight ) { NSInteger newValue = [ sender integerValue ]; NSInteger oldValue = [[ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_VERTICAL_HEIGHT group : PREF_GROUP_APPEARANCE ] integerValue ]; if ( newValue != oldValue ) { [ adium . preferenceController setPreference : [ NSNumber numberWithInteger : newValue ] forKey : KEY_LIST_LAYOUT_VERTICAL_HEIGHT group : PREF_GROUP_APPEARANCE ]; [ self _updateSliderValues ]; } else if ( sender == popUp_sortContacts ) { AISortController * controller = [[ sender selectedItem ] representedObject ]; //Inform the contact controller of the new active sort controller [ AISortController setActiveSortController : controller ]; [ adium . preferenceController setPreference : [ controller identifier ] forKey : KEY_CURRENT_SORT_MODE_IDENTIFIER group : PREF_GROUP_CONTACT_SORTING ]; } else if ( sender == popUp_contactNameFormat ) { [ adium . preferenceController setPreference : [ NSNumber numberWithInteger : [[ sender selectedItem ] tag ]] forKey : @"Long Display Format" group : PREF_GROUP_DISPLAYFORMAT ]; - ( void ) _updateSliderValues [ textField_windowOpacity setStringValue : [ NSString stringWithFormat : @"%ld%%" , ( NSInteger )[ slider_windowOpacity doubleValue ]]]; [ textField_horizontalWidth setStringValue : [ NSString stringWithFormat : @"%ldpx" ,[ slider_horizontalWidth integerValue ]]]; [ textField_verticalHeight setStringValue : [ NSString stringWithFormat : @"%ldpx" ,[ slider_verticalHeight integerValue ]]]; //Contact list options ------------------------------------------------------------------------------------------------- #pragma mark Contact list options - ( NSMenu * ) _windowStyleMenu NSMenu * menu = [[ NSMenu allocWithZone : [ NSMenu menuZone ]] init ]; [ self _addWindowStyleOption : AILocalizedString ( @"Regular Window" , nil ) withTag : AIContactListWindowStyleStandard [ menu addItem : [ NSMenuItem separatorItem ]]; [ self _addWindowStyleOption : AILocalizedString ( @"Borderless Window" , nil ) withTag : AIContactListWindowStyleBorderless [ self _addWindowStyleOption : AILocalizedString ( @"Group Bubbles" , nil ) withTag : AIContactListWindowStyleGroupBubbles [ self _addWindowStyleOption : AILocalizedString ( @"Contact Bubbles" , nil ) withTag : AIContactListWindowStyleContactBubbles [ self _addWindowStyleOption : AILocalizedString ( @"Contact Bubbles (To Fit)" , nil ) withTag : AIContactListWindowStyleContactBubbles_Fitted return [ menu autorelease ]; - ( void ) _addWindowStyleOption: ( NSString * ) option withTag: ( NSInteger ) tag toMenu: ( NSMenu * ) menu { NSMenuItem * menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : option keyEquivalent : @"" ] autorelease ]; //Contact list layout & theme ---------------------------------------------------------------------------------------- #pragma mark Contact list layout & theme * @brief Create a new theme - ( IBAction ) createListTheme: ( id ) sender NSString * theme = [ adium . preferenceController preferenceForKey : KEY_LIST_THEME_NAME group : PREF_GROUP_APPEARANCE ]; ESPresetNameSheetController * presetNameSheetController = [[ ESPresetNameSheetController alloc ] initWithDefaultName : [[ theme stringByAppendingString : @" " ] stringByAppendingString : AILocalizedString ( @"(Copy)" , nil )] explanatoryText : AILocalizedString ( @"Enter a unique name for this new theme." , nil ) [ presetNameSheetController showOnWindow : [[ self view ] window ]]; * @brief Customize the active theme - ( IBAction ) customizeListTheme: ( id ) sender NSString * theme = [ adium . preferenceController preferenceForKey : KEY_LIST_THEME_NAME group : PREF_GROUP_APPEARANCE ]; AIListThemeWindowController * listThemeWindowController = [[ AIListThemeWindowController alloc ] initWithName : theme [ listThemeWindowController showOnWindow : [[ self view ] window ]]; - ( IBAction ) customizeSort: ( id ) sender [ NSApp beginSheet : [[ AISortController activeSortController ] configureView ]. window modalForWindow : self . view . window didEndSelector : @selector ( sheetDidEnd : returnCode : contextInfo : ) - ( IBAction ) customizeHiddenAccounts: ( id ) sender AIHideAccountsWindowController * windowController = [[ AIHideAccountsWindowController alloc ] initWithWindowNibName : @"AIHideAccountsWindow" ]; [ NSApp beginSheet : windowController . window modalForWindow : self . view . window didEndSelector : @selector ( sheetDidEnd : returnCode : contextInfo : ) - ( void ) sheetDidEnd: ( NSWindow * ) sheet returnCode: ( NSInteger ) returnCode contextInfo: ( void * ) contextInfo * @brief Save (or revert) changes made when editing a theme - ( void ) listThemeEditorWillCloseWithChanges: ( BOOL ) saveChanges forThemeNamed: ( NSString * ) name //Update the modified theme if ([ self createSetFromPreferenceGroup : PREF_GROUP_LIST_THEME extension : LIST_THEME_EXTENSION inFolder : LIST_THEME_FOLDER ]) { [ adium . preferenceController setPreference : name forKey : KEY_LIST_THEME_NAME group : PREF_GROUP_APPEARANCE ]; //Revert back to selected theme NSString * theme = [ adium . preferenceController preferenceForKey : KEY_LIST_THEME_NAME group : PREF_GROUP_APPEARANCE ]; //Reapply the selected theme [ self applySetWithName : theme extension : LIST_THEME_EXTENSION inFolder : LIST_THEME_FOLDER toPreferenceGroup : PREF_GROUP_LIST_THEME ]; //Revert back to the current theme name in popUp_colorTheme component [ popUp_colorTheme selectItemWithTitle : [ adium . preferenceController preferenceForKey : KEY_LIST_THEME_NAME group : PREF_GROUP_APPEARANCE ]]; * @brief Manage available themes - ( void ) manageListThemes: ( id ) sender _listThemes = [ self availableThemeSets ]; ESPresetManagementController * presetManagementController = [[ ESPresetManagementController alloc ] initWithPresets : _listThemes [ presetManagementController showOnWindow : [[ self view ] window ]]; [ popUp_colorTheme selectItemWithRepresentedObject : [ adium . preferenceController preferenceForKey : KEY_LIST_THEME_NAME group : PREF_GROUP_APPEARANCE ]]; * @brief Create a new layout - ( IBAction ) createListLayout: ( id ) sender NSString * layout = [ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_NAME group : PREF_GROUP_APPEARANCE ]; ESPresetNameSheetController * presetNameSheetController = [[ ESPresetNameSheetController alloc ] initWithDefaultName : [[ layout stringByAppendingString : @" " ] stringByAppendingString : AILocalizedString ( @"(Copy)" , nil )] explanatoryText : AILocalizedString ( @"Enter a unique name for this new layout." , nil ) [ presetNameSheetController showOnWindow : [[ self view ] window ]]; * @brief Customize the active layout - ( IBAction ) customizeListLayout: ( id ) sender NSString * theme = [ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_NAME group : PREF_GROUP_APPEARANCE ]; AIListLayoutWindowController * listLayoutWindowController = [[ AIListLayoutWindowController alloc ] initWithName : theme [ listLayoutWindowController showOnWindow : [[ self view ] window ]]; * @brief Save (or revert) changes made when editing a layout - ( void ) listLayoutEditorWillCloseWithChanges: ( BOOL ) saveChanges forLayoutNamed: ( NSString * ) name //Update the modified layout if ([ self createSetFromPreferenceGroup : PREF_GROUP_LIST_LAYOUT extension : LIST_LAYOUT_EXTENSION inFolder : LIST_LAYOUT_FOLDER ]) { [ adium . preferenceController setPreference : name forKey : KEY_LIST_LAYOUT_NAME group : PREF_GROUP_APPEARANCE ]; //Revert back to selected layout NSString * layout = [ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_NAME group : PREF_GROUP_APPEARANCE ]; //Reapply the selected layout [ self applySetWithName : layout extension : LIST_LAYOUT_EXTENSION inFolder : LIST_LAYOUT_FOLDER toPreferenceGroup : PREF_GROUP_LIST_LAYOUT ]; //Revert back to the current layout name in popUp_listLayout component [ popUp_listLayout selectItemWithTitle : [ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_NAME group : PREF_GROUP_APPEARANCE ]]; * @brief Manage available layouts - ( void ) manageListLayouts: ( id ) sender _listLayouts = [ self availableLayoutSets ]; ESPresetManagementController * presetManagementController = [[ ESPresetManagementController alloc ] initWithPresets : _listLayouts [ presetManagementController showOnWindow : [[ self view ] window ]]; [ popUp_listLayout selectItemWithRepresentedObject : [ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_NAME group : PREF_GROUP_APPEARANCE ]]; * @brief Validate a layout or theme name to ensure it is unique - ( BOOL ) presetNameSheetController: ( ESPresetNameSheetController * ) controller shouldAcceptNewName :( NSString * ) newName NSEnumerator * enumerator ; NSDictionary * presetDict ; //Scan the correct presets to ensure this name doesn't already exist if ([ userInfo isEqualToString : @"theme" ]) { enumerator = [[ self availableThemeSets ] objectEnumerator ]; enumerator = [[ self availableLayoutSets ] objectEnumerator ]; while (( presetDict = [ enumerator nextObject ])) { if ([ newName isEqualToString : [ presetDict objectForKey : @"name" ]]) return NO ; * @brief Create a new theme with the user supplied name, activate and edit it - ( void ) presetNameSheetControllerDidEnd: ( ESPresetNameSheetController * ) controller returnCode :( ESPresetNameSheetReturnCode ) returnCode newName :( NSString * ) newName case ESPresetNameSheetOkayReturn : //User has created a new theme/layout : show the editor if ([ userInfo isEqualToString : @"theme" ]) { [ self performSelector : @selector ( _editListThemeWithName : ) withObject : newName afterDelay : 0 ]; [ self performSelector : @selector ( _editListLayoutWithName : ) withObject : newName afterDelay : 0 ]; case ESPresetNameSheetCancelReturn : //User has canceled the operation : revert back to the current theme if ([ userInfo isEqualToString : @"theme" ]) { [ popUp_colorTheme selectItemWithTitle : [ adium . preferenceController preferenceForKey : KEY_LIST_THEME_NAME group : PREF_GROUP_APPEARANCE ]]; [ popUp_listLayout selectItemWithTitle : [ adium . preferenceController preferenceForKey : KEY_LIST_LAYOUT_NAME group : PREF_GROUP_APPEARANCE ]]; - ( void ) _editListThemeWithName: ( NSString * ) name { AIListThemeWindowController * listThemeWindowController = [[ AIListThemeWindowController alloc ] initWithName : name [ listThemeWindowController showOnWindow : [[ self view ] window ]]; - ( void ) _editListLayoutWithName: ( NSString * ) name { AIListLayoutWindowController * listLayoutWindowController = [[ AIListLayoutWindowController alloc ] initWithName : name [ listLayoutWindowController showOnWindow : [[ self view ] window ]]; - ( NSArray * ) renamePreset: ( NSDictionary * ) preset toName: ( NSString * ) newName inPresets: ( NSArray * ) presets renamedPreset: ( id * ) renamedPreset if ( presets == _listLayouts ) { [ self renameSetWithName : [ preset objectForKey : @"name" ] extension : LIST_LAYOUT_EXTENSION inFolder : LIST_LAYOUT_FOLDER _listLayouts = [ self availableLayoutSets ]; newPresets = _listLayouts ; } else if ( presets == _listThemes ) { [ self renameSetWithName : [ preset objectForKey : @"name" ] extension : LIST_THEME_EXTENSION inFolder : LIST_THEME_FOLDER _listThemes = [ self availableThemeSets ]; newPresets = _listThemes ; //Return the new duplicate by reference for the preset controller for ( aPreset in newPresets ) { if ([ newName isEqualToString : [ aPreset objectForKey : @"name" ]]) { * renamedPreset = aPreset ; - ( NSArray * ) duplicatePreset: ( NSDictionary * ) preset inPresets: ( NSArray * ) presets createdDuplicate: ( id * ) duplicatePreset NSString * newName = [ NSString stringWithFormat : @"%@ (%@)" , [ preset objectForKey : @"name" ], AILocalizedString ( @"Copy" , nil )]; NSArray * newPresets = nil ; if ( presets == _listLayouts ) { [ self duplicateSetWithName : [ preset objectForKey : @"name" ] extension : LIST_LAYOUT_EXTENSION inFolder : LIST_LAYOUT_FOLDER _listLayouts = [ self availableLayoutSets ]; newPresets = _listLayouts ; } else if ( presets == _listThemes ) { [ self duplicateSetWithName : [ preset objectForKey : @"name" ] extension : LIST_THEME_EXTENSION inFolder : LIST_THEME_FOLDER _listThemes = [ self availableThemeSets ]; newPresets = _listThemes ; //Return the new duplicate by reference for the preset controller for ( aPreset in newPresets ) { if ([ newName isEqualToString : [ aPreset objectForKey : @"name" ]]) { * duplicatePreset = aPreset ; - ( NSArray * ) deletePreset: ( NSDictionary * ) preset inPresets: ( NSArray * ) presets if ( presets == _listLayouts ) { [ self deleteSetWithName : [ preset objectForKey : @"name" ] extension : LIST_LAYOUT_EXTENSION inFolder : LIST_LAYOUT_FOLDER ]; _listLayouts = [ self availableLayoutSets ]; } else if ( presets == _listThemes ) { [ self deleteSetWithName : [ preset objectForKey : @"name" ] extension : LIST_THEME_EXTENSION inFolder : LIST_THEME_FOLDER ]; _listThemes = [ self availableThemeSets ]; - ( NSMenu * ) _listLayoutMenu NSMenu * menu = [[ NSMenu allocWithZone : [ NSMenu menuZone ]] init ]; for ( NSDictionary * set in self . availableLayoutSets ) { name = [ set objectForKey : @"name" ]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : name keyEquivalent : @"" ] autorelease ]; [ menuItem setRepresentedObject : name ]; [ menu addItem : [ NSMenuItem separatorItem ]]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : [ AILocalizedString ( @"Add New Layout" , nil ) stringByAppendingEllipsis ] action : @selector ( createListLayout : ) keyEquivalent : @"" ] autorelease ]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : [ AILocalizedString ( @"Edit Layouts" , nil ) stringByAppendingEllipsis ] action : @selector ( manageListLayouts : ) keyEquivalent : @"" ] autorelease ]; return [ menu autorelease ]; - ( NSMenu * ) _colorThemeMenu NSMenu * menu = [[ NSMenu allocWithZone : [ NSMenu menuZone ]] init ]; for ( NSDictionary * set in self . availableThemeSets ) { name = [ set objectForKey : @"name" ]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : name keyEquivalent : @"" ] autorelease ]; [ menuItem setRepresentedObject : name ]; [ menu addItem : [ NSMenuItem separatorItem ]]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : [ AILocalizedString ( @"Add New Theme" , nil ) stringByAppendingEllipsis ] action : @selector ( createListTheme : ) keyEquivalent : @"" ] autorelease ]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : [ AILocalizedString ( @"Edit Themes" , nil ) stringByAppendingEllipsis ] action : @selector ( manageListThemes : ) keyEquivalent : @"" ] autorelease ]; return [ menu autorelease ]; #pragma mark ListLayout and ListTheme preference management //Apply a set of preferences - ( void ) applySetWithName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder toPreferenceGroup: ( NSString * ) preferenceGroup NSFileManager * defaultManager = [ NSFileManager defaultManager ]; NSDictionary * setDictionary = nil ; //Look in each resource location until we find it fileName = [ setName stringByAppendingPathExtension : extension ]; for ( NSString * resourcePath in [ adium resourcePathsForName : folder ]) { NSString * filePath = [ resourcePath stringByAppendingPathComponent : fileName ]; if ([ defaultManager fileExistsAtPath : filePath ]) { if (( xtraBundle = [ NSBundle bundleWithPath : filePath ]) && ([[ xtraBundle objectForInfoDictionaryKey : @"XtraBundleVersion" ] integerValue ] == 1 )) { filePath = [[ xtraBundle resourcePath ] stringByAppendingPathComponent : @"Data.plist" ]; setDictionary = [ NSDictionary dictionaryWithContentsOfFile : filePath ]; if ( setDictionary ) break ; [ adium . preferenceController setPreferences : setDictionary inGroup : preferenceGroup ]; //Create a layout or theme set - ( BOOL ) createSetFromPreferenceGroup: ( NSString * ) preferenceGroup withName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder NSString * fileName = [[ setName safeFilenameString ] stringByAppendingPathExtension : extension ]; //If we don't find one, create a path to a bundle in the application support directory path = [[[ adium applicationSupportDirectory ] stringByAppendingPathComponent : folder ] stringByAppendingPathComponent : fileName ]; if ([ AIXtrasManager createXtraBundleAtPath : path ]) path = [ path stringByAppendingPathComponent : @"Contents/Resources/Data.plist" ]; if ([[ adium . preferenceController preferencesForGroup : preferenceGroup ] writeToFile : path atomically : NO ]) { [[ NSNotificationCenter defaultCenter ] postNotificationName : AIXtrasDidChangeNotification object : extension ]; NSRunAlertPanel ( AILocalizedString ( @"Error Saving Theme" , nil ), AILocalizedString ( @"Unable to write file %@ to %@" , nil ), AILocalizedString ( @"OK" , nil ), //Delete a layout or theme set - ( BOOL ) deleteSetWithName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder success = [[ NSFileManager defaultManager ] removeItemAtPath : [ adium pathOfPackWithName : setName resourceFolderName : folder ] //The availability of an xtras just changed, since we deleted it... post a notification so we can update [[ NSNotificationCenter defaultCenter ] postNotificationName : AIXtrasDidChangeNotification object : extension ]; - ( BOOL ) renameSetWithName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder toName: ( NSString * ) newName NSString * destFolder = [[ adium applicationSupportDirectory ] stringByAppendingPathComponent : folder ]; NSString * newFileName = [ newName stringByAppendingPathExtension : extension ]; success = [[ NSFileManager defaultManager ] moveItemAtPath : [ adium pathOfPackWithName : setName resourceFolderName : folder ] toPath :[ destFolder stringByAppendingPathComponent : newFileName ] //The availability of an xtras just changed, since we deleted it... post a notification so we can update [[ NSNotificationCenter defaultCenter ] postNotificationName : AIXtrasDidChangeNotification object : extension ]; - ( BOOL ) duplicateSetWithName: ( NSString * ) setName extension: ( NSString * ) extension inFolder: ( NSString * ) folder newName: ( NSString * ) newName NSString * destFolder = [[ adium applicationSupportDirectory ] stringByAppendingPathComponent : folder ]; NSString * newFileName = [ newName stringByAppendingPathExtension : extension ]; success = [[ NSFileManager defaultManager ] copyItemAtPath : [ adium pathOfPackWithName : setName resourceFolderName : folder ] toPath :[ destFolder stringByAppendingPathComponent : newFileName ] //The availability of an xtras just changed, since we deleted it... post a notification so we can update [[ NSNotificationCenter defaultCenter ] postNotificationName : AIXtrasDidChangeNotification object : extension ]; - ( NSArray * ) availableSetsWithExtension: ( NSString * ) extension fromFolder: ( NSString * ) folder NSMutableArray * setArray = [ NSMutableArray array ]; NSMutableSet * alreadyAddedArray = [ NSMutableSet set ]; for ( NSString * filePath in [ adium allResourcesForName : folder withExtensions : extension ]) { name = [[ filePath lastPathComponent ] stringByDeletingPathExtension ]; if (( xtraBundle = [ NSBundle bundleWithPath : filePath ]) && ([[ xtraBundle objectForInfoDictionaryKey : @"XtraBundleVersion" ] integerValue ] == 1 )) { filePath = [[ xtraBundle resourcePath ] stringByAppendingPathComponent : @"Data.plist" ]; if (( themeDict = [ NSDictionary dictionaryWithContentsOfFile : filePath ])) { //The Adium resource path is last in our resourcePaths array; by only adding sets we haven't //already added, we allow precedence to occur rather than conflict. if ( ! [ alreadyAddedArray containsObject : name ]) { [ setArray addObject : [ NSDictionary dictionaryWithObjectsAndKeys : themeDict , @"preferences" , [ alreadyAddedArray addObject : name ]; return [ setArray sortedArrayUsingComparator :^ NSComparisonResult ( id obj1 , id obj2 ) { return [[ obj1 objectForKey : @"name" ] caseInsensitiveCompare : [ obj2 objectForKey : @"name" ]]; - ( NSArray * ) availableLayoutSets return [ self availableSetsWithExtension : LIST_LAYOUT_EXTENSION fromFolder : LIST_LAYOUT_FOLDER ]; - ( NSArray * ) availableThemeSets return [ self availableSetsWithExtension : LIST_THEME_EXTENSION fromFolder : LIST_THEME_FOLDER ]; * @brief Generate the menu of long display name format choices * @result The autoreleased menu - ( NSMenu * ) _contactNameMenu choicesMenu = [[[ NSMenu allocWithZone : [ NSMenu menuZone ]] initWithTitle : @"" ] autorelease ]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : ALIAS keyEquivalent : @"" ] autorelease ]; [ menuItem setTag : AINameFormat_DisplayName ]; [ choicesMenu addItem : menuItem ]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : ALIAS_SCREENNAME keyEquivalent : @"" ] autorelease ]; [ menuItem setTag : AINameFormat_DisplayName_ScreenName ]; [ choicesMenu addItem : menuItem ]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : SCREENNAME_ALIAS keyEquivalent : @"" ] autorelease ]; [ menuItem setTag : AINameFormat_ScreenName_DisplayName ]; [ choicesMenu addItem : menuItem ]; menuItem = [[[ NSMenuItem allocWithZone : [ NSMenu menuZone ]] initWithTitle : SCREENNAME keyEquivalent : @"" ] autorelease ]; [ menuItem setTag : AINameFormat_ScreenName ]; [ choicesMenu addItem : menuItem ]; * @brief Configure the sort selection menu items NSMenu * sortMenu = [[[ NSMenu alloc ] init ] autorelease ]; //Add each sort controller for ( AISortController * controller in [ AISortController availableSortControllers ]) { NSMenuItem * menuItem = [[[ NSMenuItem alloc ] initWithTitle : controller . displayName keyEquivalent : @"" ] autorelease ]; [ menuItem setRepresentedObject : controller ]; [ sortMenu addItem : menuItem ];