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 "AIPreferenceController.h"
19 #import <Adium/AIContactControllerProtocol.h>
20 #import <Adium/AILoginControllerProtocol.h>
21 #import <Adium/AIToolbarControllerProtocol.h>
23 #import "AIPreferenceWindowController.h"
24 #import <AIUtilities/AIDictionaryAdditions.h>
25 #import <AIUtilities/AIFileManagerAdditions.h>
26 #import <AIUtilities/AIStringAdditions.h>
27 #import <AIUtilities/AIToolbarUtilities.h>
28 #import <AIUtilities/AIImageAdditions.h>
29 #import <Adium/AIListObject.h>
30 #import "AIPreferencePane.h"
31 #import "AIAdvancedPreferencePane.h"
33 #define PREFS_DEFAULT_PREFS @"PrefsPrefs.plist"
34 #define TITLE_OPEN_PREFERENCES AILocalizedString(@"Open Preferences",nil)
36 @interface AIPreferenceController (PRIVATE)
37 - (NSDictionary *)cachedDefaultsForGroup:(NSString *)group object:(AIListObject *)object;
38 - (NSDictionary *)cachedPreferencesWithDefaultsForGroup:(NSString *)group object:(AIListObject *)object;
39 - (NSMutableDictionary *)cachedPreferencesForGroup:(NSString *)group object:(AIListObject *)object;
41 - (void)updatePreferences:(NSMutableDictionary *)prefDict forKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object;
45 * @class AIPreferenceController
46 * @brief Preference Controller
48 * Handles loading and saving preferences, default preferences, and preference changed notifications
50 @implementation AIPreferenceController
57 if ((self = [super init])) {
59 paneArray = [[NSMutableArray alloc] init];
60 advancedPaneArray = [[NSMutableArray alloc] init];
62 defaults = [[NSMutableDictionary alloc] init];
63 prefCache = [[NSMutableDictionary alloc] init];
64 prefWithDefaultsCache = [[NSMutableDictionary alloc] init];
66 objectDefaults = [[NSMutableDictionary alloc] init];
67 objectPrefCache = [[NSMutableDictionary alloc] init];
68 objectPrefWithDefaultsCache = [[NSMutableDictionary alloc] init];
70 observers = [[NSMutableDictionary alloc] init];
71 delayedNotificationGroups = [[NSMutableSet alloc] init];
72 preferenceChangeDelays = 0;
79 * @brief Finish initialization
81 * Sets up the toolbar items.
82 * We can't do these in initing, since the toolbar controller hasn't loaded yet at that point.
84 - (void)controllerDidLoad
87 userDirectory = [[[adium loginController] userDirectory] retain];
89 //Create the 'ByObject' and 'Accounts' object specific preference directory
90 [[NSFileManager defaultManager] createDirectoriesForPath:[userDirectory stringByAppendingPathComponent:OBJECT_PREFS_PATH]];
91 [[NSFileManager defaultManager] createDirectoriesForPath:[userDirectory stringByAppendingPathComponent:ACCOUNT_PREFS_PATH]];
93 //Register our default preferences
94 [self registerDefaults:[NSDictionary dictionaryNamed:PREFS_DEFAULT_PREFS forClass:[self class]] forGroup:PREF_GROUP_GENERAL];
100 - (void)controllerWillClose
102 //Preferences are (always) saved as they're modified, so there's no need to save them here.
110 [delayedNotificationGroups release]; delayedNotificationGroups = nil;
111 [paneArray release]; paneArray = nil;
112 [prefCache release]; prefCache = nil;
113 [objectPrefCache release]; objectPrefCache = nil;
119 //Preference Window ----------------------------------------------------------------------------------------------------
120 #pragma mark Preference Window
122 * @brief Show the preference window
124 - (IBAction)showPreferenceWindow:(id)sender
126 [AIPreferenceWindowController openPreferenceWindow];
129 - (IBAction)closePreferenceWindow:(id)sender
131 [AIPreferenceWindowController closePreferenceWindow];
135 * @brief Show a specific category of the preference window
137 * Opens the preference window if necessary
139 * @param category The category to show
141 - (void)openPreferencesToCategoryWithIdentifier:(NSString *)identifier
143 [AIPreferenceWindowController openPreferenceWindowToCategoryWithIdentifier:identifier];
147 * @brief Add a view to the preferences
149 - (void)addPreferencePane:(AIPreferencePane *)inPane
151 [paneArray addObject:inPane];
155 * @brief Returns all currently available preference panes
157 - (NSArray *)paneArray
163 * @brief Add a view to the preferences
165 - (void)addAdvancedPreferencePane:(AIAdvancedPreferencePane *)inPane
167 [advancedPaneArray addObject:inPane];
170 - (NSArray *)advancedPaneArray
172 return advancedPaneArray;
175 //Observing ------------------------------------------------------------------------------------------------------------
176 #pragma mark Observing
178 * @brief Register a preference observer
180 * The preference observer will be notified when preferences in group change and passed the preference dictionary for that group
181 * The observer must implement:
182 * - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
185 - (void)registerPreferenceObserver:(id)observer forGroup:(NSString *)group
187 NSMutableSet *groupObservers;
189 NSParameterAssert([observer respondsToSelector:@selector(preferencesChangedForGroup:key:object:preferenceDict:firstTime:)]);
191 //Fetch the observers for this group
192 if (!(groupObservers = [observers objectForKey:group])) {
193 groupObservers = [[NSMutableSet alloc] init];
194 [observers setObject:groupObservers forKey:group];
195 [groupObservers release];
198 //Add our new observer
199 [groupObservers addObject:[NSValue valueWithNonretainedObject:observer]];
201 //Blanket change notification for initialization
202 [observer preferencesChangedForGroup:group
205 preferenceDict:[self cachedPreferencesWithDefaultsForGroup:group object:nil]
210 * @brief Unregister a preference observer
212 - (void)unregisterPreferenceObserver:(id)observer
214 NSEnumerator *enumerator = [observers objectEnumerator];
215 NSMutableArray *observerArray;
216 NSValue *observerValue = [NSValue valueWithNonretainedObject:observer];
218 while ((observerArray = [enumerator nextObject])) {
219 [observerArray removeObject:observerValue];
224 * @brief Broadcast a key changed notification.
226 * Broadcasts a group changed notification if key is nil.
228 * If notifications are delayed, remember the group that changed and broadcast this notification when the delay is
229 * lifted instead of immediately. Currently, our delayed notification system isn't setup to handle object-specific
230 * preferences, so always notify if there is an object present for now.
233 * @param group The group
234 * @param object The object, or nil if global
236 - (void)informObserversOfChangedKey:(NSString *)key inGroup:(NSString *)group object:(AIListObject *)object
238 if (!object && preferenceChangeDelays > 0) {
239 [delayedNotificationGroups addObject:group];
241 NSDictionary *preferenceDict = [self cachedPreferencesWithDefaultsForGroup:group object:object];
242 NSEnumerator *enumerator = [[observers objectForKey:group] objectEnumerator];
243 NSValue *observerValue;
245 while ((observerValue = [enumerator nextObject])) {
246 id observer = [observerValue nonretainedObjectValue];
248 [observer preferencesChangedForGroup:group
251 preferenceDict:preferenceDict
258 * @brief Set if preference changed notifications should be delayed
260 * Changing large amounts of preferences at once causes a lot of notification overhead. This should be used like
261 * [lockFocus] / [unlockFocus] around groups of preference changes to improve performance.
263 - (void)delayPreferenceChangedNotifications:(BOOL)inDelay
266 preferenceChangeDelays++;
268 preferenceChangeDelays--;
271 //If changes are no longer delayed, save and notify of all preferences modified while delayed
272 if (!preferenceChangeDelays) {
273 NSEnumerator *enumerator = [delayedNotificationGroups objectEnumerator];
276 [[adium contactController] delayListObjectNotifications];
278 while ((group = [enumerator nextObject])) {
279 [self informObserversOfChangedKey:nil inGroup:group object:nil];
282 [[adium contactController] endListObjectNotificationsDelay];
284 [delayedNotificationGroups removeAllObjects];
289 //Setting Preferences -------------------------------------------------------------------
290 #pragma mark Setting Preferences
292 * @brief Set a global preference
294 * Set and save a preference at the global level.
296 * @param value The preference, which must be plist-encodable
297 * @param key An arbitrary NSString key
298 * @param group An arbitrary NSString group
300 - (void)setPreference:(id)value forKey:(NSString *)key group:(NSString *)group{
301 [self setPreference:value forKey:key group:group object:nil];
305 * @brief Set multiple preferences at once
307 * @param inPrefDict An NSDictionary whose keys are preference keys and objects are the preferences for those keys. All must be plist-encodable.
308 * @param group An arbitrary NSString group
310 - (void)setPreferences:(NSDictionary *)inPrefDict inGroup:(NSString *)group object:(AIListObject *)object
312 NSMutableDictionary *prefDict = [self cachedPreferencesForGroup:group object:object];
314 [prefDict addEntriesFromDictionary:inPrefDict];
315 [self updatePreferences:prefDict forKey:nil group:group object:object];
319 * @brief Set multiple global preferences at once
321 * @param inPrefDict An NSDictionary whose keys are preference keys and objects are the preferences for those keys. All must be plist-encodable.
322 * @param group An arbitrary NSString group
324 - (void)setPreferences:(NSDictionary *)inPrefDict inGroup:(NSString *)group
326 [self setPreferences:inPrefDict inGroup:group object:nil];
330 * @brief Set a global or object-specific preference
332 * Set and save a preference. This should not be called directly from plugins or components. To set an object-specific
333 * preference, use the appropriate method on the object. To set a global preference, use setPreference:forKey:group:
335 - (void)setPreference:(id)value
336 forKey:(NSString *)key
337 group:(NSString *)group
338 object:(AIListObject *)object
340 NSMutableDictionary *prefDict = [self cachedPreferencesForGroup:group object:object];
345 [prefDict setObject:value forKey:key];
347 if ([prefDict objectForKey:key]) {
348 [prefDict removeObjectForKey:key];
354 //Update the preference cache with our changes
356 [self updatePreferences:prefDict forKey:key group:group object:object];
361 //Retrieving Preferences ----------------------------------------------------------------
362 #pragma mark Retrieving Preferences
364 * @brief Retrieve a preference
366 - (id)preferenceForKey:(NSString *)key group:(NSString *)group
368 return [self preferenceForKey:key group:group objectIgnoringInheritance:nil];
372 * @brief Retrieve an object specific preference with inheritance, ignoring defaults
374 * Should only be used within AIPreferenceController. See preferenceForKey:group:object: for details.
376 - (id)_noDefaultsPreferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
378 id result = [[self cachedPreferencesForGroup:group object:object] objectForKey:key];
380 //If there is no object specific preference, inherit the value from the object containing this one
381 if (!result && object) {
382 return [self _noDefaultsPreferenceForKey:key group:group object:[object containingObject]];
384 //If we have no object (either were passed no object initially or got here recursively) use defaults if necessary
390 * @brief Retrieve an object specific default preference with inheritance
392 - (id)defaultPreferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
394 //Don't use the defaults initially
395 id result = [[self cachedDefaultsForGroup:group object:object] objectForKey:key];
397 //If there is no object specific preference, inherit the value from the object containing this one
398 if (!result && object) {
399 return [self defaultPreferenceForKey:key group:group object:[object containingObject]];
401 //If we have no object (either were passed no object initially or got here recursively) use defaults if necessary
407 * @brief Retrieve an object specific preference with inheritance.
409 * Objects inherit from their containing objects, up to the global preference. If this entire tree has no preference
410 * defaults are searched starting with the object and continuing up to global.
412 - (id)preferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
414 //Don't use the defaults initially
415 id result = [self _noDefaultsPreferenceForKey:key group:group object:object];
417 //If no result, try defaults
418 if (!result) result = [self defaultPreferenceForKey:key group:group object:object];
424 * @brief Retrieve an object specific preference ignoring inheritance.
426 * If object is nil, this returns the global preference. Uses defaults only for the specified preference level,
427 * not inherited defaults, as expected.
429 - (id)preferenceForKey:(NSString *)key group:(NSString *)group objectIgnoringInheritance:(AIListObject *)object
431 //We are ignoring inheritance, so we can ignore inherited defaults, too, and use the cachedPreferencesWithDefaultsForGroup:object: dict
432 id result = [[self cachedPreferencesWithDefaultsForGroup:group object:object] objectForKey:key];
438 * @brief Retrieve all the preferences in a group
440 * @result A dictionary of preferences for the group, including default values as appropriate
442 - (NSDictionary *)preferencesForGroup:(NSString *)group
444 return [self cachedPreferencesWithDefaultsForGroup:group object:nil];
447 //Defaults -------------------------------------------------------------------------------------------------------------
448 #pragma mark Defaults
450 * @brief Register a dictionary of defaults.
452 - (void)registerDefaults:(NSDictionary *)defaultDict forGroup:(NSString *)group{
453 [self registerDefaults:defaultDict forGroup:group object:nil];
457 * @brief Register a dictionary of object-specific defaults.
459 - (void)registerDefaults:(NSDictionary *)defaultDict forGroup:(NSString *)group object:(AIListObject *)object
461 NSMutableDictionary *targetDefaultsDict;
462 NSMutableDictionary *activeDefaultsCache;
463 NSMutableDictionary *actualDefaultsDict;
466 //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
468 cacheKey = [object preferencesCacheKey];
469 activeDefaultsCache = objectPrefWithDefaultsCache;
470 targetDefaultsDict = objectDefaults;
474 activeDefaultsCache = prefWithDefaultsCache;
475 targetDefaultsDict = defaults;
479 actualDefaultsDict = [targetDefaultsDict objectForKey:cacheKey];
480 if (!actualDefaultsDict) actualDefaultsDict = [NSMutableDictionary dictionary];
482 [actualDefaultsDict addEntriesFromDictionary:defaultDict];
483 [targetDefaultsDict setObject:actualDefaultsDict
486 //Now clear our current prefWithDefaults cache so it will be regenerated with these entries included on next call
487 [activeDefaultsCache removeObjectForKey:cacheKey];
491 //Preference Cache -----------------------------------------------------------------------------------------------------
492 //We cache the preferences locally to avoid loading them each time we need a value
493 #pragma mark Preference Cache
495 * @brief Fetch cached preferences
497 * @param group The group
498 * @param object The object, or nil for global
500 - (NSMutableDictionary *)cachedPreferencesForGroup:(NSString *)group object:(AIListObject *)object
502 NSMutableDictionary *prefDict;
504 //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
506 NSString *cacheKey = [object preferencesCacheKey];
508 if (!(prefDict = [objectPrefCache objectForKey:cacheKey])) {
509 prefDict = [NSMutableDictionary dictionaryAtPath:[userDirectory stringByAppendingPathComponent:[object pathToPreferences]]
510 withName:[[object internalObjectID] safeFilenameString]
512 [objectPrefCache setObject:prefDict forKey:cacheKey];
516 if (!(prefDict = [prefCache objectForKey:group])) {
517 prefDict = [NSMutableDictionary dictionaryAtPath:userDirectory
520 [prefCache setObject:prefDict forKey:group];
528 * @brief Return just the defaults for a specified group and object
530 * @param group The group
531 * @param object The object, or nil for global defaults
533 - (NSDictionary *)cachedDefaultsForGroup:(NSString *)group object:(AIListObject *)object
535 NSDictionary *sourceDefaultsDict;
538 //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
540 cacheKey = [object preferencesCacheKey];
541 sourceDefaultsDict = objectDefaults;
545 sourceDefaultsDict = defaults;
548 return [sourceDefaultsDict objectForKey:cacheKey];
552 * @brief Locally update our cached prefrences, including defaults
554 * Must be called before preferences are accessed after preferences change for changes to be visible to the rest of Adium
556 - (NSDictionary *)updateCachedPreferencesWithDefaultsForGroup:(NSString *)group object:(AIListObject *)object
558 NSDictionary *prefWithDefaultsDict;
559 NSMutableDictionary *activeDefaultsCache;
560 NSDictionary *sourceDefaultsDict;
563 //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
565 cacheKey = [object preferencesCacheKey];
566 activeDefaultsCache = objectPrefWithDefaultsCache;
567 sourceDefaultsDict = objectDefaults;
571 activeDefaultsCache = prefWithDefaultsCache;
572 sourceDefaultsDict = defaults;
575 NSDictionary *userPrefs = [self cachedPreferencesForGroup:group object:object];
576 NSDictionary *defaultPrefs = [sourceDefaultsDict objectForKey:cacheKey];
578 //Add the object's own preferences to the defaults dictionary to get a dict with the object's keys
579 //overriding the default keys
580 prefWithDefaultsDict = [[defaultPrefs mutableCopy] autorelease];
581 [(NSMutableDictionary *)prefWithDefaultsDict addEntriesFromDictionary:userPrefs];
583 //With no defaults, just use the userPrefs
584 prefWithDefaultsDict = userPrefs;
587 NSMutableDictionary *existingDict;
589 if (!(existingDict = [activeDefaultsCache objectForKey:cacheKey])) {
590 existingDict = [NSMutableDictionary dictionary];
591 [activeDefaultsCache setObject:existingDict forKey:cacheKey];
594 [existingDict setDictionary:prefWithDefaultsDict];
599 * @brief Return the result of taking the defaults and superceding them with any set preferences
601 * @param group The group
602 * @param object The object, or nil for global
604 - (NSDictionary *)cachedPreferencesWithDefaultsForGroup:(NSString *)group object:(AIListObject *)object
606 NSDictionary *prefWithDefaultsDict;
607 NSMutableDictionary *activeDefaultsCache;
608 NSDictionary *sourceDefaultsDict;
611 //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
613 cacheKey = [object preferencesCacheKey];
614 activeDefaultsCache = objectPrefWithDefaultsCache;
615 sourceDefaultsDict = objectDefaults;
619 activeDefaultsCache = prefWithDefaultsCache;
620 sourceDefaultsDict = defaults;
623 if (!(prefWithDefaultsDict = [activeDefaultsCache objectForKey:cacheKey])) {
624 prefWithDefaultsDict = [self updateCachedPreferencesWithDefaultsForGroup:group object:object];
627 return prefWithDefaultsDict;
631 * @brief Write preference changes back to the cache and to disk
633 * @param prefDict The user-specified preferences (not including defaults)
634 * @param key The key that changed, or nil if multiple keys changed
635 * @param group The group
636 * @param object The object, or nil for global
638 - (void)updatePreferences:(NSMutableDictionary *)prefDict forKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
640 //Upgrade code; message context isn't needed now that we draw from logs. This should be removed eventually.
641 [prefDict removeObjectForKey:@"Message Context"];
643 NSString *path = (object ? [userDirectory stringByAppendingPathComponent:[object pathToPreferences]] : userDirectory);
644 NSString *name = (object ? [[object internalObjectID] safeFilenameString] : group);
647 [self updateCachedPreferencesWithDefaultsForGroup:group object:object];
649 //Now inform observers
650 [self informObserversOfChangedKey:key inGroup:group object:object];
652 //Save the preference change immediately (Probably not the best idea?)
653 [prefDict writeToPath:path
657 //Default download locaiton --------------------------------------------------------------------------------------------
658 #pragma mark Default download location
660 * @brief Get the default download location
662 * This will use an Adium-specific preference if set, or the systemwide download location if not
664 * @result A full path to the download location
666 - (NSString *)userPreferredDownloadFolder
668 NSString *userPreferredDownloadFolder;
670 userPreferredDownloadFolder = [self preferenceForKey:@"UserPreferredDownloadFolder"
671 group:PREF_GROUP_GENERAL];
673 if (!userPreferredDownloadFolder) {
674 OSStatus err = noErr;
675 ICInstance inst = NULL;
677 long length = kICFileSpecHeaderSize;
681 memset( path, 0, 1024 ); //clear path's memory range
683 if ((err = ICStart(&inst, 'AdiM')) == noErr) {
684 ICGetPref( inst, kICDownloadFolder, NULL, &folder, &length );
687 if (((err = FSpMakeFSRef(&folder.fss, &ref)) == noErr) &&
688 ((err = FSRefMakePath(&ref, (unsigned char *)path, 1024)) == noErr) &&
689 ((path != NULL) && (strlen(path) > 0))) {
690 userPreferredDownloadFolder = [NSString stringWithUTF8String:path];
695 if (!userPreferredDownloadFolder) {
696 userPreferredDownloadFolder = @"~/Desktop";
699 userPreferredDownloadFolder = [userPreferredDownloadFolder stringByExpandingTildeInPath];
701 /* If we can't write to the specified folder, fall back to the desktop and then to the home directory;
702 * if neither are writable the user has worse problems then an IM download to worry about.
704 if (![[NSFileManager defaultManager] isWritableFileAtPath:userPreferredDownloadFolder]) {
705 NSString *originalFolder = userPreferredDownloadFolder;
707 userPreferredDownloadFolder = [@"~/Desktop" stringByExpandingTildeInPath];
709 if (![[NSFileManager defaultManager] isWritableFileAtPath:userPreferredDownloadFolder]) {
710 userPreferredDownloadFolder = NSHomeDirectory();
713 NSLog(@"Could not obtain write access for %@; defaulting to %@",
715 userPreferredDownloadFolder);
718 return userPreferredDownloadFolder;
722 * @brief Set the location Adium should use for saving files
724 * @param A path to an existing folder
726 - (void)setUserPreferredDownloadFolder:(NSString *)path
728 [self setPreference:[path stringByAbbreviatingWithTildeInPath]
729 forKey:@"UserPreferredDownloadFolder"
730 group:PREF_GROUP_GENERAL];
735 + (BOOL) accessInstanceVariablesDirectly {
739 - (id) valueForKey:(NSString *)key {
740 return [self cachedPreferencesWithDefaultsForGroup:key object:nil];
744 * @brief Set a dictionary of preferences for a group
746 * Note that while setPreferences:inGroup: adds the passed dictionary to the current one, this method replaces the dictionary entirely
748 * @param value An NSDictionary which reprsents an entire group of preferences (without defaults)
749 * @param key The group name
751 - (void) setValue:(id)value forKey:(NSString *)key {
752 NSRange prefixRange = [key rangeOfString:@"Group:" options:NSLiteralSearch | NSAnchoredSearch];
753 if(prefixRange.location == 0) {
754 key = [key substringFromIndex:prefixRange.length + 1];
756 prefixRange = [key rangeOfString:@"ByObject:" options:NSLiteralSearch | NSAnchoredSearch];
757 if(prefixRange.location == 0) {
758 NSAssert(NO, @"ByObject is not yet supported in AIPreferenceController KVC methods.");
759 // key = [key substringFromIndex:prefixRange.length + 1];
760 #warning XXX ByObject NOT REALLY SUPPORTED YET
764 NSMutableDictionary *prefDict = [self cachedPreferencesForGroup:key object:nil];
766 //Handy feature: This asserts for us that [value isKindOfClass:[NSDictionary class]].
767 [prefDict setDictionary:value];
769 [self updatePreferences:prefDict forKey:nil group:key object:nil];
772 //- (id) valueForKeyPath:(NSString *)keyPath
773 //We don't need this method. NSObject's version works by calling -valueForKey: successively, which works for us.
779 * "ByObject" (futar): by-object (objectXyz instead of xyz ivars)
781 * For example, General.MyKey would refer to the MyKey value of the General group, as would Group:General.MyKey
783 - (void) setValue:(id)value forKeyPath:(NSString *)keyPath {
784 //NSLog(@"AIPC setting value %@ for key path %@", value, keyPath);
785 unsigned periodIdx = [keyPath rangeOfString:@"." options:NSLiteralSearch].location;
786 NSString *key = [keyPath substringToIndex:periodIdx];
787 if(periodIdx == NSNotFound) {
788 [self setValue:value forKey:key];
790 NSRange prefixRange = [key rangeOfString:@"Group:" options:NSLiteralSearch | NSAnchoredSearch];
791 if(prefixRange.location == 0) {
792 key = [key substringFromIndex:prefixRange.length + 1];
794 prefixRange = [key rangeOfString:@"ByObject:" options:NSLiteralSearch | NSAnchoredSearch];
795 if(prefixRange.location == 0) {
796 NSAssert(NO, @"ByObject is not yet supported in AIPreferenceController KVC methods.");
797 // key = [key substringFromIndex:prefixRange.length + 1];
798 #warning XXX ByObject NOT REALLY SUPPORTED YET
801 keyPath = [keyPath substringFromIndex:periodIdx + 1];
803 //We need the key to do AIPC change notifications.
804 NSString *keyInGroup;
805 periodIdx = [keyPath rangeOfString:@"." options:NSLiteralSearch].location;
806 if (periodIdx == NSNotFound) {
807 keyInGroup = keyPath;
809 keyInGroup = [keyPath substringToIndex:periodIdx];
812 //NSLog(@"key path: %@; first key: %@; second key: %@", keyPath, key, keyInGroup);
814 NSMutableDictionary *prefDict = [self cachedPreferencesForGroup:key object:nil];
816 [self willChangeValueForKey:key];
817 [prefDict setValue:value forKeyPath:keyPath];
818 [self updatePreferences:prefDict forKey:keyInGroup group:key object:nil];
819 [self didChangeValueForKey:key];