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 "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
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
53 //Add the menu item for configuring the sort
54 menuItem_userIcons = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Show User Icons", nil)
56 action:@selector(toggleAppearancePreference:)
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)
63 action:@selector(toggleAppearancePreference:)
65 [menuItem_userStatusMessages setKeyEquivalentModifierMask:(NSControlKeyMask | NSCommandKeyMask)];
66 [[adium menuController] addMenuItem:menuItem_userStatusMessages toLocation:LOC_View_Appearance_Toggles];
68 [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE];
69 [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_LIST_LAYOUT];
72 - (void)uninstallPlugin
74 [[adium preferenceController] unregisterPreferenceObserver:self];
79 [menuItem_userIcons release];
85 * @brief Apply changed preferences
87 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object
88 preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
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"];
99 success = [AIStatusIcons setActiveStatusIconsFromPath:path];
102 //If the preferred pack isn't found (it was probably deleted while active), use the default one
104 NSString *name = [[adium preferenceController] defaultPreferenceForKey:KEY_STATUS_ICON_PACK
105 group:PREF_GROUP_APPEARANCE
107 path = [adium pathOfPackWithName:name
108 extension:@"AdiumStatusIcons"
109 resourceFolderName:@"Status Icons"];
111 [AIStatusIcons setActiveStatusIconsFromPath:path];
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"];
123 success = [AIServiceIcons setActiveServiceIconsFromPath:path];
126 //If the preferred pack isn't found (it was probably deleted while active), use the default one
128 NSString *name = [[adium preferenceController] defaultPreferenceForKey:KEY_SERVICE_ICON_PACK
129 group:PREF_GROUP_APPEARANCE
131 path = [adium pathOfPackWithName:name
132 extension:@"AdiumServiceIcons"
133 resourceFolderName:@"Service Icons"];
135 [AIServiceIcons setActiveServiceIconsFromPath:path];
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
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];
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];
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]];
174 * @brief An invalid status set was activated
176 * Reset to the default by clearing our preference
178 - (void)invalidStatusSetActivated:(NSNotification *)inNotification
180 [[adium preferenceController] setPreference:nil
181 forKey:KEY_STATUS_ICON_PACK
182 group:PREF_GROUP_APPEARANCE];
184 //Tell the preferences to update
185 [preferences xtrasChanged:nil];
188 - (void)toggleAppearancePreference:(NSMenuItem *)sender
190 BOOL oldValue = [sender state];
193 if (sender == menuItem_userIcons) {
194 key = KEY_LIST_LAYOUT_SHOW_ICON;
196 } else if (sender == menuItem_userStatusMessages) {
197 key = KEY_LIST_LAYOUT_SHOW_EXT_STATUS;
204 [[adium preferenceController] setPreference:[NSNumber numberWithBool:!oldValue]
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];
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;
227 //Look in each resource location until we find it
228 fileName = [setName stringByAppendingPathExtension:extension];
230 enumerator = [[adium resourcePathsForName:folder] objectEnumerator];
231 while ((resourcePath = [enumerator nextObject]) && !setDictionary) {
232 NSString *filePath = [resourcePath stringByAppendingPathComponent:fileName];
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"];
241 setDictionary = [NSDictionary dictionaryWithContentsOfFile:filePath];
247 [[adium preferenceController] setPreferences:setDictionary
248 inGroup:preferenceGroup];
252 //Create a layout or theme set
253 - (BOOL)createSetFromPreferenceGroup:(NSString *)preferenceGroup withName:(NSString *)setName extension:(NSString *)extension inFolder:(NSString *)folder
256 NSString *fileName = [[setName safeFilenameString] stringByAppendingPathExtension:extension];
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"];
263 if ([[[adium preferenceController] preferencesForGroup:preferenceGroup] writeToFile:path atomically:NO]) {
264 [[adium notificationCenter] postNotificationName:AIXtrasDidChangeNotification object:extension];
268 NSRunAlertPanel(AILocalizedString(@"Error Saving Theme",nil),
269 AILocalizedString(@"Unable to write file %@ to %@",nil),
270 AILocalizedString(@"OK",nil),
279 //Delete a layout or theme set
280 - (BOOL)deleteSetWithName:(NSString *)setName extension:(NSString *)extension inFolder:(NSString *)folder
284 success = [[NSFileManager defaultManager] removeFileAtPath:[adium pathOfPackWithName:setName
286 resourceFolderName:folder]
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];
296 - (BOOL)renameSetWithName:(NSString *)setName extension:(NSString *)extension inFolder:(NSString *)folder toName:(NSString *)newName
300 NSString *destFolder = [[adium applicationSupportDirectory] stringByAppendingPathComponent:folder];
301 NSString *newFileName = [newName stringByAppendingPathExtension:extension];
303 success = [[NSFileManager defaultManager] movePath:[adium pathOfPackWithName:setName
305 resourceFolderName:folder]
306 toPath:[destFolder stringByAppendingPathComponent:newFileName]
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];
316 - (BOOL)duplicateSetWithName:(NSString *)setName extension:(NSString *)extension inFolder:(NSString *)folder newName:(NSString *)newName
321 NSString *destFolder = [[adium applicationSupportDirectory] stringByAppendingPathComponent:folder];
322 NSString *newFileName = [newName stringByAppendingPathExtension:extension];
324 success = [[NSFileManager defaultManager] copyPath:[adium pathOfPackWithName:setName
326 resourceFolderName:folder]
327 toPath:[destFolder stringByAppendingPathComponent:newFileName]
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];
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];
349 while ((filePath = [enumerator nextObject])) {
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"];
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:
368 themeDict, @"preferences",
370 [alreadyAddedArray addObject:name];
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];