* 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 "AIAppearancePreferencesPlugin.h" #import "AIAppearancePreferences.h" #import "AIDockController.h" #import <Adium/AIMenuControllerProtocol.h> #import <Adium/AIAbstractListController.h> #import <Adium/AIStatusIcons.h> #import <Adium/AIServiceIcons.h> #import "AIMenuBarIcons.h" #import <AIUtilities/AIStringAdditions.h> #import <AIUtilities/AIDictionaryAdditions.h> #import <AIUtilities/AIMenuAdditions.h> #import "AIXtrasManager.h" #define APPEARANCE_DEFAULT_PREFS @"AppearanceDefaults" @interface AIAppearancePreferencesPlugin () - (void)invalidStatusSetActivated:(NSNotification *)inNotification; @implementation AIAppearancePreferencesPlugin id<AIPreferenceController> preferenceController = adium.preferenceController; [adium createResourcePathForName:LIST_LAYOUT_FOLDER]; [adium createResourcePathForName:LIST_THEME_FOLDER]; //Prepare our preferences [preferenceController registerDefaults:[NSDictionary dictionaryNamed:APPEARANCE_DEFAULT_PREFS forGroup:PREF_GROUP_APPEARANCE]; preferences = [(AIAppearancePreferences *)[AIAppearancePreferences preferencePaneForPlugin:self] retain]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(invalidStatusSetActivated:) name:AIStatusIconSetInvalidSetNotification [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_APPEARANCE]; [adium.preferenceController unregisterPreferenceObserver:self]; * @brief Apply changed preferences - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime if ([group isEqualToString:PREF_GROUP_APPEARANCE]) { if (firstTime || [key isEqualToString:KEY_STATUS_ICON_PACK]) { NSString *path = [adium pathOfPackWithName:[prefDict objectForKey:KEY_STATUS_ICON_PACK] extension:@"AdiumStatusIcons" resourceFolderName:@"Status Icons"]; success = [AIStatusIcons setActiveStatusIconsFromPath:path]; //If the preferred pack isn't found (it was probably deleted while active), use the default one NSString *name = [adium.preferenceController defaultPreferenceForKey:KEY_STATUS_ICON_PACK group:PREF_GROUP_APPEARANCE path = [adium pathOfPackWithName:name extension:@"AdiumStatusIcons" resourceFolderName:@"Status Icons"]; [AIStatusIcons setActiveStatusIconsFromPath:path]; if (firstTime || [key isEqualToString:KEY_SERVICE_ICON_PACK]) { NSString *path = [adium pathOfPackWithName:[prefDict objectForKey:KEY_SERVICE_ICON_PACK] extension:@"AdiumServiceIcons" resourceFolderName:@"Service Icons"]; success = [AIServiceIcons setActiveServiceIconsFromPath:path]; //If the preferred pack isn't found (it was probably deleted while active), use the default one NSString *name = [adium.preferenceController defaultPreferenceForKey:KEY_SERVICE_ICON_PACK group:PREF_GROUP_APPEARANCE path = [adium pathOfPackWithName:name extension:@"AdiumServiceIcons" resourceFolderName:@"Service Icons"]; [AIServiceIcons setActiveServiceIconsFromPath:path]; if (firstTime || [key isEqualToString:KEY_MENU_BAR_ICONS]) { // Post a notification to update the menu bar icons. [[NSNotificationCenter defaultCenter] postNotificationName:AIMenuBarIconsDidChangeNotification if (firstTime || [key isEqualToString:KEY_LIST_THEME_NAME]) { [self applySetWithName:[prefDict objectForKey:KEY_LIST_THEME_NAME] extension:LIST_THEME_EXTENSION inFolder:LIST_THEME_FOLDER toPreferenceGroup:PREF_GROUP_LIST_THEME]; if (firstTime || [key isEqualToString:KEY_LIST_LAYOUT_NAME]) { [self applySetWithName:[prefDict objectForKey:KEY_LIST_LAYOUT_NAME] extension:LIST_LAYOUT_EXTENSION inFolder:LIST_LAYOUT_FOLDER toPreferenceGroup:PREF_GROUP_LIST_LAYOUT]; * @brief An invalid status set was activated * Reset to the default by clearing our preference - (void)invalidStatusSetActivated:(NSNotification *)inNotification [adium.preferenceController setPreference:nil forKey:KEY_STATUS_ICON_PACK group:PREF_GROUP_APPEARANCE]; //Tell the preferences to update [preferences xtrasChanged:nil]; #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]; NSInteger availableSetSort(NSDictionary *objectA, NSDictionary *objectB, void *context) { return [[objectA objectForKey:@"name"] caseInsensitiveCompare:[objectB objectForKey:@"name"]]; - (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 sortedArrayUsingFunction:availableSetSort context:nil]; - (NSArray *)availableLayoutSets return [self availableSetsWithExtension:LIST_LAYOUT_EXTENSION fromFolder:LIST_LAYOUT_FOLDER]; - (NSArray *)availableThemeSets return [self availableSetsWithExtension:LIST_THEME_EXTENSION fromFolder:LIST_THEME_FOLDER];