adium/adium

Docs: Minor tweaks.
adium-1.5.11
2016-09-04, Robert Vehse
307f53385811
Docs: Minor tweaks.
/*
* 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 "AIPreferenceController.h"
#import <Adium/AIContactControllerProtocol.h>
#import <Adium/AIContactObserverManager.h>
#import <Adium/AILoginControllerProtocol.h>
#import <Adium/AIToolbarControllerProtocol.h>
#import "AIPreferenceWindowController.h"
#import <AIUtilities/AIDictionaryAdditions.h>
#import <AIUtilities/AIFileManagerAdditions.h>
#import <AIUtilities/AIStringAdditions.h>
#import <AIUtilities/AIToolbarUtilities.h>
#import <AIUtilities/AIImageAdditions.h>
#import <Adium/AIListObject.h>
#import "AIPreferenceContainer.h"
#import "AIPreferencePane.h"
#import "AIAdvancedPreferencePane.h"
#define TITLE_OPEN_PREFERENCES AILocalizedString(@"Open Preferences",nil)
#define LOADED_OBJECT_PREFS_KEY @"Loaded individual object & account prefs"
#define PREFS_GROUP @"Preferences"
@interface AIPreferenceController ()
- (AIPreferenceContainer *)preferenceContainerForGroup:(NSString *)group object:(AIListObject *)object;
- (void)upgradeToSingleObjectPrefsDictIfNeeded;
@end
/*!
* @class AIPreferenceController
* @brief Preference Controller
*
* Handles loading and saving preferences, default preferences, and preference changed notifications
*/
@implementation AIPreferenceController
/*!
* @brief Initialize
*/
- (id)init
{
if ((self = [super init])) {
//
paneArray = [[NSMutableArray alloc] init];
advancedPaneArray = [[NSMutableArray alloc] init];
prefCache = [[NSMutableDictionary alloc] init];
objectPrefCache = [[NSMutableDictionary alloc] init];
observers = [[NSMutableDictionary alloc] init];
delayedNotificationGroups = [[NSMutableSet alloc] init];
preferenceChangeDelays = 0;
}
return self;
}
/*!
* @brief Finish initialization
*/
- (void)controllerDidLoad
{
[self upgradeToSingleObjectPrefsDictIfNeeded];
}
/*!
* @brief Upgrade to a single, monolithic prefs dictionary for all objects
*
* Adium 1.2 and below used a separate plist file on disk for each object. This is a nice memory optimization but a nasty performance hit.
* This code moves all those plists into a single file when first run and is a no-op after that.
*/
- (void)upgradeToSingleObjectPrefsDictIfNeeded
{
if (![[self preferenceForKey:LOADED_OBJECT_PREFS_KEY group:PREF_GROUP_GENERAL] boolValue]) {
NSString *userDirectory = [adium.loginController userDirectory];
NSMutableDictionary *prefsDict;
NSString *dir;
NSEnumerator *enumerator;
NSString *file;
dir = [userDirectory stringByAppendingPathComponent:OBJECT_PREFS_PATH];
prefsDict = [NSMutableDictionary dictionary];
enumerator = [[NSFileManager defaultManager] enumeratorAtPath:dir];
while ((file = [enumerator nextObject])) {
NSString *name = [file stringByDeletingPathExtension];
NSMutableDictionary *thisDict = [NSMutableDictionary dictionaryAtPath:dir
withName:name
create:NO];
if ([thisDict count]) {
[thisDict removeObjectForKey:@"Message Context"];
//This was previously written out for every single contact. It's only needed for the exceptions
[thisDict removeObjectForKey:@"Last Used Spelling Languge"];
//This was previously written out for every single contact. It's only needed for the exceptions
[thisDict removeObjectForKey:@"Base Writing Direction"];
[prefsDict setObject:thisDict
forKey:name];
}
}
[prefsDict asyncWriteToPath:userDirectory
withName:@"ByObjectPrefs"];
dir = [userDirectory stringByAppendingPathComponent:ACCOUNT_PREFS_PATH];
prefsDict = [NSMutableDictionary dictionary];
enumerator = [[NSFileManager defaultManager] enumeratorAtPath:dir];
while ((file = [enumerator nextObject])) {
NSString *name = [file stringByDeletingPathExtension];
NSDictionary *thisDict = [NSDictionary dictionaryAtPath:dir
withName:name
create:NO];
if ([thisDict count]) {
[prefsDict setObject:thisDict
forKey:name];
}
}
[prefsDict asyncWriteToPath:userDirectory
withName:@"AccountPrefs"];
[self setPreference:[NSNumber numberWithBool:YES]
forKey:LOADED_OBJECT_PREFS_KEY group:PREF_GROUP_GENERAL];
}
}
/*!
* @brief Close
*/
- (void)controllerWillClose
{
[AIPreferenceContainer preferenceControllerWillClose];
}
/*!
* @brief Deallocate
*/
- (void)dealloc
{
[delayedNotificationGroups release]; delayedNotificationGroups = nil;
[paneArray release]; paneArray = nil;
[prefCache release]; prefCache = nil;
[objectPrefCache release]; objectPrefCache = nil;
[super dealloc];
}
//Preference Window ----------------------------------------------------------------------------------------------------
#pragma mark Preference Window
/*!
* @brief Show the preference window
*/
- (IBAction)showPreferenceWindow:(id)sender
{
[AIPreferenceWindowController openPreferenceWindow];
}
- (IBAction)closePreferenceWindow:(id)sender
{
[AIPreferenceWindowController closePreferenceWindow];
}
/*!
* @brief Show a specific category of the preference window
*
* Opens the preference window if necessary
*
* @param category The category to show
*/
- (void)openPreferencesToCategoryWithIdentifier:(NSString *)identifier
{
[AIPreferenceWindowController openPreferenceWindowToCategoryWithIdentifier:identifier];
}
/*!
* @brief Add a view to the preferences
*/
- (void)addPreferencePane:(AIPreferencePane *)inPane
{
[paneArray addObject:inPane];
}
/*!
* @brief Add a view to the preferences
*/
- (void)removePreferencePane:(AIPreferencePane *)inPane
{
[paneArray removeObject:inPane];
}
/*!
* @brief Returns all currently available preference panes
*/
- (NSArray *)paneArray
{
return paneArray;
}
/*!
* @brief Add a view to the preferences
*/
- (void)addAdvancedPreferencePane:(AIAdvancedPreferencePane *)inPane
{
[advancedPaneArray addObject:inPane];
}
- (NSArray *)advancedPaneArray
{
return advancedPaneArray;
}
//Observing ------------------------------------------------------------------------------------------------------------
#pragma mark Observing
/*!
* @brief Register a preference observer
*
* The preference observer will be notified when preferences in group change and passed the preference dictionary for that group
* The observer must implement:
* - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
*
*/
- (void)registerPreferenceObserver:(id)observer forGroup:(NSString *)group
{
NSMutableArray *groupObservers;
NSParameterAssert([observer respondsToSelector:@selector(preferencesChangedForGroup:key:object:preferenceDict:firstTime:)]);
//Fetch the observers for this group
if (!(groupObservers = [observers objectForKey:group])) {
groupObservers = [[NSMutableArray alloc] init];
[observers setObject:groupObservers forKey:group];
[groupObservers release];
}
//Add our new observer
[groupObservers addObject:[NSValue valueWithNonretainedObject:observer]];
//Blanket change notification for initialization
[observer preferencesChangedForGroup:group
key:nil
object:nil
preferenceDict:[[self preferenceContainerForGroup:group object:nil] dictionary]
firstTime:YES];
}
/*!
* @brief Unregister a preference observer
*/
- (void)unregisterPreferenceObserver:(id)observer
{
NSEnumerator *enumerator = [observers objectEnumerator];
NSMutableArray *observerArray;
NSValue *observerValue = [NSValue valueWithNonretainedObject:observer];
while ((observerArray = [enumerator nextObject])) {
[observerArray removeObject:observerValue];
}
}
/*!
* @brief Broadcast a key changed notification.
*
* Broadcasts a group changed notification if key is nil.
*
* If notifications are delayed, remember the group that changed and broadcast this notification when the delay is
* lifted instead of immediately. Currently, our delayed notification system isn't setup to handle object-specific
* preferences, so always notify if there is an object present for now.
*
* @param key The key
* @param group The group
* @param object The object, or nil if global
*/
- (void)informObserversOfChangedKey:(NSString *)key inGroup:(NSString *)group object:(AIListObject *)object
{
if (!object && preferenceChangeDelays > 0) {
[delayedNotificationGroups addObject:group];
} else {
NSDictionary *preferenceDict = [[[self preferenceContainerForGroup:group object:object] dictionary] retain];
for (NSValue *observerValue in [[[observers objectForKey:group] copy] autorelease]) {
id observer = observerValue.nonretainedObjectValue;
[observer preferencesChangedForGroup:group
key:key
object:object
preferenceDict:preferenceDict
firstTime:NO];
}
[preferenceDict release];
}
}
/*!
* @brief Set if preference changed notifications should be delayed
*
* Changing large amounts of preferences at once causes a lot of notification overhead. This should be used like
* [lockFocus] / [unlockFocus] around groups of preference changes to improve performance.
*/
- (void)delayPreferenceChangedNotifications:(BOOL)inDelay
{
if (inDelay) {
preferenceChangeDelays++;
} else {
preferenceChangeDelays--;
}
//If changes are no longer delayed, save and notify of all preferences modified while delayed
if (!preferenceChangeDelays) {
NSString *group;
[[AIContactObserverManager sharedManager] delayListObjectNotifications];
for (group in delayedNotificationGroups) {
[self informObserversOfChangedKey:nil inGroup:group object:nil];
}
[[AIContactObserverManager sharedManager] endListObjectNotificationsDelay];
[delayedNotificationGroups removeAllObjects];
}
}
//Setting Preferences -------------------------------------------------------------------
#pragma mark Setting Preferences
/*!
* @brief Set a global preference
*
* Set and save a preference at the global level.
*
* @param value The preference, which must be plist-encodable
* @param key An arbitrary NSString key
* @param group An arbitrary NSString group
*/
- (void)setPreference:(id)value forKey:(NSString *)key group:(NSString *)group{
[self setPreference:value forKey:key group:group object:nil];
}
/*!
* @brief Set multiple preferences at once
*
* @param inPrefDict An NSDictionary whose keys are preference keys and objects are the preferences for those keys. All must be plist-encodable.
* @param group An arbitrary NSString group
*/
- (void)setPreferences:(NSDictionary *)inPrefDict inGroup:(NSString *)group object:(AIListObject *)object
{
AIPreferenceContainer *prefContainer = [self preferenceContainerForGroup:group object:object];
[prefContainer setPreferenceChangedNotificationsEnabled:NO];
[prefContainer setValuesForKeysWithDictionary:inPrefDict];
[prefContainer setPreferenceChangedNotificationsEnabled:YES];
}
/*!
* @brief Set multiple global preferences at once
*
* @param inPrefDict An NSDictionary whose keys are preference keys and objects are the preferences for those keys. All must be plist-encodable.
* @param group An arbitrary NSString group
*/
- (void)setPreferences:(NSDictionary *)inPrefDict inGroup:(NSString *)group
{
[self setPreferences:inPrefDict inGroup:group object:nil];
}
/*!
* @brief Set a global or object-specific preference
*
* Set and save a preference. This should not be called directly from plugins or components. To set an object-specific
* preference, use the appropriate method on the object. To set a global preference, use setPreference:forKey:group:
*/
- (void)setPreference:(id)value
forKey:(NSString *)key
group:(NSString *)group
object:(AIListObject *)object
{
[[self preferenceContainerForGroup:group object:object] setValue:value forKey:key];
}
//Retrieving Preferences ----------------------------------------------------------------
#pragma mark Retrieving Preferences
/*!
* @brief Retrieve a preference
*/
- (id)preferenceForKey:(NSString *)key group:(NSString *)group
{
return [self preferenceForKey:key group:group objectIgnoringInheritance:nil];
}
/*!
* @brief Retrieve an object specific preference with inheritance, ignoring defaults
*
* Should only be used within AIPreferenceController. See preferenceForKey:group:object: for details.
*/
- (id)_noDefaultsPreferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
{
return [[self preferenceContainerForGroup:group object:object] valueForKey:key ignoringDefaults:YES];
}
/*!
* @brief Retrieve an object specific default preference with inheritance
*/
- (id)defaultPreferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
{
return [[self preferenceContainerForGroup:group object:object] defaultValueForKey:key];
}
/*!
* @brief Retrieve an object specific preference with inheritance.
*
* Objects inherit from their containing objects, up to the global preference. If this entire tree has no set preference,
* defaults are searched, starting against with the object and proceeding up to the global defaults.
*/
- (id)preferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
{
//Don't use the defaults initially
id result = [self _noDefaultsPreferenceForKey:key group:group object:object];
//If no result, try defaults
if (!result) result = [self defaultPreferenceForKey:key group:group object:object];
return result;
}
/*!
* @brief Retrieve an object specific preference ignoring inheritance.
*
* If object is nil, this returns the global preference. Uses defaults only for the specified preference level,
* not inherited defaults, as expected.
*/
- (id)preferenceForKey:(NSString *)key group:(NSString *)group objectIgnoringInheritance:(AIListObject *)object
{
//We are ignoring inheritance, so we can ignore inherited defaults, too, and use the preferenceContainerForGroup:object: dict
id result = [[self preferenceContainerForGroup:group object:object] valueForKey:key];
return result;
}
/*!
* @brief Retrieve all the preferences in a group
*
* @result A dictionary of preferences for the group, including default values as appropriate
*/
- (NSDictionary *)preferencesForGroup:(NSString *)group
{
return [[self preferenceContainerForGroup:group object:nil] dictionary];
}
//Defaults -------------------------------------------------------------------------------------------------------------
#pragma mark Defaults
/*!
* @brief Register a dictionary of defaults.
*/
- (void)registerDefaults:(NSDictionary *)defaultDict forGroup:(NSString *)group{
[self registerDefaults:defaultDict forGroup:group object:nil];
}
/*!
* @brief Register a dictionary of object-specific defaults.
*/
- (void)registerDefaults:(NSDictionary *)defaultDict forGroup:(NSString *)group object:(AIListObject *)object
{
AIPreferenceContainer *prefContainer = [self preferenceContainerForGroup:group object:object];
[prefContainer registerDefaults:defaultDict];
[self informObserversOfChangedKey:nil inGroup:group object:object];
}
#pragma mark Preference Container
/*!
* @brief Retrieve an AIPreferenceContainer
*
* @param group The group
* @param object The object, or nil for global
*/
- (AIPreferenceContainer *)preferenceContainerForGroup:(NSString *)group object:(AIListObject *)object
{
AIPreferenceContainer *prefContainer;
if (object) {
NSString *cacheKey = [object.internalObjectID stringByAppendingString:group];
if ((prefContainer = [objectPrefCache objectForKey:cacheKey])) {
//Until we access this pref container again, it will be associated with the passed group
[prefContainer setGroup:group];
} else {
prefContainer = [AIPreferenceContainer preferenceContainerForGroup:group
object:object];
[objectPrefCache setObject:prefContainer forKey:cacheKey];
}
} else {
if (!(prefContainer = [prefCache objectForKey:group])) {
prefContainer = [AIPreferenceContainer preferenceContainerForGroup:group
object:object];
[prefCache setObject:prefContainer forKey:group];
}
}
return prefContainer;
}
//Default download locaiton --------------------------------------------------------------------------------------------
#pragma mark Default download location
/*!
* @brief Get the default download location
*
* This will use an Adium-specific preference if set, or the systemwide download location if not
*
* @result A full path to the download location
*/
- (NSString *)userPreferredDownloadFolder
{
NSString *userPreferredDownloadFolder;
userPreferredDownloadFolder = [[self preferenceForKey:@"UserPreferredDownloadFolder"
group:PREF_GROUP_GENERAL] stringByExpandingTildeInPath];
if (!userPreferredDownloadFolder) {
//10.5: ICGetPref() for kICDownloadFolder is useless
CFURLRef urlToDefaultBrowser = NULL;
//Use Safari's preference as a default if it's the default browser and it is set
if (LSGetApplicationForURL((CFURLRef)[NSURL URLWithString:@"http://google.com"],
kLSRolesViewer,
NULL /*outAppRef*/,
&urlToDefaultBrowser) != kLSApplicationNotFoundErr) {
NSString *defaultBrowserName = nil;
defaultBrowserName = [[NSFileManager defaultManager] displayNameAtPath:[(NSURL *)urlToDefaultBrowser path]];
if ([defaultBrowserName rangeOfString:@"Safari"].location != NSNotFound) {
/* ICGetPref() for kICDownloadFolder returns any previously set preference, not the default ~/Downloads or the current
* Safari setting, in 10.5.0, with Safari the default browser
*/
CFPropertyListRef safariDownloadsPath = CFPreferencesCopyAppValue(CFSTR("DownloadsPath"),CFSTR("com.apple.Safari"));
if (safariDownloadsPath) {
//This should return a CFStringRef... we're using another app's prefs, so make sure.
if (CFGetTypeID(safariDownloadsPath) == CFStringGetTypeID()) {
userPreferredDownloadFolder = (NSString *)safariDownloadsPath;
}
[(NSObject *)safariDownloadsPath autorelease];
}
}
}
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, YES);
if ([searchPaths count]) {
userPreferredDownloadFolder = [searchPaths objectAtIndex:0];
}
}
/* If we can't write to the specified folder, fall back to the desktop and then to the home directory;
* if neither are writable the user has worse problems then an IM download to worry about.
*/
if (![[NSFileManager defaultManager] isWritableFileAtPath:userPreferredDownloadFolder]) {
NSString *originalFolder = userPreferredDownloadFolder;
userPreferredDownloadFolder = [NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"];
if (![[NSFileManager defaultManager] isWritableFileAtPath:userPreferredDownloadFolder]) {
userPreferredDownloadFolder = NSHomeDirectory();
}
NSLog(@"Could not obtain write access for %@; defaulting to %@",
originalFolder,
userPreferredDownloadFolder);
}
return userPreferredDownloadFolder;
}
/*!
* @brief Set the location Adium should use for saving files
*
* @param A path to an existing folder
*/
- (void)setUserPreferredDownloadFolder:(NSString *)path
{
[self setPreference:[path stringByAbbreviatingWithTildeInPath]
forKey:@"UserPreferredDownloadFolder"
group:PREF_GROUP_GENERAL];
}
#pragma mark KVC
static void parseKeypath(NSString *keyPath, NSString **outGroup, NSString **outKeyPath, NSString **outInternalObjectID)
{
NSRange prefixRange = [keyPath rangeOfString:@"Group:" options:NSLiteralSearch | NSAnchoredSearch];
NSString *groupWithKeyPath = keyPath;
NSString *group = nil, *finalKeyPath = nil;
NSString *internalObjectID = nil;
if (prefixRange.location == 0) {
//Allow a Group: prefix, stripping it out if present.
groupWithKeyPath = [keyPath substringFromIndex:prefixRange.length];
} else {
prefixRange = [keyPath rangeOfString:@"ByObject:" options:(NSLiteralSearch | NSAnchoredSearch)];
if (prefixRange.location == 0) {
keyPath = [keyPath substringFromIndex:prefixRange.length];
NSRange nextPeriod = [keyPath rangeOfString:@"."
options:NSLiteralSearch
range:NSMakeRange(0, [keyPath length])];
internalObjectID = [keyPath substringToIndex:nextPeriod.location];
groupWithKeyPath = [keyPath substringFromIndex:nextPeriod.location + 1];
}
}
//We need the key to do AIPC change notifications.
NSInteger periodIdx = [groupWithKeyPath rangeOfString:@"." options:NSLiteralSearch].location;
if (periodIdx == NSNotFound) {
group = groupWithKeyPath;
} else {
group = [groupWithKeyPath substringToIndex:periodIdx];
finalKeyPath = [groupWithKeyPath substringFromIndex:periodIdx + 1];
}
if (outGroup) *outGroup = group;
if (outKeyPath) *outKeyPath = finalKeyPath;
if (outInternalObjectID) *outInternalObjectID = internalObjectID;
}
+ (BOOL) accessInstanceVariablesDirectly {
return NO;
}
- (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
NSUInteger periodIdx = [keyPath rangeOfString:@"." options:NSLiteralSearch].location;
if(periodIdx == NSNotFound) {
[super addObserver:anObserver forKeyPath:keyPath options:options context:context];
} else {
NSString *group, *newKeyPath, *internalObjectID;
parseKeypath(keyPath, &group, &newKeyPath, &internalObjectID);
AIPreferenceContainer *prefContainer = [self preferenceContainerForGroup:group
object:(internalObjectID ? [adium.contactController existingListObjectWithUniqueID:internalObjectID] : nil)];
[prefContainer addObserver:anObserver forKeyPath:newKeyPath options:options context:context];
}
}
- (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath ofObject:(AIListObject *)listObject options:(NSKeyValueObservingOptions)options context:(void *)context
{
NSString *group, *newKeyPath, *internalObjectID;
parseKeypath(keyPath, &group, &newKeyPath, &internalObjectID);
AIPreferenceContainer *prefContainer = [self preferenceContainerForGroup:group object:listObject];
[prefContainer addObserver:anObserver forKeyPath:newKeyPath options:options context:context];
}
- (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath
{
NSUInteger periodIdx = [keyPath rangeOfString:@"." options:NSLiteralSearch].location;
if(periodIdx == NSNotFound) {
[super removeObserver:anObserver forKeyPath:keyPath];
} else {
NSString *group, *newKeyPath, *internalObjectID;
parseKeypath(keyPath, &group, &newKeyPath, &internalObjectID);
AIPreferenceContainer *prefContainer = [self preferenceContainerForGroup:group
object:(internalObjectID ? [adium.contactController existingListObjectWithUniqueID:internalObjectID] : nil)];
[prefContainer removeObserver:anObserver forKeyPath:newKeyPath];
}
}
- (id) valueForKey:(NSString *)key {
return [self preferenceContainerForGroup:key object:nil];
}
- (id) valueForKeyPath:(NSString *)keyPath {
NSUInteger periodIdx = [keyPath rangeOfString:@"." options:NSLiteralSearch].location;
if(periodIdx == NSNotFound) {
return [self valueForKey:keyPath];
} else {
NSString *group, *newKeyPath, *internalObjectID;
parseKeypath(keyPath, &group, &newKeyPath, &internalObjectID);
return [[self preferenceContainerForGroup:group
object:(internalObjectID ? [adium.contactController existingListObjectWithUniqueID:internalObjectID] : nil)]
valueForKeyPath:newKeyPath];
}
}
/*!
* @brief Set a dictionary of preferences for a group
*
* Note that while setPreferences:inGroup: adds the passed dictionary to the current one, this method replaces the dictionary entirely
*
* @param value An NSDictionary which reprsents an entire group of preferences (without defaults)
* @param key The group name
*/
- (void) setValue:(id)value forKey:(NSString *)key {
NSString *group = nil;
NSString *internalObjectID = nil;
parseKeypath(key, &group, NULL, &internalObjectID);
[[self preferenceContainerForGroup:group
object:(internalObjectID ?
[adium.contactController existingListObjectWithUniqueID:internalObjectID] :
nil)] setPreferences:value];
}
/*
* Key paths:
* No prefix: Group
* "Group:": Group
* "ByObject" (futar): by-object (objectXyz instead of xyz ivars)
*
* For example, General.MyKey would refer to the MyKey value of the General group, as would Group:General.MyKey
*/
- (void) setValue:(id)value forKeyPath:(NSString *)keyPath {
NSUInteger periodIdx = [keyPath rangeOfString:@"." options:NSLiteralSearch].location;
if(periodIdx == NSNotFound) {
NSString *key = [keyPath substringToIndex:periodIdx];
[self setValue:value forKey:key];
} else {
NSString *group, *newKeyPath, *internalObjectID;
parseKeypath(keyPath, &group, &newKeyPath, &internalObjectID);
//Change the value.
AIPreferenceContainer *prefContainer = [self preferenceContainerForGroup:group
object:(internalObjectID ? [adium.contactController existingListObjectWithUniqueID:internalObjectID] : nil)];
[prefContainer setValue:value forKeyPath:newKeyPath];
}
}
@end