Plugins/WebKit Message View/ESWebKitMessageViewPreferences.m
author Frank Dowsett <wixardy@adium.im>
Tue Mar 16 06:35:39 2010 -0400 (2010-03-16)
changeset 3157 3629989a40f9
parent 1642 95ded294145e
child 3158 d7fe2ee7d9b1
permissions -rw-r--r--
Don't enable the header control if there isn't a header or topic for regular or group chats respectively. Fixes #13791
     1 /* 
     2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
     3  * with this source distribution.
     4  * 
     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.
     8  * 
     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.
    12  * 
    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.
    15  */
    16 
    17 #import "ESWebKitMessageViewPreferences.h"
    18 #import "AIWebKitMessageViewPlugin.h"
    19 #import "AIWebkitMessageViewStyle.h"
    20 #import "AIWebKitPreviewMessageViewController.h"
    21 #import "AIPreviewChat.h"
    22 #import "ESWebView.h"
    23 #import <Adium/AIAccountControllerProtocol.h>
    24 #import <Adium/AIContactControllerProtocol.h>
    25 #import <Adium/AIContentControllerProtocol.h>
    26 #import <Adium/AIInterfaceControllerProtocol.h>
    27 #import <Adium/AIAccount.h>
    28 #import <Adium/AIChat.h>
    29 #import <Adium/AIContentMessage.h>
    30 #import <Adium/AIContentObject.h>
    31 #import <Adium/AIContentEvent.h>
    32 #import <Adium/AIListContact.h>
    33 #import <Adium/AIHTMLDecoder.h>
    34 #import <Adium/AIService.h>
    35 #import <Adium/JVFontPreviewField.h>
    36 #import <AIUtilities/AIAttributedStringAdditions.h>
    37 #import <AIUtilities/AIColorAdditions.h>
    38 #import <AIUtilities/AIFontAdditions.h>
    39 #import <AIUtilities/AIMenuAdditions.h>
    40 #import <AIUtilities/AIPopUpButtonAdditions.h>
    41 #import <AIUtilities/AIBundleAdditions.h>
    42 #import <AIUtilities/AIDateFormatterAdditions.h>
    43 #import <AIUtilities/AIImageAdditions.h>
    44 #import <AIUtilities/AIImageViewWithImagePicker.h>
    45 
    46 #import "AIPreviewContentMessage.h"
    47 
    48 #define WEBKIT_PREVIEW_CONVERSATION_FILE	@"Preview"
    49 #define	PREF_GROUP_DISPLAYFORMAT			@"Display Format"  //To watch when the contact name display format changes
    50 
    51 @interface ESWebKitMessageViewPreferences ()
    52 - (void)configurePreferencesForTab;
    53 - (void)_setBackgroundImage:(NSImage *)image;
    54 - (NSMenu *)_stylesMenu;
    55 - (NSMenu *)_variantsMenu;
    56 - (NSMenu *)_backgroundImageTypeMenu;
    57 - (void)_addBackgroundImageTypeChoice:(NSInteger)tag toMenu:(NSMenu *)menu withTitle:(NSString *)title;
    58 - (void)_configureChatPreview;
    59 - (AIChat *)previewChatWithDictionary:(NSDictionary *)previewDict fromPath:(NSString *)previewPath listObjects:(NSDictionary **)outListObjects;
    60 - (void)_fillContentOfChat:(AIChat *)inChat withDictionary:(NSDictionary *)previewDict fromPath:(NSString *)previewPath listObjects:(NSDictionary *)listObjects;
    61 - (NSMutableDictionary *)_addParticipants:(NSDictionary *)participants toChat:(AIChat *)inChat fromPath:(NSString *)previewPath;
    62 - (void)_applySettings:(NSDictionary *)chatDict toChat:(AIPreviewChat *)inChat withParticipants:(NSDictionary *)participants;
    63 - (void)_addContent:(NSArray *)chatArray toChat:(AIChat *)inChat withParticipants:(NSDictionary *)participants;
    64 - (void)_setDisplayFontFace:(NSString *)face size:(NSNumber *)size;
    65 @end
    66 
    67 @class AIPreviewChat;
    68 
    69 @implementation ESWebKitMessageViewPreferences
    70 
    71 - (NSString *)paneIdentifier
    72 {
    73 	return @"Messages";
    74 }
    75 - (NSString *)paneName{
    76 	return AILocalizedString(@"Messages", "Title of the messages preferences");
    77 }
    78 - (NSString *)nibName{
    79     return @"WebKitPreferencesView";
    80 }
    81 - (NSImage *)paneIcon
    82 {
    83 	return [NSImage imageNamed:@"pref-messages"];
    84 }
    85 
    86 /*!
    87  * @brief Configure the preference view
    88  */
    89 - (void)viewDidLoad
    90 {
    91 	viewIsOpen = YES;
    92 	previewListObjectsDict = nil;
    93 
    94 	//Configure our menus
    95 	[popUp_backgroundImageType setMenu:[self _backgroundImageTypeMenu]];
    96 	[popUp_styles setMenu:[self _stylesMenu]];
    97 	
    98 	//Other controls
    99 	[fontPreviewField_currentFont setShowFontFace:YES];
   100 	[fontPreviewField_currentFont setShowPointSize:YES];
   101 
   102 	//We want to be able to obtain bigger images than the image picker will feed us
   103 	[imageView_backgroundImage setUsePictureTaker:NO];
   104 		
   105 	//Configure the chat preview
   106 	[self _configureChatPreview];
   107 	
   108 	[tabView_messageType selectTabViewItem:tabViewItem_regularChat];
   109 
   110 	[self configurePreferencesForTab];
   111 }
   112 
   113 /*!
   114  * @brief Close the preference view
   115  */
   116 - (void)viewWillClose
   117 {
   118 	//Hide the alpha component
   119 	[[NSColorPanel sharedColorPanel] setShowsAlpha:NO];
   120 
   121 	[[NSNotificationCenter defaultCenter] removeObserver:self];
   122 	[previewListObjectsDict release]; previewListObjectsDict = nil;
   123 
   124 	[previewController release]; previewController = nil;
   125 	[view_previewLocation setFrame:[preview frame]];
   126 	[[preview superview] replaceSubview:preview with:view_previewLocation];	
   127 	[preview release]; preview = nil;
   128 	//Matches the retain performed in -[ESWebKitMessageViewPreferences _configureChatPreview]
   129 	[view_previewLocation release];
   130 
   131 	viewIsOpen = NO;
   132 }
   133 
   134 - (void)messageStyleXtrasDidChange
   135 {
   136 	if (viewIsOpen) {
   137 		NSDictionary *prefDict = [adium.preferenceController preferencesForGroup:self.preferenceGroupForCurrentTab];
   138 		
   139 		[popUp_styles setMenu:[self _stylesMenu]];
   140 		[popUp_styles selectItemWithRepresentedObject:[prefDict objectForKey:KEY_WEBKIT_STYLE]];
   141 	}
   142 }
   143 
   144 //Preferences ----------------------------------------------------------------------------------------------------------
   145 #pragma mark Preferences
   146 - (AIWebkitStyleType)currentTab
   147 {
   148 	if (tabView_messageType.selectedTabViewItem == tabViewItem_regularChat) {
   149 		return AIWebkitRegularChat;
   150 	} else {
   151 		return AIWebkitGroupChat;
   152 	}
   153 }
   154 
   155 - (NSString *)preferenceGroupForCurrentTab
   156 {
   157 	NSString *prefGroup = nil;
   158 	
   159 	switch(self.currentTab) {
   160 		case AIWebkitRegularChat:
   161 			prefGroup = PREF_GROUP_WEBKIT_REGULAR_MESSAGE_DISPLAY;
   162 			break;
   163 			
   164 		case AIWebkitGroupChat:
   165 			prefGroup = PREF_GROUP_WEBKIT_GROUP_MESSAGE_DISPLAY;
   166 			break;		
   167 	}
   168 	
   169 	return prefGroup;
   170 }
   171 
   172 - (void)configurePreferencesForTab
   173 {
   174 	//Configure our controls to represent the global preferences
   175 
   176 	NSDictionary *prefDict = [adium.preferenceController preferencesForGroup:self.preferenceGroupForCurrentTab];
   177 	
   178 	[checkBox_showUserIcons setState:([[previewController messageStyle] allowsUserIcons] ?
   179 									  [[prefDict objectForKey:KEY_WEBKIT_SHOW_USER_ICONS] boolValue] :
   180 									  NSOffState)];
   181 	[checkBox_showHeader setState:[[prefDict objectForKey:KEY_WEBKIT_SHOW_HEADER] boolValue]];
   182 	[checkBox_showMessageColors setState:([[previewController messageStyle] allowsColors] ?
   183 										  [[prefDict objectForKey:KEY_WEBKIT_SHOW_MESSAGE_COLORS] boolValue] :
   184 										  NSOffState)];
   185 	[checkBox_showMessageFonts setState:[[prefDict objectForKey:KEY_WEBKIT_SHOW_MESSAGE_FONTS] boolValue]];
   186 	
   187 	[checkBox_useRegularChatForGroup setState:[[adium.preferenceController preferenceForKey:KEY_WEBKIT_USE_REGULAR_PREFERENCES
   188 																					  group:self.preferenceGroupForCurrentTab] boolValue]];
   189 	
   190 	//Allow the alpha component to be set for our background color
   191 	[[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
   192 	
   193 	[previewController setIsGroupChat:(self.currentTab == AIWebkitGroupChat)];
   194 	
   195 	// The preview controller will send us a preferences changed message also.
   196 	[previewController preferencesChangedForGroup:self.preferenceGroupForCurrentTab
   197 											  key:nil
   198 										   object:nil
   199 								   preferenceDict:[adium.preferenceController preferencesForGroup:self.preferenceGroupForCurrentTab]
   200 										firstTime:NO];
   201 }
   202 
   203 - (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
   204 {
   205 	[self configurePreferencesForTab];
   206 }
   207 
   208 /*!
   209  * @brief Update our preference view to reflect changed preferences
   210  */
   211 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object
   212 					preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
   213 {
   214 	if (!viewIsOpen) return;
   215 
   216 	if ([group isEqualToString:self.preferenceGroupForCurrentTab]) {
   217 		NSString	*style;
   218 		NSString	*variant;
   219 
   220 		//Ensure our style/variant menus are showing the correct selection
   221 		style = [prefDict objectForKey:KEY_WEBKIT_STYLE];
   222 		if (!style || ![popUp_styles selectItemWithRepresentedObject:style]) {
   223 			style = [[plugin messageStyleBundleWithIdentifier:style] bundleIdentifier];
   224 			[popUp_styles selectItemWithRepresentedObject:style];
   225 		}
   226 
   227 		//When the active style changes, rebuild our variant menu for the new style
   228 		if (!key || [key isEqualToString:KEY_WEBKIT_STYLE]) {
   229 			[popUp_variants setMenu:[self _variantsMenu]];
   230 		}
   231 
   232 		variant = [prefDict objectForKey:[plugin styleSpecificKey:@"Variant" forStyle:style]];
   233 		if (!variant || ![popUp_variants selectItemWithRepresentedObject:variant]) {
   234 			variant = [AIWebkitMessageViewStyle defaultVariantForBundle:[plugin messageStyleBundleWithIdentifier:style]];
   235 			[popUp_variants selectItemWithRepresentedObject:variant];
   236 		}
   237 		
   238 		[popUp_variants synchronizeTitleAndSelectedItem];
   239 		
   240 		//Configure our style-specific controls to represent the current style
   241 		NSString	*fontFamily = [prefDict objectForKey:[plugin styleSpecificKey:@"FontFamily" forStyle:style]];
   242 		if (!fontFamily) fontFamily = [[plugin messageStyleBundleWithIdentifier:style] objectForInfoDictionaryKey:KEY_WEBKIT_DEFAULT_FONT_FAMILY];
   243 		if (!fontFamily) fontFamily = [[NSFont systemFontOfSize:0] familyName];
   244 		
   245 		NSNumber	*fontSize = [prefDict objectForKey:[plugin styleSpecificKey:@"FontSize" forStyle:style]];
   246 		if (!fontSize) fontSize = [[plugin messageStyleBundleWithIdentifier:style] objectForInfoDictionaryKey:KEY_WEBKIT_DEFAULT_FONT_SIZE];
   247 		if (!fontSize) fontSize = [NSNumber numberWithInteger:[[NSFont systemFontOfSize:0] pointSize]];
   248 
   249 		NSFont	*defaultFont = [NSFont cachedFontWithName:fontFamily size:[fontSize integerValue]];
   250 		[fontPreviewField_currentFont setFont:defaultFont];
   251 
   252 		//Style-specific background prefs
   253 		NSData	*backgroundImage = [adium.preferenceController preferenceForKey:[plugin styleSpecificKey:@"Background" forStyle:style]
   254 																		   group:PREF_GROUP_WEBKIT_BACKGROUND_IMAGES];
   255 		if (backgroundImage) {
   256 			[imageView_backgroundImage setImage:[[[NSImage alloc] initWithData:backgroundImage] autorelease]];
   257 		} else {
   258 			[imageView_backgroundImage setImage:nil];
   259 		}
   260 
   261 		NSColor	*backgroundColor = [[prefDict objectForKey:[plugin styleSpecificKey:@"BackgroundColor" forStyle:style]] representedColor];
   262 		[colorWell_customBackgroundColor setColor:(backgroundColor ? backgroundColor : [NSColor whiteColor])] ;
   263 
   264 		[checkBox_useCustomBackground setState:[[prefDict objectForKey:[plugin styleSpecificKey:@"UseCustomBackground" forStyle:style]] boolValue]];
   265 		[popUp_backgroundImageType selectItemWithTag:[[prefDict objectForKey:[plugin styleSpecificKey:@"BackgroundType" forStyle:style]] integerValue]];
   266 		
   267 		[self configureControlDimming];
   268 	}
   269 }
   270 
   271 /*!
   272  * @brief Save changed preferences
   273  */
   274 - (IBAction)changePreference:(id)sender
   275 {
   276 	if (viewIsOpen) {
   277 		if (sender == checkBox_showUserIcons) {
   278 			[adium.preferenceController setPreference:[NSNumber numberWithBool:[sender state]]
   279 												 forKey:KEY_WEBKIT_SHOW_USER_ICONS
   280 												  group:self.preferenceGroupForCurrentTab];
   281 			
   282 		} else if (sender == checkBox_showHeader) {
   283 			[adium.preferenceController setPreference:[NSNumber numberWithBool:[sender state]]
   284 												 forKey:KEY_WEBKIT_SHOW_HEADER
   285 												  group:self.preferenceGroupForCurrentTab];
   286 			
   287 		} else if (sender == checkBox_showMessageColors) {
   288 			[adium.preferenceController setPreference:[NSNumber numberWithBool:[sender state]]
   289 												 forKey:KEY_WEBKIT_SHOW_MESSAGE_COLORS
   290 												  group:self.preferenceGroupForCurrentTab];
   291 			
   292 		} else if (sender == checkBox_showMessageFonts) {
   293 			[adium.preferenceController setPreference:[NSNumber numberWithBool:[sender state]]
   294 												 forKey:KEY_WEBKIT_SHOW_MESSAGE_FONTS
   295 												  group:self.preferenceGroupForCurrentTab];
   296 		} else if (sender == checkBox_useCustomBackground) {
   297 			[adium.preferenceController setPreference:[NSNumber numberWithBool:[sender state]]
   298 												 forKey:[plugin styleSpecificKey:@"UseCustomBackground" 
   299 																		forStyle:[[popUp_styles selectedItem] representedObject]]
   300 												  group:self.preferenceGroupForCurrentTab];
   301 		} else if (sender == checkBox_useRegularChatForGroup) {
   302 			[adium.preferenceController setPreference:[NSNumber numberWithBool:[sender state]]
   303 												 forKey:KEY_WEBKIT_USE_REGULAR_PREFERENCES
   304 												  group:self.preferenceGroupForCurrentTab];		
   305 			
   306 			[self configurePreferencesForTab];
   307 		} else if (sender == colorWell_customBackgroundColor) {
   308 			[adium.preferenceController setPreference:[[colorWell_customBackgroundColor color] stringRepresentation]
   309 												 forKey:[plugin styleSpecificKey:@"BackgroundColor"
   310 																		forStyle:[[popUp_styles selectedItem] representedObject]]
   311 												  group:self.preferenceGroupForCurrentTab];
   312 			
   313 		} else if (sender == popUp_backgroundImageType) {
   314 			[adium.preferenceController setPreference:[NSNumber numberWithInteger:[[popUp_backgroundImageType selectedItem] tag]]
   315 												 forKey:[plugin styleSpecificKey:@"BackgroundType"
   316 																		forStyle:[[popUp_styles selectedItem] representedObject]]
   317 												  group:self.preferenceGroupForCurrentTab];	
   318 			
   319 		} else if (sender == popUp_styles) {
   320 			[adium.preferenceController setPreference:[[sender selectedItem] representedObject]
   321 												 forKey:KEY_WEBKIT_STYLE
   322 												  group:self.preferenceGroupForCurrentTab];
   323 			
   324 		} else if (sender == popUp_variants) {
   325 			NSString *activeStyle = [adium.preferenceController preferenceForKey:KEY_WEBKIT_STYLE
   326 																		   group:self.preferenceGroupForCurrentTab];
   327 			
   328 			[adium.preferenceController setPreference:[[sender selectedItem] representedObject]
   329 												 forKey:[plugin styleSpecificKey:@"Variant" forStyle:activeStyle]
   330 												  group:self.preferenceGroupForCurrentTab];
   331 		}
   332 		
   333 		[self configureControlDimming];
   334 	}
   335 }
   336 
   337 - (void)configureControlDimming
   338 {	
   339 	// Controls are enabled if we're the regular chat tab, or we're not using regular preferences.
   340 	BOOL anyControlsEnabled = (self.currentTab == AIWebkitRegularChat || ![[adium.preferenceController preferenceForKey:KEY_WEBKIT_USE_REGULAR_PREFERENCES
   341 																			group:self.preferenceGroupForCurrentTab] boolValue]);
   342 	
   343 	// General controls with no other qualifiers.
   344 	[popUp_styles setEnabled:anyControlsEnabled];
   345 	[fontPreviewField_currentFont setEnabled:anyControlsEnabled];
   346 	[checkBox_showMessageFonts setEnabled:anyControlsEnabled];
   347 	[checkBox_showMessageColors setEnabled:anyControlsEnabled];
   348 	[button_setFont setEnabled:anyControlsEnabled];
   349 	[button_defaultFont setEnabled:anyControlsEnabled];
   350 	
   351 	//Only enable if there are multiple variant choices
   352 	[popUp_variants setEnabled:[popUp_variants numberOfItems] > 0 && anyControlsEnabled];
   353 	
   354 	//Disable the custom background controls if the style doesn't support them
   355 	AIWebkitMessageViewStyle *messageStyle = [previewController messageStyle];
   356 	BOOL	allowCustomBackground = [messageStyle allowsCustomBackground] && anyControlsEnabled;
   357 	[checkBox_useCustomBackground setEnabled:allowCustomBackground];
   358 	
   359 	allowCustomBackground = allowCustomBackground && checkBox_useCustomBackground.state;
   360 	
   361 	[colorWell_customBackgroundColor setEnabled:allowCustomBackground];
   362 	[popUp_backgroundImageType setEnabled:allowCustomBackground];
   363 	[imageView_backgroundImage setEnabled:allowCustomBackground];
   364 	
   365 	//Disable the header control if this style doesn't have a header or topic
   366 	if (self.currentTab == AIWebkitGroupChat)
   367 		[checkBox_showHeader setEnabled:[messageStyle hasTopic] && anyControlsEnabled];
   368 	else
   369 		[checkBox_showHeader setEnabled:[messageStyle hasHeader] && anyControlsEnabled];
   370 	
   371 	//Disable user icon toggling if the style doesn't support them
   372 	[checkBox_showUserIcons setEnabled:[messageStyle allowsUserIcons] && anyControlsEnabled];
   373 	
   374 	[checkBox_showMessageColors setEnabled:[messageStyle allowsColors] && anyControlsEnabled];
   375 }
   376 
   377 /*!
   378  * @brief Save changes to the font field
   379  */
   380 - (void)fontPreviewField:(JVFontPreviewField *)field didChangeToFont:(NSFont *)font
   381 {
   382 	[self _setDisplayFontFace:[font fontName] size:[NSNumber numberWithInteger:[font pointSize]]];
   383 }
   384 
   385 - (IBAction)resetDisplayFontToDefault:(id)sender
   386 {
   387 	[self _setDisplayFontFace:nil size:0];
   388 }
   389 
   390 /*!
   391  * @brief Set the display font of the active style.
   392  *
   393  * @param face New font face, nil to remove custom font
   394  * @param size New font size, nil to remove custom size
   395  */
   396 - (void)_setDisplayFontFace:(NSString *)face size:(NSNumber *)size
   397 {
   398 	NSString *activeStyle = [adium.preferenceController preferenceForKey:KEY_WEBKIT_STYLE
   399 																	group:self.preferenceGroupForCurrentTab];
   400 	
   401 	[adium.preferenceController setPreference:face
   402 										 forKey:[plugin styleSpecificKey:@"FontFamily" forStyle:activeStyle]
   403 										  group:self.preferenceGroupForCurrentTab];
   404 	[adium.preferenceController setPreference:size
   405 										 forKey:[plugin styleSpecificKey:@"FontSize" forStyle:activeStyle]
   406 										  group:self.preferenceGroupForCurrentTab];
   407 	
   408 }
   409 
   410 /*!
   411  * @brief Save changes to the background image
   412  */
   413 - (void)imageViewWithImagePicker:(AIImageViewWithImagePicker *)picker didChangeToImage:(NSImage *)image
   414 {
   415 	[self _setBackgroundImage:image];
   416 }
   417 
   418 /*!
   419  * @brief Remove the background image
   420  */
   421 - (void)deleteInImageViewWithImagePicker:(AIImageViewWithImagePicker *)picker
   422 {
   423 	[self _setBackgroundImage:nil];
   424 }
   425 
   426 /*!
   427  * @brief Set the background image of the active style.
   428  *
   429  * @param image New background image, nil to remove background image
   430  */
   431 - (void)_setBackgroundImage:(NSImage *)image
   432 {
   433 	NSString	*style = [[popUp_styles selectedItem] representedObject];
   434 
   435 	/* Save the new image.  We store the images in a separate preference group since they may get big.
   436 	 * This will let loading other groups not be affected by its presence.
   437 	 */
   438 	[adium.preferenceController setPreference:[image PNGRepresentation]
   439 										 forKey:[plugin styleSpecificKey:@"Background" forStyle:style]
   440 										  group:PREF_GROUP_WEBKIT_BACKGROUND_IMAGES];
   441 }
   442 
   443 /*!
   444  * @brief Builds and returns a menu of available styles
   445  */
   446 - (NSMenu *)_stylesMenu
   447 {
   448 	NSMenu			*menu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@""];
   449 	NSMutableArray	*menuItemArray = [NSMutableArray array];
   450 	NSArray			*availableStyles = [[plugin availableMessageStyles] allValues];
   451 	NSMenuItem		*menuItem;
   452 	
   453 	for (NSBundle *style in availableStyles) {
   454 		menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[style name]
   455 																		target:nil
   456 																		action:nil
   457 																 keyEquivalent:@""];
   458 		[menuItem setRepresentedObject:[style bundleIdentifier]];
   459 		[menuItemArray addObject:menuItem];
   460 		[menuItem release];
   461 	}
   462 	
   463 	[menuItemArray sortUsingSelector:@selector(titleCompare:)];
   464 	
   465 	for (menuItem in menuItemArray) {
   466 		[menu addItem:menuItem];
   467 	}
   468 	
   469 	return [menu autorelease];
   470 }
   471 
   472 /*! 
   473  * @brief Build & return a menu of variants for the passed style
   474  */
   475 - (NSMenu *)_variantsMenu
   476 {
   477 	NSMenu			*menu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@""];
   478 
   479 	//Add a menu item for each variant
   480 	for (NSString *variant in previewController.messageStyle.availableVariants) {
   481 		[menu addItemWithTitle:variant
   482 						target:nil
   483 						action:nil
   484 				 keyEquivalent:@""
   485 			 representedObject:variant];
   486 	}
   487 
   488 	return [menu autorelease];
   489 }
   490 
   491 /*!
   492  * @brief Build & return a menu of choices for background display
   493  */
   494 - (NSMenu *)_backgroundImageTypeMenu
   495 {
   496 	NSMenu	*menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];	
   497 
   498 	[self _addBackgroundImageTypeChoice:BackgroundNormal toMenu:menu withTitle:AILocalizedString(@"Normal","Background image display preference: The image will be displayed normally")];
   499 	[self _addBackgroundImageTypeChoice:BackgroundCenter toMenu:menu withTitle:AILocalizedString(@"Centered","Background image display preference: The image will be centered in the window")];
   500 	[self _addBackgroundImageTypeChoice:BackgroundTile toMenu:menu withTitle:AILocalizedString(@"Tiled","Background image display preference: The image will be tiled (repeated) in the window to fill available space")];
   501 	[self _addBackgroundImageTypeChoice:BackgroundTileCenter toMenu:menu withTitle:AILocalizedString(@"Tiled (Centered)","Background image display preference: The image will be tiled and centered in the window")];
   502 	[self _addBackgroundImageTypeChoice:BackgroundScale toMenu:menu withTitle:AILocalizedString(@"Scaled", "Background image display preference: The image will be increased or decreased in size to fit the window")];
   503 			
   504 	return [menu autorelease];
   505 }
   506 - (void)_addBackgroundImageTypeChoice:(NSInteger)tag toMenu:(NSMenu *)menu withTitle:(NSString *)title
   507 {
   508 	NSMenuItem	*menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:title
   509 																				 action:nil
   510 																		  keyEquivalent:@""];
   511 	[menuItem setTag:tag];
   512 	[menu addItem:menuItem];
   513 	[menuItem release];
   514 }
   515 
   516 
   517 //Chat Preview ---------------------------------------------------------------------------------------------------------
   518 #pragma mark Chat Preview
   519 /*!
   520  * @brief Configure our chat preview
   521  */
   522 - (void)_configureChatPreview
   523 {
   524 	NSDictionary	*previewDict;
   525 	NSString		*previewFilePath;
   526 	NSString		*previewPath;
   527 	AIChat			*previewChat;
   528 
   529 	//Create our fake chat and message controller for the live preview
   530 	previewFilePath = [[NSBundle bundleForClass:[self class]] pathForResource:WEBKIT_PREVIEW_CONVERSATION_FILE ofType:@"plist"];
   531 	previewDict = [[NSDictionary alloc] initWithContentsOfFile:previewFilePath];
   532 	previewPath = [previewFilePath stringByDeletingLastPathComponent];
   533 	
   534 	NSDictionary *listObjects;
   535 	previewChat = [self previewChatWithDictionary:previewDict fromPath:previewPath listObjects:&listObjects];
   536 	previewController = [[AIWebKitPreviewMessageViewController messageDisplayControllerForChat:previewChat
   537 																					withPlugin:plugin] retain];
   538 
   539 	//Enable live refreshing of our preview
   540 	[previewController setShouldReflectPreferenceChanges:YES];	
   541 	[previewController setPreferencesChangedDelegate:self];
   542 	
   543 	//Add fake users and content to our chat
   544 	[self _fillContentOfChat:previewChat withDictionary:previewDict fromPath:previewPath listObjects:listObjects];
   545 	[previewDict release];
   546 	
   547 	//Place the preview chat in our view
   548 	preview = [[previewController messageView] retain];
   549 	[preview setFrame:[view_previewLocation frame]];
   550 	//Will be released in viewWillClose
   551 	[view_previewLocation retain];
   552 	[[view_previewLocation superview] replaceSubview:view_previewLocation with:preview];
   553 
   554 	//Disable drag and drop onto the preview chat - Jeff doesn't need your porn :)
   555 	if ([preview respondsToSelector:@selector(setAllowsDragAndDrop:)]) {
   556 		[(ESWebView *)preview setAllowsDragAndDrop:NO];
   557 	}
   558 
   559 	//Disable forwarding of events so the preferences responder chain works properly
   560 	if ([preview respondsToSelector:@selector(setShouldForwardEvents:)]) {
   561 		[(ESWebView *)preview setShouldForwardEvents:NO];		
   562 	}	
   563 }
   564 
   565 - (AIChat *)previewChatWithDictionary:(NSDictionary *)previewDict fromPath:(NSString *)previewPath listObjects:(NSDictionary **)outListObjects
   566 {
   567 	AIPreviewChat *previewChat = [AIPreviewChat previewChat];
   568 	[previewChat setDisplayName:AILocalizedString(@"Sample Conversation", "Title for the sample conversation")];
   569 
   570 	//Process and create all participants
   571 	*outListObjects = [self _addParticipants:[previewDict objectForKey:@"Participants"]
   572 									  toChat:previewChat fromPath:previewPath];
   573 
   574 	//Setup the chat, and its source/destination
   575 	[self _applySettings:[previewDict objectForKey:@"Chat"]
   576 				  toChat:previewChat withParticipants:*outListObjects];
   577 	
   578 	return previewChat;
   579 }
   580 
   581 /*!
   582  * @brief Fill the content of the specified chat using content archived in the dictionary
   583  */
   584 - (void)_fillContentOfChat:(AIChat *)inChat withDictionary:(NSDictionary *)previewDict fromPath:(NSString *)previewPath listObjects:(NSDictionary *)listObjects
   585 {
   586 	//Add the archived chat content
   587 	[self _addContent:[previewDict objectForKey:@"Preview Messages"]
   588 			   toChat:inChat withParticipants:listObjects];
   589 }
   590 
   591 /*!
   592  * @brief Add participants
   593  */
   594 - (NSMutableDictionary *)_addParticipants:(NSDictionary *)participants toChat:(AIChat *)inChat fromPath:(NSString *)previewPath
   595 {
   596 	NSMutableDictionary	*listObjectDict = [NSMutableDictionary dictionary];
   597 	AIService			*aimService = [adium.accountController firstServiceWithServiceID:@"AIM"];
   598 	
   599 	for (NSDictionary *participant in participants) {
   600 		NSString		*UID, *alias, *userIconName;
   601 		AIListContact	*listContact;
   602 		
   603 		//Create object
   604 		UID = [participant objectForKey:@"UID"];
   605 		listContact = [[AIListContact alloc] initWithUID:UID service:aimService];
   606 		
   607 		//Display name
   608 		if ((alias = [participant objectForKey:@"Display Name"])) {
   609 			[[NSNotificationCenter defaultCenter] postNotificationName:Contact_ApplyDisplayName
   610 													  object:listContact
   611 													userInfo:[NSDictionary dictionaryWithObject:alias forKey:@"Alias"]];
   612 		}
   613 		
   614 		//User icon
   615 		if ((userIconName = [participant objectForKey:@"UserIcon Name"])) {
   616 			[listContact setValue:[previewPath stringByAppendingPathComponent:userIconName]
   617 								  forProperty:@"UserIconPath"
   618 								  notify:YES];
   619 		}
   620 		
   621 		[listObjectDict setObject:listContact forKey:UID];
   622 		[listContact release];
   623 	}
   624 	
   625 	return listObjectDict;
   626 }
   627 
   628 /*!
   629  * @brief Chat settings
   630  */
   631 - (void)_applySettings:(NSDictionary *)chatDict toChat:(AIPreviewChat *)inChat withParticipants:(NSDictionary *)participants
   632 {
   633 	NSString			*dateOpened, *type, *name, *UID;
   634 	
   635 	//Date opened
   636 	if ((dateOpened = [chatDict objectForKey:@"Date Opened"])) {
   637 		[inChat setDateOpened:[NSDate dateWithNaturalLanguageString:dateOpened]];
   638 	}
   639 	
   640 	//Source/Destination
   641 	type = [chatDict objectForKey:@"Type"];
   642 	if ([type isEqualToString:@"IM"]) {
   643 		if ((UID = [chatDict objectForKey:@"Destination UID"])) {
   644 			[inChat addParticipatingListObject:[participants objectForKey:UID] notify:YES];
   645 		}
   646 		if ((UID = [chatDict objectForKey:@"Source UID"])) {
   647 			[inChat setAccount:(AIAccount *)[participants objectForKey:UID]];
   648 		}
   649 	} else {
   650 		if ((name = [chatDict objectForKey:@"Name"])) {
   651 			[inChat setName:name];
   652 		}
   653 	}
   654 	
   655 	//We don't want the interface controller to try to open this fake chat
   656 	[inChat setIsOpen:YES];
   657 }
   658 
   659 /*!
   660  * @brief Chat content
   661  */
   662 - (void)_addContent:(NSArray *)chatArray toChat:(AIChat *)inChat withParticipants:(NSDictionary *)participants
   663 {
   664 	NSDictionary		*messageDict;
   665 	
   666 	for (messageDict in chatArray) {
   667 		AIContentObject		*content = nil;
   668 		AIListObject		*source;
   669 		NSString			*from, *msgType;
   670 		NSAttributedString  *message;
   671 		
   672 		msgType = [messageDict objectForKey:@"Type"];
   673 		from = [messageDict objectForKey:@"From"];
   674 
   675 		source = (from ? [participants objectForKey:from] : nil);
   676 
   677 		if ([msgType isEqualToString:CONTENT_MESSAGE_TYPE]) {
   678 			//Create message content object
   679 			AIListObject		*dest;
   680 			NSString			*to;
   681 			BOOL				outgoing;
   682 
   683 			message = [AIHTMLDecoder decodeHTML:[messageDict objectForKey:@"Message"]];
   684 			to = [messageDict objectForKey:@"To"];
   685 			outgoing = [[messageDict objectForKey:@"Outgoing"] boolValue];
   686 
   687 			//The other person is always the one we're chatting with right now
   688 			dest = [participants objectForKey:to];
   689 			content = [AIPreviewContentMessage messageInChat:inChat
   690 												  withSource:source
   691 												 destination:dest
   692 														date:[NSDate dateWithNaturalLanguageString:[messageDict objectForKey:@"Date"]]
   693 													 message:message
   694 												   autoreply:[[messageDict objectForKey:@"Autoreply"] boolValue]];
   695 
   696 			//AIContentMessage won't know whether the message is outgoing unless we tell it since neither our source
   697 			//nor our destination are AIAccount objects.
   698 			[(AIPreviewContentMessage *)content setIsOutgoing:outgoing];
   699 
   700 		} else if ([msgType isEqualToString:CONTENT_STATUS_TYPE]) {
   701 			//Create status content object
   702 			NSString			*statusMessageType;
   703 			
   704 			message = [AIHTMLDecoder decodeHTML:[messageDict objectForKey:@"Message"]];
   705 			statusMessageType = [messageDict objectForKey:@"Status Message Type"];
   706 			
   707 			//Create our content object
   708 			content = [AIContentEvent eventInChat:inChat
   709 									   withSource:source
   710 									  destination:nil
   711 											 date:[NSDate dateWithNaturalLanguageString:[messageDict objectForKey:@"Date"]]
   712 										  message:message
   713 										 withType:statusMessageType];
   714 		}
   715 
   716 		if (content) {			
   717 			[content setTrackContent:NO];
   718 			[content setPostProcessContent:NO];
   719 			[content setDisplayContentImmediately:NO];
   720 			
   721 			[adium.contentController displayContentObject:content
   722 										usingContentFilters:YES
   723 												immediately:YES];
   724 		}
   725 	}
   726 
   727 	//We finished adding untracked content
   728 	[[NSNotificationCenter defaultCenter] postNotificationName:Content_ChatDidFinishAddingUntrackedContent
   729 											  object:inChat];
   730 }
   731 
   732 @end