* 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 <Adium/AIInterfaceControllerProtocol.h> #import "AIWebKitMessageViewController.h" #import "AIWebKitMessageViewPlugin.h" #import "ESWebKitMessageViewPreferences.h" #import <AIUtilities/AIDictionaryAdditions.h> #import <AIUtilities/AIBundleAdditions.h> #import "AIWebkitMessageViewStyle.h" #import <AIUtilities/AIStringAdditions.h> #define NEW_CONTENT_RETRY_DELAY 0.01 #define MESSAGE_STYLES_SUBFOLDER_OF_APP_SUPPORT @"Message Styles" @interface AIWebKitMessageViewPlugin () - (void) resetStylesForType:(AIWebkitStyleType)styleType; - (void) xtrasChanged:(NSNotification *)notification; - (void)clearHardcodedBuiltInStylePaths; - (void)performAdium14PreferenceUpdates; @implementation AIWebKitMessageViewPlugin [adium createResourcePathForName:MESSAGE_STYLES_SUBFOLDER_OF_APP_SUPPORT]; /* If this isn't done, Bad Things happen, so check it each launch (cheap!) in case the user reverted to a * pre-Adium 1.4 version. This check can be removed ~Adium 1.6. [self clearHardcodedBuiltInStylePaths]; [self performAdium14PreferenceUpdates]; [adium.preferenceController registerDefaults:[NSDictionary dictionaryNamed:WEBKIT_DEFAULT_PREFS forClass:[self class]] forGroup:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY]; [adium.preferenceController registerDefaults:[NSDictionary dictionaryNamed:WEBKIT_DEFAULT_PREFS forClass:[self class]] forGroup:PREF_GROUP_WEBKIT_GROUP_MESSAGE_DISPLAY]; preferences = [(ESWebKitMessageViewPreferences *)[ESWebKitMessageViewPreferences preferencePaneForPlugin:self] retain]; //Observe for installation of new styles [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(xtrasChanged:) name:AIXtrasDidChangeNotification [adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY]; [adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_WEBKIT_GROUP_MESSAGE_DISPLAY]; //Register ourself as a message view plugin [adium.interfaceController registerMessageDisplayPlugin:self]; [adium.preferenceController unregisterPreferenceObserver:self]; [adium.interfaceController unregisterMessageDisplayPlugin:self]; [adium.preferenceController removePreferencePane:preferences]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [styleDictionary release]; styleDictionary = nil; [preferences release]; preferences = nil; [currentRegularStyle release]; currentRegularStyle = nil; [currentGroupStyle release]; currentGroupStyle = nil; [lastStyleLoadDate release]; lastStyleLoadDate = nil; - (id <AIMessageDisplayController>)messageDisplayControllerForChat:(AIChat *)inChat return [AIWebKitMessageViewController messageDisplayControllerForChat:inChat withPlugin:self]; - (void)preferencesChangedForGroup:(NSString *)group object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime if([key isEqualToString:KEY_WEBKIT_STYLE]) { if([group isEqualToString:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY]) { [self resetStylesForType:AIWebkitRegularChat]; [self resetStylesForType:AIWebkitGroupChat]; if ([group isEqualToString:PREF_GROUP_WEBKIT_GROUP_MESSAGE_DISPLAY]) { useRegularForGroupChat = [[prefDict objectForKey:KEY_WEBKIT_USE_REGULAR_PREFERENCES] boolValue]; - (NSDictionary *)availableMessageStyles NSArray *stylesArray = [adium allResourcesForName:MESSAGE_STYLES_SUBFOLDER_OF_APP_SUPPORT withExtensions:@"AdiumMessageStyle"]; //Clear the current dictionary of styles and ready a new mutable dictionary styleDictionary = [[NSMutableDictionary alloc] init]; //Get all resource paths to search for (resourcePath in stylesArray) { if ((style = [NSBundle bundleWithPath:resourcePath])) { NSString *styleIdentifier = [style bundleIdentifier]; if (styleIdentifier && [styleIdentifier length]) { [styleDictionary setObject:style forKey:styleIdentifier]; NSAssert([styleDictionary count] > 0, @"No message styles available"); //Abort if we have no message styles return [NSDictionary dictionaryWithDictionary:styleDictionary]; //returning mutable private variables == nuh uh - (NSBundle *)defaultMessageStyleBundleBasedOnFailedIdentifier:(NSString *)identifier NSDictionary *styles = [self availableMessageStyles]; NSBundle *defaultMessageStyleBundle = nil; if (!defaultMessageStyleBundle) { defaultMessageStyleBundle = [styles objectForKey:KEY_WEBKIT_STYLE]; if (!defaultMessageStyleBundle) { defaultMessageStyleBundle = [[styles allValues] lastObject]; return defaultMessageStyleBundle; - (NSBundle *)messageStyleBundleWithIdentifier:(NSString *)identifier NSDictionary *styles = [self availableMessageStyles]; NSBundle *bundle = [styles objectForKey:identifier]; //If the style isn't available, use our default. Or, failing that, any available style bundle = [self defaultMessageStyleBundleBasedOnFailedIdentifier:identifier]; - (AIWebkitMessageViewStyle *) currentMessageStyleForChat:(AIChat *)chat NSString *loadFromGroup = nil; AIWebkitMessageViewStyle **thisStyle = nil; if (!chat.isGroupChat || useRegularForGroupChat) { if (!currentRegularStyle) { loadFromGroup = PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY; thisStyle = ¤tRegularStyle; } else if (chat.isGroupChat) { if (!currentGroupStyle) { loadFromGroup = PREF_GROUP_WEBKIT_GROUP_MESSAGE_DISPLAY; thisStyle = ¤tGroupStyle; if(loadFromGroup && thisStyle) { id<AIPreferenceController> prefs = adium.preferenceController; /* We use the path directly, if possible, to avoid a relatively expensive search through multiple folders */ *thisStyle = [AIWebkitMessageViewStyle messageViewStyleFromPath:[prefs preferenceForKey:KEY_CURRENT_WEBKIT_STYLE_PATH /* If the path isn't cached yet, load the style and then store the path */ *thisStyle = [AIWebkitMessageViewStyle messageViewStyleFromBundle: [self messageStyleBundleWithIdentifier:[prefs preferenceForKey:KEY_WEBKIT_STYLE [prefs setPreference:[[[*thisStyle bundle] bundlePath] stringByCollapsingBundlePath] forKey:KEY_CURRENT_WEBKIT_STYLE_PATH /* If the style failed to load, clear our preference to fall back to the default */ /* XXX An error message could potentially be displayed here */ [prefs setPreference:nil forKey:KEY_WEBKIT_STYLE group:loadFromGroup]; *thisStyle = [AIWebkitMessageViewStyle messageViewStyleFromBundle: [self messageStyleBundleWithIdentifier:[prefs preferenceForKey:KEY_WEBKIT_STYLE NSDictionary *fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:[[*thisStyle bundle] bundlePath] NSDate *modDate = [fileAttrs objectForKey:NSFileModificationDate]; if (lastStyleLoadDate && [modDate timeIntervalSinceDate:lastStyleLoadDate] > 0) { [currentGroupStyle reloadStyle]; [currentRegularStyle reloadStyle]; [lastStyleLoadDate release]; lastStyleLoadDate = [[NSDate date] retain]; [lastStyleLoadDate release]; return (thisStyle ? *thisStyle : nil); - (void) resetStylesForType:(AIWebkitStyleType)styleType [styleDictionary release]; styleDictionary = nil; case AIWebkitRegularChat: [currentRegularStyle release]; currentRegularStyle = nil; [adium.preferenceController setPreference:nil forKey:KEY_CURRENT_WEBKIT_STYLE_PATH group:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY]; [currentGroupStyle release]; currentGroupStyle = nil; [adium.preferenceController setPreference:nil forKey:KEY_CURRENT_WEBKIT_STYLE_PATH group:PREF_GROUP_WEBKIT_GROUP_MESSAGE_DISPLAY]; * @brief Rebuild our list of available styles when the installed xtras change - (void)xtrasChanged:(NSNotification *)notification if ([[notification object] caseInsensitiveCompare:@"AdiumMessageStyle"] == NSOrderedSame) { [self resetStylesForType:AIWebkitRegularChat]; [self resetStylesForType:AIWebkitGroupChat]; [preferences messageStyleXtrasDidChange]; * @brief The preference group this chat should use. - (NSString *)preferenceGroupForChat:(AIChat *)chat if (useRegularForGroupChat || !chat.isGroupChat) { return PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY; return PREF_GROUP_WEBKIT_GROUP_MESSAGE_DISPLAY; - (NSString *)styleSpecificKey:(NSString *)key forStyle:(NSString *)style return [NSString stringWithFormat:@"%@:%@", style, key]; * @brief Clears cached style bundle pathes if they are built-in but not bundle-relative; Adium 1.4b18 and prior made these. - (void)clearHardcodedBuiltInStylePaths if ([[adium.preferenceController preferenceForKey:KEY_CURRENT_WEBKIT_STYLE_PATH group:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY] rangeOfString:@".app"].location != NSNotFound) [self resetStylesForType:AIWebkitRegularChat]; if ([[adium.preferenceController preferenceForKey:KEY_CURRENT_WEBKIT_STYLE_PATH group:PREF_GROUP_WEBKIT_GROUP_MESSAGE_DISPLAY] rangeOfString:@".app"].location != NSNotFound) [self resetStylesForType:AIWebkitGroupChat]; - (void)performAdium14PreferenceUpdates if (![[adium.preferenceController preferenceForKey:@"Adium 1.4:Updated Preferences" group:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY] boolValue]) { NSDictionary *dict = [adium.preferenceController preferencesForGroup:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY]; NSMutableDictionary *newDict = [[dict mutableCopy] autorelease]; NSMutableSet *keysToRemove = [NSMutableSet set]; NSDictionary *conversionDict = [NSDictionary dictionaryWithObjectsAndKeys: /* complete style changes */ @"im.adium.Gone Dark.style", @"com.adiumx.eclipse.style", @"im.adium.Stockholm.style", @"com.adiumx.plastic.style", @"im.adium.minimal_mod.style", @"com.adiumx.minimal_2.0.style", @"im.adium.Renkoo.style", @"com.adiumx.renkooNaked.style", @"im.adium.minimal_mod.style", @"com.adiumx.minimal.style", /* bundle identifier changes */ @"im.adium.Gone Dark.style", @"com.adiumx.gonedark.style", @"im.adium.minimal_mod.style", @"com.adiumx.minimal_mod.style", @"im.adium.Mockie.style", @"com.adiumx.mockie.style", @"im.adium.Renkoo.style", @"com.adiumx.renkoo.style", @"im.adium.Smooth Operator.style", @"com.adiumx.smooth.operator.style", @"im.adium.Stockholm.style", @"com.adiumx.stockholm.style", @"im.adium.yMous.style", @"mathuaerknedam.yMous.style", /* Upgrade the style ID itself (that is, the style that Adium will be displaying) if it changed */ NSString *upgradedStyleID = [conversionDict objectForKey:[dict objectForKey:KEY_WEBKIT_STYLE]]; [newDict setObject:upgradedStyleID forKey:KEY_WEBKIT_STYLE]; /* Now update style-specific preferences, whose keys are prefixed with style names, as needed */ for (NSString *key in [dict keyEnumerator]) { /* For each changed bundle, check each key */ for (NSString *oldBundleID in [conversionDict keyEnumerator]) { if ([key hasPrefix:oldBundleID]) { NSString *newBundleID = [conversionDict objectForKey:oldBundleID]; NSString *newKey = [newBundleID stringByAppendingString:[key substringFromIndex:oldBundleID.length]]; /* Store with the new bundle ID in the key */ [newDict setObject:[dict objectForKey:key] /* Remove the obsolete preference; we'll want it not in the newDict but also need to manually * remove it when we're done, since AIPreferenceController's setPreferences:inGroup: is nondestructive. */ [newDict removeObjectForKey:key]; [keysToRemove addObject:key]; [adium.preferenceController setPreferences:newDict inGroup:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY]; for (NSString *key in keysToRemove) { [adium.preferenceController setPreference:nil group:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY]; [adium.preferenceController setPreference:[NSNumber numberWithBool:YES] forKey:@"Adium 1.4:Updated Preferences" group:PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY];