Added the Add Bookmark toolbar item to the default toolbar. Refs #8772
[adiumx.git] / Source / AIAppearancePreferencesPlugin.m
blob596a823e018f12f596398d4e2281d1eb28d1724b
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "AIAppearancePreferencesPlugin.h"
18 #import "AIAppearancePreferences.h"
19 #import "AIDockController.h"
20 #import <Adium/AIMenuControllerProtocol.h>
21 #import <Adium/AIAbstractListController.h>
22 #import <Adium/AIStatusIcons.h>
23 #import <Adium/AIServiceIcons.h>
24 #import <AIMenuBarIcons.h>
25 #import <AIUtilities/AIStringAdditions.h>
26 #import <AIUtilities/AIDictionaryAdditions.h>
27 #import <AIUtilities/AIMenuAdditions.h>
28 #import "AIXtrasManager.h"
30 #define APPEARANCE_DEFAULT_PREFS        @"AppearanceDefaults"
32 @implementation AIAppearancePreferencesPlugin
34 - (void)installPlugin
36         id<AIPreferenceController> preferenceController = [adium preferenceController];
38         [adium createResourcePathForName:LIST_LAYOUT_FOLDER];
39         [adium createResourcePathForName:LIST_THEME_FOLDER];
41         //Prepare our preferences
42         [preferenceController registerDefaults:[NSDictionary dictionaryNamed:APPEARANCE_DEFAULT_PREFS
43                                                                                                                                 forClass:[self class]] 
44                                       forGroup:PREF_GROUP_APPEARANCE];
46         preferences = [[AIAppearancePreferences preferencePaneForPlugin:self] retain];  
48         [[adium notificationCenter] addObserver:self
49                                                                    selector:@selector(invalidStatusSetActivated:)
50                                                                            name:AIStatusIconSetInvalidSetNotification
51                                                                          object:nil];
52         
53         //Add the menu item for configuring the sort
54         menuItem_userIcons = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Show User Icons", nil)
55                                                                                                                                         target:self
56                                                                                                                                         action:@selector(toggleAppearancePreference:)
57                                                                                                                          keyEquivalent:@"i"];
58         [menuItem_userIcons setKeyEquivalentModifierMask:(NSControlKeyMask | NSCommandKeyMask)];
59         [[adium menuController] addMenuItem:menuItem_userIcons toLocation:LOC_View_Appearance_Toggles];
61         menuItem_userStatusMessages = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Show Status Messages", nil)
62                                                                                                                                                                            target:self
63                                                                                                                                                                            action:@selector(toggleAppearancePreference:)
64                                                                                                                                                                 keyEquivalent:@"s"];
65         [menuItem_userStatusMessages setKeyEquivalentModifierMask:(NSControlKeyMask | NSCommandKeyMask)];
66         [[adium menuController] addMenuItem:menuItem_userStatusMessages toLocation:LOC_View_Appearance_Toggles];
67         
68         [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE];
69         [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_LIST_LAYOUT]; 
70 }       
72 - (void)uninstallPlugin
74         [[adium preferenceController] unregisterPreferenceObserver:self];
77 - (void)dealloc
79         [menuItem_userIcons release];
80         
81         [super dealloc];
84 /*!
85  * @brief Apply changed preferences
86  */
87 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object
88                                         preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
90         //Status icons
91         if ([group isEqualToString:PREF_GROUP_APPEARANCE]) {
92                 if (firstTime || [key isEqualToString:KEY_STATUS_ICON_PACK]) {
93                         NSString *path = [adium pathOfPackWithName:[prefDict objectForKey:KEY_STATUS_ICON_PACK]
94                                                                                          extension:@"AdiumStatusIcons"
95                                                                         resourceFolderName:@"Status Icons"];
96                         BOOL success = NO;
97                         
98                         if (path) {
99                                 success = [AIStatusIcons setActiveStatusIconsFromPath:path];
100                         }
102                         //If the preferred pack isn't found (it was probably deleted while active), use the default one
103                         if (!success) {
104                                 NSString *name = [[adium preferenceController] defaultPreferenceForKey:KEY_STATUS_ICON_PACK
105                                                                                                                                                                  group:PREF_GROUP_APPEARANCE
106                                                                                                                                                                 object:nil];
107                                 path = [adium pathOfPackWithName:name
108                                                                            extension:@"AdiumStatusIcons"
109                                                           resourceFolderName:@"Status Icons"];
110                                 
111                                 [AIStatusIcons setActiveStatusIconsFromPath:path];
112                         }
113                 }
114                 
115                 //Service icons
116                 if (firstTime || [key isEqualToString:KEY_SERVICE_ICON_PACK]) {
117                         NSString *path = [adium pathOfPackWithName:[prefDict objectForKey:KEY_SERVICE_ICON_PACK]
118                                                                                          extension:@"AdiumServiceIcons"
119                                                                         resourceFolderName:@"Service Icons"];
120                         BOOL success = NO;
121                         
122                         if (path) {
123                                 success = [AIServiceIcons setActiveServiceIconsFromPath:path];
124                         }
125                         
126                         //If the preferred pack isn't found (it was probably deleted while active), use the default one
127                         if (!success) {
128                                 NSString *name = [[adium preferenceController] defaultPreferenceForKey:KEY_SERVICE_ICON_PACK
129                                                                                                                                                                  group:PREF_GROUP_APPEARANCE
130                                                                                                                                                                 object:nil];
131                                 path = [adium pathOfPackWithName:name
132                                                                            extension:@"AdiumServiceIcons"
133                                                           resourceFolderName:@"Service Icons"];
135                                 [AIServiceIcons setActiveServiceIconsFromPath:path];
136                         }
137                 }
138                 
139                 // Menu Bar Icons
140                 if (firstTime || [key isEqualToString:KEY_MENU_BAR_ICONS]) {
141                         // Post a notification to update the menu bar icons.
142                         [[[AIObject sharedAdiumInstance] notificationCenter] postNotificationName:AIMenuBarIconsDidChangeNotification
143                                                                                                                                                            object:nil];
144                 }
145                 
146                 //Theme
147                 if (firstTime || [key isEqualToString:KEY_LIST_THEME_NAME]) {
148                         [self applySetWithName:[prefDict objectForKey:KEY_LIST_THEME_NAME]
149                                                  extension:LIST_THEME_EXTENSION
150                                                   inFolder:LIST_THEME_FOLDER
151                                  toPreferenceGroup:PREF_GROUP_LIST_THEME];
152                 }
153         
154                 if (firstTime || [key isEqualToString:KEY_LIST_LAYOUT_NAME]) {
155                         [self applySetWithName:[prefDict objectForKey:KEY_LIST_LAYOUT_NAME]
156                                                  extension:LIST_LAYOUT_EXTENSION
157                                                   inFolder:LIST_LAYOUT_FOLDER
158                                  toPreferenceGroup:PREF_GROUP_LIST_LAYOUT];
159                 }               
160         }
162         //Layout
163         if ([group isEqualToString:PREF_GROUP_LIST_LAYOUT]) {
164                 if (firstTime || !key ||
165                         [key isEqualToString:KEY_LIST_LAYOUT_SHOW_ICON] ||
166                         [key isEqualToString:KEY_LIST_LAYOUT_SHOW_EXT_STATUS]) {
167                         [menuItem_userIcons setState:[[prefDict objectForKey:KEY_LIST_LAYOUT_SHOW_ICON] boolValue]];
168                         [menuItem_userStatusMessages setState:[[prefDict objectForKey:KEY_LIST_LAYOUT_SHOW_EXT_STATUS] boolValue]];
169                 }
170         }
174  * @brief An invalid status set was activated
176  * Reset to the default by clearing our preference
177  */
178 - (void)invalidStatusSetActivated:(NSNotification *)inNotification
180         [[adium preferenceController] setPreference:nil
181                                                                                  forKey:KEY_STATUS_ICON_PACK
182                                                                                   group:PREF_GROUP_APPEARANCE];
183         
184         //Tell the preferences to update
185         [preferences xtrasChanged:nil];
188 - (void)toggleAppearancePreference:(NSMenuItem *)sender
190         BOOL     oldValue = [sender state];
191         NSString *key;
193         if (sender == menuItem_userIcons) {             
194                 key = KEY_LIST_LAYOUT_SHOW_ICON;
195                 
196         } else if (sender == menuItem_userStatusMessages) {
197                 key = KEY_LIST_LAYOUT_SHOW_EXT_STATUS;
199         } else {
200                 key = nil;
201         }
202         
203         if (key) {
204                 [[adium preferenceController] setPreference:[NSNumber numberWithBool:!oldValue]
205                                                                                          forKey:key
206                                                                                           group:PREF_GROUP_LIST_LAYOUT];
207                 [sender setState:!oldValue];
209                 //Save the updated layout
210                 [self createSetFromPreferenceGroup:PREF_GROUP_LIST_LAYOUT
211                                                                   withName:[[adium preferenceController] preferenceForKey:KEY_LIST_LAYOUT_NAME
212                                                                                                                                                                         group:PREF_GROUP_APPEARANCE]
213                                                                  extension:LIST_LAYOUT_EXTENSION
214                                                                   inFolder:LIST_LAYOUT_FOLDER];
215         }
218 #pragma mark ListLayout and ListTheme preference management
219 //Apply a set of preferences
220 - (void)applySetWithName:(NSString *)setName extension:(NSString *)extension inFolder:(NSString *)folder toPreferenceGroup:(NSString *)preferenceGroup
222         NSFileManager   *defaultManager = [NSFileManager defaultManager];
223         NSEnumerator    *enumerator;
224         NSString                *fileName, *resourcePath;
225         NSDictionary    *setDictionary = nil;
226         
227         //Look in each resource location until we find it
228         fileName = [setName stringByAppendingPathExtension:extension];
229         
230         enumerator = [[adium resourcePathsForName:folder] objectEnumerator];
231         while ((resourcePath = [enumerator nextObject]) && !setDictionary) {
232                 NSString                *filePath = [resourcePath stringByAppendingPathComponent:fileName];
233                 
234                 if ([defaultManager fileExistsAtPath:filePath]) {
235                         NSBundle *xtraBundle;
236                         if((xtraBundle = [NSBundle bundleWithPath:filePath]) &&
237                            ([[xtraBundle objectForInfoDictionaryKey:@"XtraBundleVersion"] intValue] == 1)) {
238                                 filePath = [[xtraBundle resourcePath] stringByAppendingPathComponent:@"Data.plist"];
239                         }
241                         setDictionary = [NSDictionary dictionaryWithContentsOfFile:filePath];
242                 }
243         }
244         
245         //Apply its values
246         if (setDictionary) {
247                 [[adium preferenceController] setPreferences:setDictionary
248                                                                                          inGroup:preferenceGroup];
249         }
252 //Create a layout or theme set
253 - (BOOL)createSetFromPreferenceGroup:(NSString *)preferenceGroup withName:(NSString *)setName extension:(NSString *)extension inFolder:(NSString *)folder
255         NSString                *path;
256         NSString                *fileName = [[setName safeFilenameString] stringByAppendingPathExtension:extension];
257         
258         //If we don't find one, create a path to a bundle in the application support directory
259         path = [[[adium applicationSupportDirectory] stringByAppendingPathComponent:folder] stringByAppendingPathComponent:fileName];
260         if ([AIXtrasManager createXtraBundleAtPath:path])
261                 path = [path stringByAppendingPathComponent:@"Contents/Resources/Data.plist"];
262         
263         if ([[[adium preferenceController] preferencesForGroup:preferenceGroup] writeToFile:path atomically:NO]) {
264                 [[adium notificationCenter] postNotificationName:AIXtrasDidChangeNotification object:extension];
265                 
266                 return YES;
267         } else {
268                 NSRunAlertPanel(AILocalizedString(@"Error Saving Theme",nil),
269                                                 AILocalizedString(@"Unable to write file %@ to %@",nil),
270                                                 AILocalizedString(@"OK",nil),
271                                                 nil,
272                                                 nil,
273                                                 fileName,
274                                                 path);
275                 return NO;
276         }
279 //Delete a layout or theme set
280 - (BOOL)deleteSetWithName:(NSString *)setName extension:(NSString *)extension inFolder:(NSString *)folder
282         BOOL            success;
283         
284         success = [[NSFileManager defaultManager] removeFileAtPath:[adium pathOfPackWithName:setName
285                                                                                                                                                            extension:extension
286                                                                                                                                           resourceFolderName:folder]
287                                                                                                            handler:nil];
288         
289         //The availability of an xtras just changed, since we deleted it... post a notification so we can update
290         [[adium notificationCenter] postNotificationName:AIXtrasDidChangeNotification object:extension];
291         
292         return success;
296 - (BOOL)renameSetWithName:(NSString *)setName extension:(NSString *)extension inFolder:(NSString *)folder toName:(NSString *)newName
298         BOOL            success;
299         
300         NSString        *destFolder = [[adium applicationSupportDirectory] stringByAppendingPathComponent:folder];
301         NSString        *newFileName = [newName stringByAppendingPathExtension:extension];
302         
303         success = [[NSFileManager defaultManager] movePath:[adium pathOfPackWithName:setName
304                                                                                                                                            extension:extension
305                                                                                                                           resourceFolderName:folder]
306                                                                                                 toPath:[destFolder stringByAppendingPathComponent:newFileName]
307                                                                                            handler:nil];
308         
309         //The availability of an xtras just changed, since we deleted it... post a notification so we can update
310         [[adium notificationCenter] postNotificationName:AIXtrasDidChangeNotification object:extension];
311         
312         return success;
316 - (BOOL)duplicateSetWithName:(NSString *)setName extension:(NSString *)extension inFolder:(NSString *)folder newName:(NSString *)newName
318         BOOL            success;
319         
320         //Duplicate the set
321         NSString        *destFolder = [[adium applicationSupportDirectory] stringByAppendingPathComponent:folder];
322         NSString        *newFileName = [newName stringByAppendingPathExtension:extension];
323         
324         success = [[NSFileManager defaultManager] copyPath:[adium pathOfPackWithName:setName
325                                                                                                                                            extension:extension
326                                                                                                                           resourceFolderName:folder]
327                                                                                                 toPath:[destFolder stringByAppendingPathComponent:newFileName]
328                                                                                            handler:nil];
329         
330         //The availability of an xtras just changed, since we deleted it... post a notification so we can update
331         [[adium notificationCenter] postNotificationName:AIXtrasDidChangeNotification object:extension];
332         
333         return success;
336 //Sort sets
337 int availableSetSort(NSDictionary *objectA, NSDictionary *objectB, void *context) {
338         return [[objectA objectForKey:@"name"] caseInsensitiveCompare:[objectB objectForKey:@"name"]];
342 - (NSArray *)availableSetsWithExtension:(NSString *)extension fromFolder:(NSString *)folder
344         NSMutableArray  *setArray = [NSMutableArray array];
345         NSEnumerator    *enumerator = [[adium allResourcesForName:folder withExtensions:extension] objectEnumerator];
346         NSMutableSet    *alreadyAddedArray = [NSMutableSet set];
347         NSString                *filePath;
348         
349     while ((filePath = [enumerator nextObject])) {
350                 NSString                *name;
351                 NSBundle                *xtraBundle;
352                 NSDictionary    *themeDict;
354                 name = [[filePath lastPathComponent] stringByDeletingPathExtension];
356                 if((xtraBundle = [NSBundle bundleWithPath:filePath]) &&
357                    ([[xtraBundle objectForInfoDictionaryKey:@"XtraBundleVersion"] intValue] == 1)) {
358                         filePath = [[xtraBundle resourcePath] stringByAppendingPathComponent:@"Data.plist"];
359                 }
361                 if ((themeDict = [NSDictionary dictionaryWithContentsOfFile:filePath])) {                       
362                         //The Adium resource path is last in our resourcePaths array; by only adding sets we haven't
363                         //already added, we allow precedence to occur rather than conflict.
364                         if (![alreadyAddedArray containsObject:name]) {
365                                 [setArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
366                                         name, @"name",
367                                         filePath, @"path",
368                                         themeDict, @"preferences",
369                                         nil]];
370                                 [alreadyAddedArray addObject:name];
371                         }
372                 }
373         }
374         
375         return [setArray sortedArrayUsingFunction:availableSetSort context:nil];
378 - (NSArray *)availableLayoutSets
380         return [self availableSetsWithExtension:LIST_LAYOUT_EXTENSION 
381                                                                  fromFolder:LIST_LAYOUT_FOLDER];
383 - (NSArray *)availableThemeSets
385         return [self availableSetsWithExtension:LIST_THEME_EXTENSION
386                                                                  fromFolder:LIST_THEME_FOLDER];
389 @end