Plugins/WebKit Message View/ESWebKitMessageViewPreferences.m
author Frank Dowsett <wixardy@adium.im>
Tue Mar 16 22:14:34 2010 -0400 (2010-03-16)
changeset 2911 f211430a455e
parent 2910 1bcb24fe3222
permissions -rw-r--r--
Enable the header control when there's a topic or a header and "Use regular chat style settings" is enabled. Fixes #13791
(transplanted from d7fe2ee7d9b1a0d3e83ed73053e8866c9cc958a2)
     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 useRegularPreferences = [[adium.preferenceController preferenceForKey:KEY_WEBKIT_USE_REGULAR_PREFERENCES
   341 																		 group:PREF_GROUP_WEBKIT_GROUP_MESSAGE_DISPLAY] boolValue];
   342 	BOOL anyControlsEnabled = (self.currentTab == AIWebkitRegularChat || !useRegularPreferences);
   343 	
   344 	// General controls with no other qualifiers.
   345 	[popUp_styles setEnabled:anyControlsEnabled];
   346 	[fontPreviewField_currentFont setEnabled:anyControlsEnabled];
   347 	[checkBox_showMessageFonts setEnabled:anyControlsEnabled];
   348 	[checkBox_showMessageColors setEnabled:anyControlsEnabled];
   349 	[button_setFont setEnabled:anyControlsEnabled];
   350 	[button_defaultFont setEnabled:anyControlsEnabled];
   351 	
   352 	//Only enable if there are multiple variant choices
   353 	[popUp_variants setEnabled:[popUp_variants numberOfItems] > 0 && anyControlsEnabled];
   354 	
   355 	//Disable the custom background controls if the style doesn't support them
   356 	AIWebkitMessageViewStyle *messageStyle = [previewController messageStyle];
   357 	BOOL	allowCustomBackground = [messageStyle allowsCustomBackground] && anyControlsEnabled;
   358 	[checkBox_useCustomBackground setEnabled:allowCustomBackground];
   359 	
   360 	allowCustomBackground = allowCustomBackground && checkBox_useCustomBackground.state;
   361 	
   362 	[colorWell_customBackgroundColor setEnabled:allowCustomBackground];
   363 	[popUp_backgroundImageType setEnabled:allowCustomBackground];
   364 	[imageView_backgroundImage setEnabled:allowCustomBackground];
   365 	
   366 	//Disable the header control if this style doesn't have a header or topic
   367 	if (self.currentTab == AIWebkitGroupChat)
   368 		[checkBox_showHeader setEnabled:[messageStyle hasTopic] && anyControlsEnabled];
   369 	else
   370 		[checkBox_showHeader setEnabled:[messageStyle hasHeader] || ([messageStyle hasTopic] && useRegularPreferences)];
   371 	
   372 	//Disable user icon toggling if the style doesn't support them
   373 	[checkBox_showUserIcons setEnabled:[messageStyle allowsUserIcons] && anyControlsEnabled];
   374 	
   375 	[checkBox_showMessageColors setEnabled:[messageStyle allowsColors] && anyControlsEnabled];
   376 }
   377 
   378 /*!
   379  * @brief Save changes to the font field
   380  */
   381 - (void)fontPreviewField:(JVFontPreviewField *)field didChangeToFont:(NSFont *)font
   382 {
   383 	[self _setDisplayFontFace:[font fontName] size:[NSNumber numberWithInteger:[font pointSize]]];
   384 }
   385 
   386 - (IBAction)resetDisplayFontToDefault:(id)sender
   387 {
   388 	[self _setDisplayFontFace:nil size:0];
   389 }
   390 
   391 /*!
   392  * @brief Set the display font of the active style.
   393  *
   394  * @param face New font face, nil to remove custom font
   395  * @param size New font size, nil to remove custom size
   396  */
   397 - (void)_setDisplayFontFace:(NSString *)face size:(NSNumber *)size
   398 {
   399 	NSString *activeStyle = [adium.preferenceController preferenceForKey:KEY_WEBKIT_STYLE
   400 																	group:self.preferenceGroupForCurrentTab];
   401 	
   402 	[adium.preferenceController setPreference:face
   403 										 forKey:[plugin styleSpecificKey:@"FontFamily" forStyle:activeStyle]
   404 										  group:self.preferenceGroupForCurrentTab];
   405 	[adium.preferenceController setPreference:size
   406 										 forKey:[plugin styleSpecificKey:@"FontSize" forStyle:activeStyle]
   407 										  group:self.preferenceGroupForCurrentTab];
   408 	
   409 }
   410 
   411 /*!
   412  * @brief Save changes to the background image
   413  */
   414 - (void)imageViewWithImagePicker:(AIImageViewWithImagePicker *)picker didChangeToImage:(NSImage *)image
   415 {
   416 	[self _setBackgroundImage:image];
   417 }
   418 
   419 /*!
   420  * @brief Remove the background image
   421  */
   422 - (void)deleteInImageViewWithImagePicker:(AIImageViewWithImagePicker *)picker
   423 {
   424 	[self _setBackgroundImage:nil];
   425 }
   426 
   427 /*!
   428  * @brief Set the background image of the active style.
   429  *
   430  * @param image New background image, nil to remove background image
   431  */
   432 - (void)_setBackgroundImage:(NSImage *)image
   433 {
   434 	NSString	*style = [[popUp_styles selectedItem] representedObject];
   435 
   436 	/* Save the new image.  We store the images in a separate preference group since they may get big.
   437 	 * This will let loading other groups not be affected by its presence.
   438 	 */
   439 	[adium.preferenceController setPreference:[image PNGRepresentation]
   440 										 forKey:[plugin styleSpecificKey:@"Background" forStyle:style]
   441 										  group:PREF_GROUP_WEBKIT_BACKGROUND_IMAGES];
   442 }
   443 
   444 /*!
   445  * @brief Builds and returns a menu of available styles
   446  */
   447 - (NSMenu *)_stylesMenu
   448 {
   449 	NSMenu			*menu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@""];
   450 	NSMutableArray	*menuItemArray = [NSMutableArray array];
   451 	NSArray			*availableStyles = [[plugin availableMessageStyles] allValues];
   452 	NSMenuItem		*menuItem;
   453 	
   454 	for (NSBundle *style in availableStyles) {
   455 		menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[style name]
   456 																		target:nil
   457 																		action:nil
   458 																 keyEquivalent:@""];
   459 		[menuItem setRepresentedObject:[style bundleIdentifier]];
   460 		[menuItemArray addObject:menuItem];
   461 		[menuItem release];
   462 	}
   463 	
   464 	[menuItemArray sortUsingSelector:@selector(titleCompare:)];
   465 	
   466 	for (menuItem in menuItemArray) {
   467 		[menu addItem:menuItem];
   468 	}
   469 	
   470 	return [menu autorelease];
   471 }
   472 
   473 /*! 
   474  * @brief Build & return a menu of variants for the passed style
   475  */
   476 - (NSMenu *)_variantsMenu
   477 {
   478 	NSMenu			*menu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@""];
   479 
   480 	//Add a menu item for each variant
   481 	for (NSString *variant in previewController.messageStyle.availableVariants) {
   482 		[menu addItemWithTitle:variant
   483 						target:nil
   484 						action:nil
   485 				 keyEquivalent:@""
   486 			 representedObject:variant];
   487 	}
   488 
   489 	return [menu autorelease];
   490 }
   491 
   492 /*!
   493  * @brief Build & return a menu of choices for background display
   494  */
   495 - (NSMenu *)_backgroundImageTypeMenu
   496 {
   497 	NSMenu	*menu = [[NSMenu allocWithZone:[NSMenu menuZone]] init];	
   498 
   499 	[self _addBackgroundImageTypeChoice:BackgroundNormal toMenu:menu withTitle:AILocalizedString(@"Normal","Background image display preference: The image will be displayed normally")];
   500 	[self _addBackgroundImageTypeChoice:BackgroundCenter toMenu:menu withTitle:AILocalizedString(@"Centered","Background image display preference: The image will be centered in the window")];
   501 	[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")];
   502 	[self _addBackgroundImageTypeChoice:BackgroundTileCenter toMenu:menu withTitle:AILocalizedString(@"Tiled (Centered)","Background image display preference: The image will be tiled and centered in the window")];
   503 	[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")];
   504 			
   505 	return [menu autorelease];
   506 }
   507 - (void)_addBackgroundImageTypeChoice:(NSInteger)tag toMenu:(NSMenu *)menu withTitle:(NSString *)title
   508 {
   509 	NSMenuItem	*menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:title
   510 																				 action:nil
   511 																		  keyEquivalent:@""];
   512 	[menuItem setTag:tag];
   513 	[menu addItem:menuItem];
   514 	[menuItem release];
   515 }
   516 
   517 
   518 //Chat Preview ---------------------------------------------------------------------------------------------------------
   519 #pragma mark Chat Preview
   520 /*!
   521  * @brief Configure our chat preview
   522  */
   523 - (void)_configureChatPreview
   524 {
   525 	NSDictionary	*previewDict;
   526 	NSString		*previewFilePath;
   527 	NSString		*previewPath;
   528 	AIChat			*previewChat;
   529 
   530 	//Create our fake chat and message controller for the live preview
   531 	previewFilePath = [[NSBundle bundleForClass:[self class]] pathForResource:WEBKIT_PREVIEW_CONVERSATION_FILE ofType:@"plist"];
   532 	previewDict = [[NSDictionary alloc] initWithContentsOfFile:previewFilePath];
   533 	previewPath = [previewFilePath stringByDeletingLastPathComponent];
   534 	
   535 	NSDictionary *listObjects;
   536 	previewChat = [self previewChatWithDictionary:previewDict fromPath:previewPath listObjects:&listObjects];
   537 	previewController = [[AIWebKitPreviewMessageViewController messageDisplayControllerForChat:previewChat
   538 																					withPlugin:plugin] retain];
   539 
   540 	//Enable live refreshing of our preview
   541 	[previewController setShouldReflectPreferenceChanges:YES];	
   542 	[previewController setPreferencesChangedDelegate:self];
   543 	
   544 	//Add fake users and content to our chat
   545 	[self _fillContentOfChat:previewChat withDictionary:previewDict fromPath:previewPath listObjects:listObjects];
   546 	[previewDict release];
   547 	
   548 	//Place the preview chat in our view
   549 	preview = [[previewController messageView] retain];
   550 	[preview setFrame:[view_previewLocation frame]];
   551 	//Will be released in viewWillClose
   552 	[view_previewLocation retain];
   553 	[[view_previewLocation superview] replaceSubview:view_previewLocation with:preview];
   554 
   555 	//Disable drag and drop onto the preview chat - Jeff doesn't need your porn :)
   556 	if ([preview respondsToSelector:@selector(setAllowsDragAndDrop:)]) {
   557 		[(ESWebView *)preview setAllowsDragAndDrop:NO];
   558 	}
   559 
   560 	//Disable forwarding of events so the preferences responder chain works properly
   561 	if ([preview respondsToSelector:@selector(setShouldForwardEvents:)]) {
   562 		[(ESWebView *)preview setShouldForwardEvents:NO];		
   563 	}	
   564 }
   565 
   566 - (AIChat *)previewChatWithDictionary:(NSDictionary *)previewDict fromPath:(NSString *)previewPath listObjects:(NSDictionary **)outListObjects
   567 {
   568 	AIPreviewChat *previewChat = [AIPreviewChat previewChat];
   569 	[previewChat setDisplayName:AILocalizedString(@"Sample Conversation", "Title for the sample conversation")];
   570 
   571 	//Process and create all participants
   572 	*outListObjects = [self _addParticipants:[previewDict objectForKey:@"Participants"]
   573 									  toChat:previewChat fromPath:previewPath];
   574 
   575 	//Setup the chat, and its source/destination
   576 	[self _applySettings:[previewDict objectForKey:@"Chat"]
   577 				  toChat:previewChat withParticipants:*outListObjects];
   578 	
   579 	return previewChat;
   580 }
   581 
   582 /*!
   583  * @brief Fill the content of the specified chat using content archived in the dictionary
   584  */
   585 - (void)_fillContentOfChat:(AIChat *)inChat withDictionary:(NSDictionary *)previewDict fromPath:(NSString *)previewPath listObjects:(NSDictionary *)listObjects
   586 {
   587 	//Add the archived chat content
   588 	[self _addContent:[previewDict objectForKey:@"Preview Messages"]
   589 			   toChat:inChat withParticipants:listObjects];
   590 }
   591 
   592 /*!
   593  * @brief Add participants
   594  */
   595 - (NSMutableDictionary *)_addParticipants:(NSDictionary *)participants toChat:(AIChat *)inChat fromPath:(NSString *)previewPath
   596 {
   597 	NSMutableDictionary	*listObjectDict = [NSMutableDictionary dictionary];
   598 	AIService			*aimService = [adium.accountController firstServiceWithServiceID:@"AIM"];
   599 	
   600 	for (NSDictionary *participant in participants) {
   601 		NSString		*UID, *alias, *userIconName;
   602 		AIListContact	*listContact;
   603 		
   604 		//Create object
   605 		UID = [participant objectForKey:@"UID"];
   606 		listContact = [[AIListContact alloc] initWithUID:UID service:aimService];
   607 		
   608 		//Display name
   609 		if ((alias = [participant objectForKey:@"Display Name"])) {
   610 			[[NSNotificationCenter defaultCenter] postNotificationName:Contact_ApplyDisplayName
   611 													  object:listContact
   612 													userInfo:[NSDictionary dictionaryWithObject:alias forKey:@"Alias"]];
   613 		}
   614 		
   615 		//User icon
   616 		if ((userIconName = [participant objectForKey:@"UserIcon Name"])) {
   617 			[listContact setValue:[previewPath stringByAppendingPathComponent:userIconName]
   618 								  forProperty:@"UserIconPath"
   619 								  notify:YES];
   620 		}
   621 		
   622 		[listObjectDict setObject:listContact forKey:UID];
   623 		[listContact release];
   624 	}
   625 	
   626 	return listObjectDict;
   627 }
   628 
   629 /*!
   630  * @brief Chat settings
   631  */
   632 - (void)_applySettings:(NSDictionary *)chatDict toChat:(AIPreviewChat *)inChat withParticipants:(NSDictionary *)participants
   633 {
   634 	NSString			*dateOpened, *type, *name, *UID;
   635 	
   636 	//Date opened
   637 	if ((dateOpened = [chatDict objectForKey:@"Date Opened"])) {
   638 		[inChat setDateOpened:[NSDate dateWithNaturalLanguageString:dateOpened]];
   639 	}
   640 	
   641 	//Source/Destination
   642 	type = [chatDict objectForKey:@"Type"];
   643 	if ([type isEqualToString:@"IM"]) {
   644 		if ((UID = [chatDict objectForKey:@"Destination UID"])) {
   645 			[inChat addParticipatingListObject:[participants objectForKey:UID] notify:YES];
   646 		}
   647 		if ((UID = [chatDict objectForKey:@"Source UID"])) {
   648 			[inChat setAccount:(AIAccount *)[participants objectForKey:UID]];
   649 		}
   650 	} else {
   651 		if ((name = [chatDict objectForKey:@"Name"])) {
   652 			[inChat setName:name];
   653 		}
   654 	}
   655 	
   656 	//We don't want the interface controller to try to open this fake chat
   657 	[inChat setIsOpen:YES];
   658 }
   659 
   660 /*!
   661  * @brief Chat content
   662  */
   663 - (void)_addContent:(NSArray *)chatArray toChat:(AIChat *)inChat withParticipants:(NSDictionary *)participants
   664 {
   665 	NSDictionary		*messageDict;
   666 	
   667 	for (messageDict in chatArray) {
   668 		AIContentObject		*content = nil;
   669 		AIListObject		*source;
   670 		NSString			*from, *msgType;
   671 		NSAttributedString  *message;
   672 		
   673 		msgType = [messageDict objectForKey:@"Type"];
   674 		from = [messageDict objectForKey:@"From"];
   675 
   676 		source = (from ? [participants objectForKey:from] : nil);
   677 
   678 		if ([msgType isEqualToString:CONTENT_MESSAGE_TYPE]) {
   679 			//Create message content object
   680 			AIListObject		*dest;
   681 			NSString			*to;
   682 			BOOL				outgoing;
   683 
   684 			message = [AIHTMLDecoder decodeHTML:[messageDict objectForKey:@"Message"]];
   685 			to = [messageDict objectForKey:@"To"];
   686 			outgoing = [[messageDict objectForKey:@"Outgoing"] boolValue];
   687 
   688 			//The other person is always the one we're chatting with right now
   689 			dest = [participants objectForKey:to];
   690 			content = [AIPreviewContentMessage messageInChat:inChat
   691 												  withSource:source
   692 												 destination:dest
   693 														date:[NSDate dateWithNaturalLanguageString:[messageDict objectForKey:@"Date"]]
   694 													 message:message
   695 												   autoreply:[[messageDict objectForKey:@"Autoreply"] boolValue]];
   696 
   697 			//AIContentMessage won't know whether the message is outgoing unless we tell it since neither our source
   698 			//nor our destination are AIAccount objects.
   699 			[(AIPreviewContentMessage *)content setIsOutgoing:outgoing];
   700 
   701 		} else if ([msgType isEqualToString:CONTENT_STATUS_TYPE]) {
   702 			//Create status content object
   703 			NSString			*statusMessageType;
   704 			
   705 			message = [AIHTMLDecoder decodeHTML:[messageDict objectForKey:@"Message"]];
   706 			statusMessageType = [messageDict objectForKey:@"Status Message Type"];
   707 			
   708 			//Create our content object
   709 			content = [AIContentEvent eventInChat:inChat
   710 									   withSource:source
   711 									  destination:nil
   712 											 date:[NSDate dateWithNaturalLanguageString:[messageDict objectForKey:@"Date"]]
   713 										  message:message
   714 										 withType:statusMessageType];
   715 		}
   716 
   717 		if (content) {			
   718 			[content setTrackContent:NO];
   719 			[content setPostProcessContent:NO];
   720 			[content setDisplayContentImmediately:NO];
   721 			
   722 			[adium.contentController displayContentObject:content
   723 										usingContentFilters:YES
   724 												immediately:YES];
   725 		}
   726 	}
   727 
   728 	//We finished adding untracked content
   729 	[[NSNotificationCenter defaultCenter] postNotificationName:Content_ChatDidFinishAddingUntrackedContent
   730 											  object:inChat];
   731 }
   732 
   733 @end