Source/AIInterfaceController.m
author Zachary West <zacw@adium.im>
Fri Oct 30 21:28:39 2009 -0400 (2009-10-30)
changeset 2693 5a28417276f5
parent 2131 0846c7e2b617
child 2736 d25c420e8e66
permissions -rw-r--r--
Grab the "Authorization Requests" localized string from the right bundle. Refs #13275.
David@0
     1
/* 
David@0
     2
 * Adium is the legal property of its developers, whose names are listed in the copyright file included
David@0
     3
 * with this source distribution.
David@0
     4
 * 
David@0
     5
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
David@0
     6
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
David@0
     7
 * or (at your option) any later version.
David@0
     8
 * 
David@0
     9
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
David@0
    10
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
David@0
    11
 * Public License for more details.
David@0
    12
 * 
David@0
    13
 * You should have received a copy of the GNU General Public License along with this program; if not,
David@0
    14
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
David@0
    15
 */
David@0
    16
David@0
    17
// $Id$
David@0
    18
David@0
    19
#import "AIInterfaceController.h"
David@0
    20
David@0
    21
#import <Adium/AIAccountControllerProtocol.h>
David@0
    22
#import <Adium/AIContactControllerProtocol.h>
David@0
    23
#import <Adium/AIChatControllerProtocol.h>
David@0
    24
#import <Adium/AIContentControllerProtocol.h>
David@0
    25
#import <Adium/AIMenuControllerProtocol.h>
zacw@1260
    26
#import <Adium/AIAuthorizationRequestsWindowController.h>
David@0
    27
#import <AIUtilities/AIAttributedStringAdditions.h>
David@0
    28
#import <AIUtilities/AIColorAdditions.h>
David@0
    29
#import <AIUtilities/AIFontAdditions.h>
David@0
    30
#import <AIUtilities/AIImageDrawingAdditions.h>
David@0
    31
#import <AIUtilities/AIMenuAdditions.h>
David@0
    32
#import <AIUtilities/AIStringAdditions.h>
David@0
    33
#import <AIUtilities/AITooltipUtilities.h>
David@0
    34
#import <AIUtilities/AIWindowAdditions.h>
David@0
    35
#import <AIUtilities/AITextAttributes.h>
David@0
    36
#import <AIUtilities/AIWindowControllerAdditions.h>
David@0
    37
#import <Adium/AIChat.h>
David@0
    38
#import <Adium/AIListContact.h>
David@0
    39
#import <Adium/AIListGroup.h>
David@0
    40
#import <Adium/AIListObject.h>
David@0
    41
#import <Adium/AIMetaContact.h>
David@0
    42
#import <Adium/AIService.h>
David@0
    43
#import <Adium/AIServiceIcons.h>
David@0
    44
#import <Adium/AISortController.h>
David@0
    45
#import "AIMessageTabViewItem.h"
catfish@1894
    46
#import "KNShelfSplitview.h"
David@52
    47
#import <Adium/AIContactList.h>
David@0
    48
David@0
    49
#import "AIMessageViewController.h"
David@0
    50
David@0
    51
#define ERROR_MESSAGE_WINDOW_TITLE		AILocalizedString(@"Adium : Error","Error message window title")
David@0
    52
#define LABEL_ENTRY_SPACING				4.0
David@0
    53
#define DISPLAY_IMAGE_ON_RIGHT			NO
David@0
    54
David@0
    55
#define PREF_GROUP_FORMATTING			@"Formatting"
David@0
    56
#define KEY_FORMATTING_FONT				@"Default Font"
David@0
    57
David@0
    58
#define MESSAGES_WINDOW_MENU_TITLE		AILocalizedString(@"Chats","Title for the messages window menu item")
David@0
    59
David@0
    60
//#define	LOG_RESPONDER_CHAIN
David@0
    61
David@84
    62
@interface AIInterfaceController ()
David@0
    63
- (void)_resetOpenChatsCache;
David@0
    64
- (void)_addItemToMainMenuAndDock:(NSMenuItem *)item;
David@0
    65
- (NSAttributedString *)_tooltipTitleForObject:(AIListObject *)object;
David@0
    66
- (NSAttributedString *)_tooltipBodyForObject:(AIListObject *)object;
David@0
    67
- (void)_pasteWithPreferredSelector:(SEL)preferredSelector sender:(id)sender;
David@0
    68
- (void)updateCloseMenuKeys;
David@0
    69
David@0
    70
- (void)saveContainers;
David@0
    71
- (void)restoreSavedContainers;
David@0
    72
David@0
    73
//Window Menu
David@0
    74
- (void)updateActiveWindowMenuItem;
David@0
    75
- (void)buildWindowMenu;
David@0
    76
David@0
    77
- (AIChat *)mostRecentActiveChat;
David@0
    78
@end
David@0
    79
David@0
    80
/*!
David@0
    81
 * @class AIInterfaceController
David@0
    82
 * @brief Interface controller
David@0
    83
 *
David@0
    84
 * Chat window related requests, such as opening and closing chats, are routed through the interface controller
David@0
    85
 * to the appropriate component. The interface controller keeps track of the most recently active chat, handles chat
David@0
    86
 * cycling (switching between chats), chat sorting, and so on.  The interface controller also handles switching to
David@0
    87
 * an appropriate window or chat when the dock icon is clicked for a 'reopen' event.
David@0
    88
 *
David@0
    89
 * Contact list window requests, such as toggling window visibilty are routed to the contact list controller component.
David@0
    90
 *
David@0
    91
 * Error messages are routed through the interface controller.
David@0
    92
 *
David@0
    93
 * Tooltips, such as seen on hover in the contact list are generated and displayed here.  Tooltip display components and
David@0
    94
 * plugins register with the interface controller to be queried for contact information when a tooltip is displayed.
David@0
    95
 *
David@0
    96
 * When displays in Adium flash, such as in the dock or the contact list for unviewed content, the interface controller
David@0
    97
 * manages keeping the flashing synchronized.
David@0
    98
 *
David@0
    99
 * Finally, the interface controller manages many menu items, providing better menu item validation and target routing
David@0
   100
 * than the responder chain alone would do.
David@0
   101
 */
David@0
   102
@implementation AIInterfaceController
David@0
   103
David@0
   104
- (id)init
David@0
   105
{
David@0
   106
	if ((self = [super init])) {
David@0
   107
		contactListViewArray = [[NSMutableArray alloc] init];
David@0
   108
		messageViewArray = [[NSMutableArray alloc] init];
David@0
   109
		contactListTooltipEntryArray = [[NSMutableArray alloc] init];
David@0
   110
		contactListTooltipSecondaryEntryArray = [[NSMutableArray alloc] init];
David@0
   111
		closeMenuConfiguredForChat = NO;
David@0
   112
		_cachedOpenChats = nil;
David@0
   113
		mostRecentActiveChat = nil;
David@0
   114
		activeChat = nil;
David@0
   115
		
David@0
   116
		tooltipListObject = nil;
David@0
   117
		tooltipTitle = nil;
David@0
   118
		tooltipBody = nil;
David@0
   119
		tooltipImage = nil;
David@0
   120
		flashObserverArray = nil;
David@0
   121
		flashTimer = nil;
David@0
   122
		flashState = 0;
David@0
   123
		
David@0
   124
		windowMenuArray = nil;
David@0
   125
		
David@0
   126
#ifdef LOG_RESPONDER_CHAIN
David@0
   127
		[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(reportResponderChain:) userInfo:nil repeats:YES];
David@0
   128
#endif
David@0
   129
	}
David@0
   130
	
David@0
   131
	return self;
David@0
   132
}
David@0
   133
David@0
   134
#ifdef LOG_RESPONDER_CHAIN
David@0
   135
//Can be called by a timer to periodically log the responder chain
David@0
   136
//[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(reportResponderChain:) userInfo:nil repeats:YES];
David@0
   137
- (void)reportResponderChain:(NSTimer *)inTimer
David@0
   138
{
David@0
   139
	NSMutableString	*responderChain = [NSMutableString string];
David@0
   140
	
David@0
   141
	NSWindow	*keyWindow = [[NSApplication sharedApplication] keyWindow];
David@3
   142
#warning 64BIT: Check formatting arguments
David@0
   143
	[responderChain appendFormat:@"%@ (%i): ",keyWindow,[keyWindow respondsToSelector:@selector(print:)]];
David@0
   144
	
David@0
   145
	NSResponder	*responder = [keyWindow firstResponder];
David@0
   146
	
David@0
   147
	//First, walk down the responder chain looking for a responder which can handle the preferred selector
David@0
   148
	while (responder) {
David@3
   149
#warning 64BIT: Check formatting arguments
David@0
   150
		[responderChain appendFormat:@"%@ (%i)",responder,[responder respondsToSelector:@selector(print:)]];
David@0
   151
		responder = [responder nextResponder];
David@0
   152
		if (responder) [responderChain appendString:@" -> "];
David@0
   153
	}
David@0
   154
David@0
   155
	NSLog(responderChain);
David@0
   156
}
David@0
   157
#endif
David@0
   158
David@0
   159
- (void)controllerDidLoad
David@0
   160
{
David@0
   161
    //Load the interface
David@0
   162
    [interfacePlugin openInterface];
David@0
   163
David@0
   164
	//Open the contact list window
David@0
   165
    [self showContactList:nil];
David@0
   166
	
David@0
   167
	//Userlist show/hide item
David@0
   168
	menuItem_toggleUserlist = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Toggle User List", nil)
David@0
   169
																							 target:self
David@0
   170
																							 action:@selector(toggleUserlist:)
zacw@872
   171
																					  keyEquivalent:@"/"];
zacw@872
   172
	[menuItem_toggleUserlist setKeyEquivalentModifierMask:(NSCommandKeyMask | NSAlternateKeyMask)];
zacw@872
   173
	
zacw@1543
   174
	[adium.menuController addMenuItem:menuItem_toggleUserlist toLocation:LOC_Display_General];
zacw@1587
   175
	
zacw@1587
   176
	menuItem_toggleUserlistSide = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Toggle User List Side", nil)
zacw@1587
   177
																				   target:self
zacw@1587
   178
																				   action:@selector(toggleUserlistSide:)
zacw@1587
   179
																			keyEquivalent:@""];
zacw@1587
   180
	
zacw@1587
   181
	[adium.menuController addMenuItem:menuItem_toggleUserlistSide toLocation:LOC_Display_General];
zacw@1244
   182
zacw@1244
   183
	NSMenuItem *menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Toggle User List", nil)
zacw@1244
   184
																				target:self
zacw@1689
   185
																				action:@selector(toggleUserlist:)
zacw@1244
   186
																		 keyEquivalent:@""] autorelease];
zacw@1240
   187
	
zacw@1244
   188
	[adium.menuController addContextualMenuItem:menuItem toLocation:Context_GroupChat_Action];
zacw@1588
   189
	
zacw@1588
   190
	// Clear display
zacw@1588
   191
	menuItem_clearDisplay = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Clear Display", nil)
zacw@1588
   192
																				 target:self
zacw@1588
   193
																				 action:@selector(clearDisplay:)
zacw@1588
   194
																		  keyEquivalent:@""];
zacw@1588
   195
	[adium.menuController addMenuItem:menuItem_clearDisplay toLocation:LOC_Display_MessageControl];
David@0
   196
																			  
David@0
   197
	//Contact list menu item
zacw@1244
   198
	menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Contact List","Name of the window which lists contacts")
David@0
   199
																				target:self
David@0
   200
																				action:@selector(toggleContactList:)
David@0
   201
																		 keyEquivalent:@"/"];
David@100
   202
	[adium.menuController addMenuItem:menuItem toLocation:LOC_Window_Fixed];
David@100
   203
	[adium.menuController addMenuItem:[[menuItem copy] autorelease] toLocation:LOC_Dock_Status];
David@0
   204
	[menuItem release];
David@0
   205
	
David@0
   206
	menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Close Chat","Title for the close chat menu item")
David@0
   207
																	target:self
David@0
   208
																	action:@selector(closeContextualChat:)
David@0
   209
															 keyEquivalent:@""];
David@100
   210
	[adium.menuController addContextualMenuItem:menuItem toLocation:Context_Tab_Action];
David@0
   211
	[menuItem release];
zacw@1260
   212
	
zacw@1260
   213
	// Authorization requests menu item
zacw@2693
   214
	menuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedStringFromTableInBundle(@"Authorization Requests",nil, [NSBundle bundleForClass:[AIAuthorizationRequestsWindowController class]], nil)
zacw@2693
   215
										  target:self
zacw@2693
   216
										  action:@selector(openAuthorizationWindow:)
zacw@2693
   217
								   keyEquivalent:@""];
zacw@1260
   218
	
zacw@1260
   219
	[adium.menuController addMenuItem:menuItem toLocation:LOC_Window_Auxiliary];
zacw@1260
   220
	[menuItem release];
David@0
   221
David@0
   222
    //Observe content so we can open chats as necessary
David@1109
   223
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveContent:) 
David@0
   224
									   name:CONTENT_MESSAGE_RECEIVED object:nil];
David@1109
   225
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveContent:) 
David@0
   226
									   name:CONTENT_MESSAGE_RECEIVED_GROUP object:nil];
David@0
   227
	
David@0
   228
	//Observe Adium finishing loading so we can do things which may require other components or plugins
David@1109
   229
	[[NSNotificationCenter defaultCenter] addObserver:self
David@0
   230
								   selector:@selector(adiumDidFinishLoading:)
David@0
   231
									   name:AIApplicationDidFinishLoadingNotification
David@0
   232
									 object:nil];
David@0
   233
	
David@0
   234
	//Observe quits so we can save containers.
David@1109
   235
	[[NSNotificationCenter defaultCenter] addObserver:self
David@0
   236
								   selector:@selector(saveContainersOnQuit:)
David@0
   237
									   name:AIAppWillTerminateNotification
David@0
   238
									 object:nil];
David@0
   239
}
David@0
   240
David@0
   241
- (void)controllerWillClose
David@0
   242
{
David@0
   243
    [contactListPlugin closeContactList];
David@0
   244
    [interfacePlugin closeInterface];
David@0
   245
}
David@0
   246
David@0
   247
// Dealloc
David@0
   248
- (void)dealloc
David@0
   249
{
David@0
   250
    [contactListViewArray release]; contactListViewArray = nil;
David@0
   251
    [messageViewArray release]; messageViewArray = nil;
David@0
   252
    [interfaceArray release]; interfaceArray = nil;
David@0
   253
	
David@0
   254
    [tooltipListObject release]; tooltipListObject = nil;
David@0
   255
	[tooltipTitle release]; tooltipTitle = nil;
David@0
   256
	[tooltipBody release]; tooltipBody = nil;
David@0
   257
	[tooltipImage release]; tooltipImage = nil;
David@0
   258
	
David@1109
   259
	[[NSNotificationCenter defaultCenter] removeObserver:self];
David@95
   260
	[adium.preferenceController unregisterPreferenceObserver:self];
David@0
   261
	
David@0
   262
    [super dealloc];
David@0
   263
}
David@0
   264
David@0
   265
- (void)adiumDidFinishLoading:(NSNotification *)inNotification
David@0
   266
{
David@0
   267
	//Observe preference changes. This will also restore saved containers if appropriate.
David@95
   268
	[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_INTERFACE];
David@0
   269
	
David@1109
   270
	[[NSNotificationCenter defaultCenter] removeObserver:self
David@0
   271
										  name:AIApplicationDidFinishLoadingNotification
David@0
   272
										object:nil];
David@0
   273
}
David@0
   274
David@0
   275
//Registers code to handle the interface
David@0
   276
- (void)registerInterfaceController:(id <AIInterfaceComponent>)inController
David@0
   277
{
David@0
   278
	if (!interfacePlugin) interfacePlugin = [inController retain];
David@0
   279
}
David@0
   280
David@0
   281
//Register code to handle the contact list
David@0
   282
- (void)registerContactListController:(id <AIContactListComponent>)inController
David@0
   283
{
David@0
   284
	if (!contactListPlugin) contactListPlugin = [inController retain];
David@0
   285
}
David@0
   286
David@0
   287
//Preferences changed
David@0
   288
- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
David@0
   289
							object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
David@0
   290
{
David@0
   291
	if (!object) {
David@0
   292
		//Update prefs
David@0
   293
		tabbedChatting = [[prefDict objectForKey:KEY_TABBED_CHATTING] boolValue];
David@0
   294
		groupChatsByContactGroup = [[prefDict objectForKey:KEY_GROUP_CHATS_BY_GROUP] boolValue];
David@0
   295
		saveContainers = [[prefDict objectForKey:KEY_SAVE_CONTAINERS] boolValue];
David@0
   296
	
David@0
   297
		if (firstTime) {
David@0
   298
			if (saveContainers) {
David@0
   299
				//Restore saved containers
David@0
   300
				[self restoreSavedContainers];	
David@0
   301
			} else if ([prefDict objectForKey:KEY_CONTAINERS]) {
David@0
   302
				/* We've loaded without wanting to save containers; clear any saved
David@0
   303
				 * from a previous session.
David@0
   304
				 */
David@95
   305
				[adium.preferenceController setPreference:nil
David@0
   306
													 forKey:KEY_CONTAINERS
David@0
   307
													  group:PREF_GROUP_INTERFACE];
David@0
   308
			}
David@0
   309
		}
David@0
   310
	}
David@0
   311
}
David@0
   312
David@0
   313
//Handle a reopen/dock icon click
David@0
   314
- (BOOL)handleReopenWithVisibleWindows:(BOOL)visibleWindows
David@0
   315
{
David@0
   316
	if (![self contactListIsVisibleAndMain] && [[interfacePlugin openContainerIDs] count] == 0) {
David@0
   317
		//The contact list is not visible, and there are no chat windows. Make the contact list visible.
David@0
   318
		[self showContactList:nil];
David@0
   319
David@0
   320
	} else {
David@0
   321
		AIChat	*mostRecentUnviewedChat;
David@0
   322
David@0
   323
		//If windows are open, try switching to a chat with unviewed content
David@95
   324
		if ((mostRecentUnviewedChat = [adium.chatController mostRecentUnviewedChat])) {
David@0
   325
			if ([mostRecentActiveChat unviewedContentCount]) {
David@0
   326
				//If the most recently active chat has unviewed content, ensure it is in the front
David@0
   327
				[self setActiveChat:mostRecentActiveChat];
David@0
   328
			} else {
David@0
   329
				//Otherwise, switch to the chat which most recently received content
David@0
   330
				[self setActiveChat:mostRecentUnviewedChat];
David@0
   331
			}
David@0
   332
David@0
   333
		} else {
Evan@166
   334
			NSWindow *targetWindow = nil;
Evan@166
   335
			BOOL	 unMinimizedWindows = 0;
David@0
   336
			
David@0
   337
			//If there was no unviewed content, ensure that atleast one of Adium's windows is unminimized
Evan@166
   338
			for (NSWindow *window in [NSApp windows]) {
David@0
   339
				//Check stylemask to rule out the system menu's window (Which reports itself as visible like a real window)
David@0
   340
				if (([window styleMask] & (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask))) {
David@0
   341
					if (!targetWindow) targetWindow = window;
David@0
   342
					if (![window isMiniaturized]) unMinimizedWindows++;
David@0
   343
				}
David@0
   344
			}
David@0
   345
			
David@0
   346
			//If there are no unminimized windows, unminimize the last one
David@0
   347
			if (unMinimizedWindows == 0 && targetWindow) {
David@0
   348
				[targetWindow deminiaturize:nil];
David@0
   349
			}
David@0
   350
		}
David@0
   351
	}
David@0
   352
David@0
   353
	return YES; 
David@0
   354
}
David@0
   355
David@0
   356
//Contact List ---------------------------------------------------------------------------------------------------------
David@0
   357
#pragma mark Contact list
David@0
   358
/*!
David@0
   359
 * @brief Toggles contact list between visible and hiden
David@0
   360
 */
David@0
   361
- (IBAction)toggleContactList:(id)sender
David@0
   362
{
David@0
   363
    if ([self contactListIsVisibleAndMain]) {
David@0
   364
		[self closeContactList:nil];
David@0
   365
    } else {
David@0
   366
		[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
David@0
   367
		[self showContactList:nil];
David@0
   368
    } 
David@0
   369
}
David@0
   370
David@0
   371
/*!
David@0
   372
 * @brief Brings contact list to the front
David@0
   373
 */
David@0
   374
- (IBAction)showContactList:(id)sender
David@0
   375
{
David@0
   376
	[contactListPlugin showContactListAndBringToFront:YES];
David@0
   377
}
David@0
   378
David@0
   379
/*!
David@0
   380
 * @brief Show the contact list window and bring Adium to the front
David@0
   381
 */
David@0
   382
- (IBAction)showContactListAndBringToFront:(id)sender
David@0
   383
{
David@0
   384
	[contactListPlugin showContactListAndBringToFront:YES];
David@0
   385
	[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
David@0
   386
}
David@0
   387
David@0
   388
/*!
David@0
   389
 * @brief Close the contact list window
David@0
   390
 */
David@0
   391
- (IBAction)closeContactList:(id)sender
David@0
   392
{
David@0
   393
	[contactListPlugin closeContactList];
David@0
   394
}
David@0
   395
David@0
   396
/*!
David@0
   397
 * @returns YES if contact list is visible and selected, otherwise NO
David@0
   398
 */
David@0
   399
- (BOOL)contactListIsVisibleAndMain
David@0
   400
{
David@0
   401
	return [contactListPlugin contactListIsVisibleAndMain];
David@0
   402
}
David@0
   403
David@0
   404
/*!
David@0
   405
* @returns YES if contact list is visible, otherwise NO
David@0
   406
 */
David@0
   407
- (BOOL)contactListIsVisible
David@0
   408
{
David@0
   409
	return [contactListPlugin contactListIsVisible];
David@0
   410
}
David@0
   411
David@0
   412
//Detachable Contact List ----------------------------------------------------------------------------------------------
David@0
   413
#pragma mark Detachable Contact List
David@0
   414
David@0
   415
/*!
David@0
   416
 * @returns Created contact list controller for detached contact list
David@0
   417
 */
David@52
   418
- (AIListWindowController *)detachContactList:(AIContactList *)aContactList
David@0
   419
{
David@0
   420
	return [contactListPlugin detachContactList:aContactList];
David@0
   421
}
David@0
   422
David@0
   423
David@0
   424
#pragma mark Container Saving
David@0
   425
/*!
David@0
   426
 * @brief Restores containers saved from a previous session
David@0
   427
 */
David@0
   428
- (void)restoreSavedContainers
David@0
   429
{
David@95
   430
	NSData				*savedData = [adium.preferenceController preferenceForKey:KEY_CONTAINERS
David@0
   431
																	group:PREF_GROUP_INTERFACE];
David@0
   432
	
David@0
   433
	// If there's no data, we can't restore anything.
David@0
   434
	if (!savedData)
David@0
   435
		return;
Evan@166
   436
Evan@166
   437
	for (NSDictionary *dict in [NSKeyedUnarchiver unarchiveObjectWithData:savedData]) {
David@0
   438
		AIMessageWindowController *windowController = [self openContainerWithID:[dict objectForKey:@"ID"]
David@0
   439
																 name:[dict objectForKey:@"Name"]];
Evan@166
   440
		AIChat *containerActiveChat = nil;
David@0
   441
		
David@0
   442
		// Position the container where it was last saved (using -savedFrameFromString: to prevent going offscreen)
David@0
   443
		[[windowController window] setFrame:[windowController savedFrameFromString:[dict objectForKey:@"Frame"]] display:YES];
David@0
   444
		
Evan@166
   445
		for (NSDictionary *chatDict in [dict objectForKey:@"Content"]) {
David@0
   446
			AIChat			*chat = nil;
David@95
   447
			AIService		*service = [adium.accountController firstServiceWithServiceID:[chatDict objectForKey:@"serviceID"]];
David@95
   448
			AIAccount		*account = [adium.accountController accountWithInternalObjectID:[chatDict objectForKey:@"AccountID"]];
David@0
   449
					
David@0
   450
			if ([[chatDict objectForKey:@"IsGroupChat"] boolValue]) {
David@95
   451
				chat = [adium.chatController chatWithName:[chatDict objectForKey:@"Name"]
David@0
   452
												 identifier:nil
David@0
   453
												  onAccount:account
David@0
   454
										   chatCreationInfo:[chatDict objectForKey:@"ChatCreationInfo"]];
David@0
   455
			} else {
David@89
   456
				AIListContact		*contact = [adium.contactController contactWithService:service
David@0
   457
																					account:account
David@0
   458
																						UID:[chatDict objectForKey:@"UID"]];
David@0
   459
				
David@95
   460
				chat = [adium.chatController chatWithContact:contact];
David@0
   461
			}
David@0
   462
			
David@0
   463
			// Tag the chat as restored.
David@0
   464
			[chat setValue:[NSNumber numberWithBool:YES]
David@0
   465
			   forProperty:@"Restored Chat"
David@0
   466
					notify:NotifyNow];
David@0
   467
			
David@0
   468
			if ([[chatDict objectForKey:@"ActiveChat"] boolValue]) {
David@0
   469
				containerActiveChat = chat;
David@0
   470
			}
David@0
   471
					
David@0
   472
			// Open the chat into the container we've created above.
David@0
   473
			[self openChat:chat inContainerWithID:[dict objectForKey:@"ID"] atIndex:-1];
David@0
   474
		}
David@0
   475
		
David@0
   476
		if (containerActiveChat)
David@0
   477
			[self setActiveChat:containerActiveChat];
David@0
   478
	}
David@0
   479
}
David@0
   480
David@0
   481
/*!
David@0
   482
 * @brief Saves open container information with their content when Adium quits
David@0
   483
 */
David@0
   484
- (void)saveContainersOnQuit:(NSNotification *)notification
David@0
   485
{
David@0
   486
	[self saveContainers];
David@0
   487
}
David@0
   488
David@0
   489
/*!
David@0
   490
 * @brief Save opened containers and windows
David@0
   491
 *
David@0
   492
 * @param withContent Save the current buffer of the window to restore at a later point
David@0
   493
 */
David@0
   494
- (void)saveContainers
David@0
   495
{
David@0
   496
	if (!saveContainers) {
David@0
   497
		// Don't save anything if we're not set to.
David@0
   498
		return;
David@0
   499
	}
David@0
   500
David@0
   501
	// Save active containers.
David@0
   502
	NSMutableArray		*savedContainers = [NSMutableArray array];
David@0
   503
	
Evan@166
   504
	for (NSDictionary *dict in [interfacePlugin openContainersAndChats]) {
David@0
   505
		NSMutableArray		*containerContents = [NSMutableArray array];
David@0
   506
		
Evan@166
   507
		for (AIChat *chat in [dict objectForKey:@"Content"]) {
David@0
   508
			NSMutableDictionary		*newContainerDict = [NSMutableDictionary dictionary];
David@0
   509
David@428
   510
			[newContainerDict setObject:chat.account.internalObjectID forKey:@"AccountID"];
David@0
   511
			
David@0
   512
			// Save chat-specific information.
Evan@166
   513
			if (chat.isGroupChat) {
David@0
   514
				// -chatCreationDictionary may be nil, so put it last.
David@0
   515
				[newContainerDict addEntriesFromDictionary:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   516
															[NSNumber numberWithBool:YES], @"IsGroupChat",
David@0
   517
															[NSNumber numberWithBool:([dict objectForKey:@"ActiveChat"] == chat)], @"ActiveChat",
David@721
   518
															chat.name, @"Name",
David@0
   519
															[chat chatCreationDictionary], @"ChatCreationInfo",nil]];
David@0
   520
			} else {
David@0
   521
				[newContainerDict addEntriesFromDictionary:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   522
															[NSNumber numberWithBool:([dict objectForKey:@"ActiveChat"] == chat)], @"ActiveChat",
David@426
   523
															chat.listObject.UID, @"UID",
David@715
   524
															chat.account.service.serviceID, @"serviceID",
David@426
   525
															chat.account.internalObjectID, @"AccountID",nil]];
David@0
   526
			}
David@0
   527
					
David@0
   528
			[containerContents addObject:newContainerDict];
David@0
   529
		}
David@0
   530
		
David@0
   531
		// Replace the "Content" key in -openContainersAndChats with our version of the content.
David@0
   532
		// Remove the ActiveChat reference
David@0
   533
		// We use the same keys otherwise that -openContainersAndChats provides (Name, ID, Frame)
David@0
   534
		NSMutableDictionary *saveDict = [[dict mutableCopy] autorelease];
David@0
   535
David@0
   536
		[saveDict removeObjectForKey:@"ActiveChat"];
David@0
   537
		
David@0
   538
		[saveDict setObject:containerContents
David@0
   539
					 forKey:@"Content"];
David@0
   540
		
David@0
   541
		[savedContainers addObject:saveDict];
David@0
   542
	}
David@0
   543
	
David@95
   544
	[adium.preferenceController setPreference:[NSKeyedArchiver archivedDataWithRootObject:savedContainers]
David@0
   545
										 forKey:KEY_CONTAINERS
David@0
   546
										  group:PREF_GROUP_INTERFACE];
David@0
   547
}
David@0
   548
David@0
   549
//Messaging ------------------------------------------------------------------------------------------------------------
David@0
   550
//Methods for instructing the interface to provide a representation of chats, and to determine which chat has user focus
David@0
   551
#pragma mark Messaging
David@0
   552
David@0
   553
/*!
David@0
   554
 * @brief Opens window for chat
David@0
   555
 */
David@0
   556
- (void)openChat:(AIChat *)inChat
David@0
   557
{
David@0
   558
	NSArray		*containerIDs = [interfacePlugin openContainerIDs];
David@0
   559
	NSString	*containerID = nil;
David@0
   560
	NSString	*containerName = nil;
David@0
   561
	
David@0
   562
	//Determine the correct container for this chat
David@0
   563
	
David@0
   564
	if (!tabbedChatting) {
David@0
   565
		//We're not using tabs; each chat starts in its own container, based on the destination object or the chat name
David@0
   566
		if ([inChat listObject]) {
David@426
   567
			containerID = inChat.listObject.internalObjectID;
David@0
   568
		} else {
David@426
   569
			containerID = inChat.name;
David@0
   570
		}
David@0
   571
		
David@0
   572
	} else if (groupChatsByContactGroup) {
David@426
   573
		if (inChat.isGroupChat) {
David@0
   574
			containerID = AILocalizedString(@"Group Chats",nil);
David@0
   575
			
David@0
   576
		} else {
David@594
   577
			//XXX multiple containers: this is "correct" but maybe not desirable, as it is non-deterministic
David@594
   578
			AIListGroup	*group = inChat.listObject.parentContact.groups.anyObject;
David@0
   579
			
David@0
   580
			//If the contact is in the contact list root, we don't have a group
David@52
   581
			if (group && ![group isKindOfClass:[AIContactList class]]) {
David@426
   582
				containerID = group.displayName;
David@0
   583
			}
David@0
   584
		}
David@0
   585
		
David@0
   586
		containerName = containerID;
David@0
   587
	}
David@0
   588
	
David@0
   589
	if (!containerID) {
David@0
   590
		//Open new chats into the first container (if not available, create a new one)
David@0
   591
		if ([containerIDs count] > 0) {
David@0
   592
			containerID = [containerIDs objectAtIndex:0];
David@0
   593
		} else {
David@0
   594
			containerID = nil;
David@0
   595
		}
David@0
   596
	}
David@0
   597
David@0
   598
	//Determine the correct placement for this chat within the container
David@0
   599
	[interfacePlugin openChat:inChat inContainerWithID:containerID withName:containerName atIndex:-1];
David@0
   600
	if (![inChat isOpen]) {
David@0
   601
		[inChat setIsOpen:YES];
David@0
   602
		
David@0
   603
		//Post the notification last, so observers receive a chat whose isOpen flag is yes.
David@1109
   604
		[[NSNotificationCenter defaultCenter] postNotificationName:Chat_DidOpen object:inChat userInfo:nil];
David@0
   605
	}
David@0
   606
}
David@0
   607
David@3
   608
- (id)openChat:(AIChat *)inChat inContainerWithID:(NSString *)containerID atIndex:(NSUInteger)index
David@0
   609
{	
David@0
   610
	NSArray		*openContainerIDs = [interfacePlugin openContainerIDs];
David@0
   611
David@0
   612
	if (!containerID) {
David@0
   613
		//Open new chats into the first container (if not available, create a new one)
David@0
   614
		if ([openContainerIDs count] > 0) {
David@0
   615
			containerID = [openContainerIDs objectAtIndex:0];
David@0
   616
		} else {
David@0
   617
			containerID = AILocalizedString(@"Chats",nil);
David@0
   618
		}
David@0
   619
	}
David@0
   620
David@0
   621
	//Determine the correct placement for this chat within the container
David@0
   622
	id tabViewItem = [interfacePlugin openChat:inChat inContainerWithID:containerID withName:nil atIndex:index];
David@0
   623
	if (![inChat isOpen]) {
David@0
   624
		[inChat setIsOpen:YES];
David@0
   625
		
David@0
   626
		//Post the notification last, so observers receive a chat whose isOpen flag is yes.
David@1109
   627
		[[NSNotificationCenter defaultCenter] postNotificationName:Chat_DidOpen object:inChat userInfo:nil];
David@0
   628
	}
David@0
   629
	return tabViewItem;
David@0
   630
}
David@0
   631
David@0
   632
/**
David@0
   633
 * @brief Opens a container with a specific ID
David@0
   634
 *
David@0
   635
 * Asks the interfacePlugin to openContainerWithID:
David@0
   636
 */
David@0
   637
- (AIMessageWindowController *)openContainerWithID:(NSString *)containerID name:(NSString *)containerName
David@0
   638
{
David@0
   639
	return [interfacePlugin openContainerWithID:containerID name:containerName];
David@0
   640
}
David@0
   641
David@0
   642
/*!
David@0
   643
 * @brief Close the interface for a chat
David@0
   644
 *
David@0
   645
 * Tell the interface plugin to close the chat.
David@0
   646
 */
David@0
   647
- (void)closeChat:(AIChat *)inChat
David@0
   648
{
David@0
   649
	if (inChat) {
David@95
   650
		if ([adium.chatController closeChat:inChat]) {
David@0
   651
			[interfacePlugin closeChat:inChat];
David@0
   652
		}
David@0
   653
	}
David@0
   654
}
David@0
   655
David@0
   656
/*!
David@0
   657
 * @brief Consolidate chats into a single container
David@0
   658
 */
David@0
   659
- (void)consolidateChats
David@0
   660
{
David@0
   661
	//We work with copies of these arrays, since moving chats may change their contents
David@0
   662
	NSArray			*openContainerIDs = [[interfacePlugin openContainerIDs] copy];
David@0
   663
	NSEnumerator	*containerEnumerator = [openContainerIDs objectEnumerator];
David@0
   664
	NSString		*firstContainerID = [containerEnumerator nextObject];
David@0
   665
	NSString		*containerID;
David@0
   666
	
David@0
   667
	//For all containers but the first, move the chats they contain to the first container
David@0
   668
	while ((containerID = [containerEnumerator nextObject])) {
David@0
   669
		NSArray			*openChats = [[interfacePlugin openChatsInContainerWithID:containerID] copy];
David@0
   670
		NSEnumerator	*chatEnumerator = [openChats objectEnumerator];
David@0
   671
		AIChat			*chat;
David@0
   672
David@0
   673
		//Move all the chats, providing a target index if chat sorting is enabled
David@0
   674
		while ((chat = [chatEnumerator nextObject])) {
David@0
   675
			[interfacePlugin moveChat:chat
David@0
   676
					toContainerWithID:firstContainerID
David@0
   677
								index:-1];
David@0
   678
		}
David@0
   679
		
David@0
   680
		[openChats release];
David@0
   681
	}
David@0
   682
	
David@0
   683
	[self chatOrderDidChange];
David@0
   684
	
David@0
   685
	[openContainerIDs release];
David@0
   686
}
David@0
   687
David@0
   688
- (void)moveChatToNewContainer:(AIChat *)inChat
David@0
   689
{
David@0
   690
	[interfacePlugin moveChatToNewContainer:inChat];
David@0
   691
}
David@0
   692
David@0
   693
/*!
David@0
   694
 * @returns Active chat
David@0
   695
 */
David@0
   696
- (AIChat *)activeChat
David@0
   697
{
David@0
   698
	return activeChat;
David@0
   699
}
David@0
   700
David@0
   701
/*!
David@0
   702
 * @brief Set the active chat window
David@0
   703
 */
David@0
   704
- (void)setActiveChat:(AIChat *)inChat
David@0
   705
{
David@0
   706
	[interfacePlugin setActiveChat:inChat];
David@0
   707
}
David@0
   708
David@0
   709
/*!
David@0
   710
 * @returns Last chat to be active, nil if not chat is open
David@0
   711
 */
David@0
   712
- (AIChat *)mostRecentActiveChat
David@0
   713
{
David@0
   714
	return mostRecentActiveChat;
David@0
   715
}
David@0
   716
David@0
   717
/*!
David@0
   718
 * @brief Sets active chat window based on chat
David@0
   719
 */
David@0
   720
- (void)setMostRecentActiveChat:(AIChat *)inChat
David@0
   721
{
David@0
   722
	[self setActiveChat:inChat];
David@0
   723
}
David@0
   724
David@0
   725
/*!
David@0
   726
 * @returns Array of open chats (cached, so call as frequently as desired)
David@0
   727
 */
David@0
   728
- (NSArray *)openChats
David@0
   729
{
David@0
   730
	if (!_cachedOpenChats) {
David@0
   731
		_cachedOpenChats = [[interfacePlugin openChats] retain];
David@0
   732
	}
David@0
   733
	
David@0
   734
	return _cachedOpenChats;
David@0
   735
}
David@0
   736
David@0
   737
- (NSArray *)openContainerIDs
David@0
   738
{
David@0
   739
	return [interfacePlugin openContainerIDs];
David@0
   740
}
David@0
   741
David@0
   742
/*!
David@0
   743
 * @param containerID ID for chat window
David@0
   744
 *
David@0
   745
 * @returns Array of all chats in chat window
David@0
   746
 */
David@0
   747
- (NSArray *)openChatsInContainerWithID:(NSString *)containerID
David@0
   748
{
David@0
   749
	return [interfacePlugin openChatsInContainerWithID:containerID];
David@0
   750
}
David@0
   751
David@0
   752
/*!
David@0
   753
 * @brief Resets the cache of open chats
David@0
   754
 */
David@0
   755
- (void)_resetOpenChatsCache
David@0
   756
{
David@0
   757
	[_cachedOpenChats release]; _cachedOpenChats = nil;
David@0
   758
}
David@0
   759
David@0
   760
David@0
   761
David@0
   762
//Interface plugin callbacks -------------------------------------------------------------------------------------------
David@0
   763
//These methods are called by the interface to let us know what's going on.  We're informed of chats opening, closing,
David@0
   764
//changing order, etc.
David@0
   765
#pragma mark Interface plugin callbacks
David@0
   766
/*!
David@0
   767
 * @brief A chat window did open: rebuild our window menu to show the new chat
David@0
   768
 *
David@0
   769
 * This should be called by the interface plugin (e.g. AIDualWindowInterfacePlugin) after a chat opens
David@0
   770
 *
David@0
   771
 * @param inChat Newly created chat 
David@0
   772
 */
David@0
   773
- (void)chatDidOpen:(AIChat *)inChat
David@0
   774
{
David@0
   775
	[self _resetOpenChatsCache];
David@0
   776
	[self buildWindowMenu];
David@0
   777
	[self saveContainers];
David@0
   778
}
David@0
   779
David@0
   780
/*!
David@0
   781
 * @brief A chat has become active: update our chat closing keys and flag this chat as selected in the window menu
David@0
   782
 *
David@0
   783
 * @param inChat Chat which has become active
David@0
   784
 */
David@0
   785
- (void)chatDidBecomeActive:(AIChat *)inChat
David@0
   786
{
David@0
   787
	AIChat	*previouslyActiveChat = activeChat;
David@0
   788
	
David@0
   789
	activeChat = [inChat retain];
David@0
   790
	
David@0
   791
	[self updateCloseMenuKeys];
David@0
   792
	[self updateActiveWindowMenuItem];
David@0
   793
	
David@0
   794
	if (inChat && (inChat != mostRecentActiveChat)) {
David@0
   795
		[mostRecentActiveChat release]; mostRecentActiveChat = nil;
David@0
   796
		mostRecentActiveChat = [inChat retain];
David@0
   797
	}
David@0
   798
	
David@1109
   799
	[[NSNotificationCenter defaultCenter] postNotificationName:Chat_BecameActive
David@0
   800
											  object:inChat 
David@0
   801
											userInfo:(previouslyActiveChat ?
David@0
   802
													  [NSDictionary dictionaryWithObject:previouslyActiveChat
David@0
   803
																				  forKey:@"PreviouslyActiveChat"] :
David@0
   804
													  nil)];
David@0
   805
	
David@0
   806
	if (inChat) {
David@0
   807
		/* Clear the unviewed content on the next event loop so other methods have a chance to react to the chat becoming
David@0
   808
		* active. Specifically, this lets the handleReopenWithVisibleWindows: method have a chance to know that this chat
David@0
   809
		* had unviewed content.
David@0
   810
		*/
David@0
   811
		[inChat performSelector:@selector(clearUnviewedContentCount)
David@0
   812
					 withObject:nil
David@0
   813
					 afterDelay:0];
David@0
   814
	}
David@0
   815
	
David@0
   816
	[previouslyActiveChat release];	
David@0
   817
}
David@0
   818
David@0
   819
/*!
David@0
   820
 * @brief A chat has become visible: send out a notification for components and plugins to take action
David@0
   821
 *
David@0
   822
 * @param inChat Chat that has become active
David@0
   823
 * @param nWindow Containing chat window
David@0
   824
 */
David@0
   825
- (void)chatDidBecomeVisible:(AIChat *)inChat inWindow:(NSWindow *)inWindow
David@0
   826
{
David@1109
   827
	[[NSNotificationCenter defaultCenter] postNotificationName:@"AIChatDidBecomeVisible"
David@0
   828
											  object:inChat
David@0
   829
											userInfo:[NSDictionary dictionaryWithObject:inWindow
David@0
   830
																				 forKey:@"NSWindow"]];
David@0
   831
}
David@0
   832
David@0
   833
/*!
David@0
   834
 * @brief Find the window currently displaying a chat
David@0
   835
 *
David@0
   836
 * @returns Window for chat otherwise if the chat is not in any window, or is not visible in any window, returns nil
David@0
   837
 */
David@0
   838
- (NSWindow *)windowForChat:(AIChat *)inChat
David@0
   839
{
David@0
   840
	return [interfacePlugin windowForChat:inChat];
David@0
   841
}
David@0
   842
David@0
   843
/*!
David@0
   844
 * @brief Find the chat active in a window
David@0
   845
 *
David@0
   846
 * If the window does not have an active chat, nil is returned
David@0
   847
 */
David@0
   848
- (AIChat *)activeChatInWindow:(NSWindow *)window
David@0
   849
{
David@0
   850
	return [interfacePlugin activeChatInWindow:window];
David@0
   851
}
David@0
   852
David@0
   853
/*!
David@0
   854
 * @brief A chat window did close: rebuild our window menu to remove the chat
David@0
   855
 * 
David@0
   856
 * @param inChat Chat that closed
David@0
   857
 */
David@0
   858
- (void)chatDidClose:(AIChat *)inChat
David@0
   859
{
David@0
   860
	[self _resetOpenChatsCache];
David@0
   861
	[inChat clearUnviewedContentCount];
David@0
   862
	[self buildWindowMenu];
David@0
   863
	
David@426
   864
	if (!adium.isQuitting) {
David@0
   865
		// Don't save containers when the chats are closed while quitting
David@0
   866
		[self saveContainers];
David@0
   867
	}
David@0
   868
	
David@0
   869
	if (inChat == activeChat) {
David@0
   870
		[activeChat release]; activeChat = nil;
David@0
   871
	}
David@0
   872
	
David@0
   873
	if (inChat == mostRecentActiveChat) {
David@0
   874
		[mostRecentActiveChat release]; mostRecentActiveChat = nil;
David@0
   875
	}
David@0
   876
}
David@0
   877
David@0
   878
/*!
David@0
   879
 * @brief The order of chats has changed: rebuild our window menu to reflect the new order
David@0
   880
 */
David@0
   881
- (void)chatOrderDidChange
David@0
   882
{
David@0
   883
	[self _resetOpenChatsCache];
David@0
   884
	[self buildWindowMenu];
David@0
   885
David@426
   886
	if (!adium.isQuitting) {
David@0
   887
		// Don't save containers when the chats are closed while quitting
David@0
   888
		[self saveContainers];
David@0
   889
	}
David@0
   890
	
David@1109
   891
	[[NSNotificationCenter defaultCenter] postNotificationName:Chat_OrderDidChange object:nil userInfo:nil];
David@0
   892
	
David@0
   893
}
David@0
   894
David@0
   895
#pragma mark Unviewed content
David@0
   896
David@0
   897
/*!
David@0
   898
 * @breif Content was received, increase the unviewed content count of the chat (if it's not currently active)
David@0
   899
 */
David@0
   900
- (void)didReceiveContent:(NSNotification *)notification
David@0
   901
{
David@0
   902
	AIChat		*chat = [[notification userInfo] objectForKey:@"AIChat"];
David@0
   903
	
David@0
   904
	if (chat != activeChat) {
David@0
   905
		[chat incrementUnviewedContentCount];
David@0
   906
	}
David@0
   907
}
David@0
   908
David@0
   909
David@0
   910
//Chat close menus -----------------------------------------------------------------------------------------------------
David@0
   911
#pragma mark Chat close menus
David@0
   912
David@0
   913
/*!
David@0
   914
 * @brief Closes currently active window
David@0
   915
 */
David@0
   916
- (IBAction)closeMenu:(id)sender
David@0
   917
{
David@0
   918
    [[[NSApplication sharedApplication] keyWindow] performClose:nil];
David@0
   919
}
David@0
   920
David@0
   921
/*!
David@0
   922
 * @brief Closes currently active chat (if there is an active chat)
David@0
   923
 */
David@0
   924
- (IBAction)closeChatMenu:(id)sender
David@0
   925
{
David@0
   926
	if (activeChat) [self closeChat:activeChat];
David@0
   927
}
David@0
   928
David@0
   929
/*!
David@0
   930
 * @brief Closes currently selected chat based on current chat contextual menu
David@0
   931
 */
David@0
   932
- (IBAction)closeContextualChat:(id)sender
David@0
   933
{
David@100
   934
	[self closeChat:[adium.menuController currentContextMenuChat]];
David@0
   935
}
David@0
   936
David@0
   937
/*!
David@0
   938
 * @brief Loop through open chats and close them
David@0
   939
 */
David@0
   940
- (IBAction)closeAllChats:(id)sender
David@0
   941
{
David@426
   942
	for (AIChat *chatToClose in [[interfacePlugin.openChats copy] autorelease]) {
David@0
   943
		[self closeChat:chatToClose];
David@0
   944
	}
David@0
   945
}
David@0
   946
David@0
   947
/*!
David@0
   948
 * @brief Updates the key equivalents on 'close' and 'close chat' (dynamically changed to make cmd-w less destructive)
David@0
   949
 */
David@0
   950
- (void)updateCloseMenuKeys
David@0
   951
{
David@0
   952
	if (activeChat && !closeMenuConfiguredForChat) {
David@0
   953
        [menuItem_close setKeyEquivalent:@"W"];
David@0
   954
        [menuItem_closeChat setKeyEquivalent:@"w"];
David@0
   955
		closeMenuConfiguredForChat = YES;
David@0
   956
	} else if (!activeChat && closeMenuConfiguredForChat) {
David@0
   957
        [menuItem_close setKeyEquivalent:@"w"];
David@0
   958
		[menuItem_closeChat removeKeyEquivalent];		
David@0
   959
		closeMenuConfiguredForChat = NO;
David@0
   960
	}
David@0
   961
}
David@0
   962
David@0
   963
David@0
   964
//Window Menu ----------------------------------------------------------------------------------------------------------
David@0
   965
#pragma mark Window Menu
David@0
   966
David@0
   967
/*!
zacw@1260
   968
 * @brief Open the authorization requests window.
zacw@1260
   969
 */
zacw@1260
   970
- (void)openAuthorizationWindow:(id)sender
zacw@1260
   971
{
zacw@1260
   972
	[[AIAuthorizationRequestsWindowController sharedController] showWindow:nil];
zacw@1260
   973
}
zacw@1260
   974
zacw@1260
   975
/*!
David@0
   976
 * @brief Make a chat window active
David@0
   977
 * 
David@0
   978
 * Invoked by a selection in the window menu
David@0
   979
 */
David@0
   980
- (IBAction)showChatWindow:(id)sender
David@0
   981
{
David@0
   982
	[self setActiveChat:[sender representedObject]];
David@0
   983
    [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
David@0
   984
}
David@0
   985
David@0
   986
/*!
David@0
   987
 * @brief Updates the 'check' icon so it's next to the active window
David@0
   988
 */
David@0
   989
- (void)updateActiveWindowMenuItem
David@0
   990
{
David@0
   991
    NSMenuItem		*item;
David@0
   992
David@76
   993
    for (item in windowMenuArray) {
David@0
   994
		if ([item representedObject]) [item setState:([item representedObject] == activeChat ? NSOnState : NSOffState)];
David@0
   995
    }
David@0
   996
}
David@0
   997
David@0
   998
/*!
David@0
   999
 * @brief Builds the window menu
David@0
  1000
 * 
David@0
  1001
 * This function gets called whenever chats are opened, closed, or re-ordered - so improvements and optimizations here
David@0
  1002
 * would probably be helpful
David@0
  1003
 */
David@0
  1004
- (void)buildWindowMenu
David@0
  1005
{	
David@0
  1006
    NSMenuItem				*item;
David@3
  1007
    NSInteger						windowKey = 1;
David@0
  1008
	
David@0
  1009
    //Remove any existing menus
David@76
  1010
    for (item in windowMenuArray) {
David@100
  1011
        [adium.menuController removeMenuItem:item];
David@0
  1012
    }
David@0
  1013
    [windowMenuArray release]; windowMenuArray = [[NSMutableArray alloc] init];
David@0
  1014
	
Evan@166
  1015
    //Messages window and any open messasges	
Evan@166
  1016
	for (NSDictionary *containerDict in [interfacePlugin openContainersAndChats]) {
David@0
  1017
		NSString		*containerName = [containerDict objectForKey:@"Name"];
David@0
  1018
		NSArray			*contentArray = [containerDict objectForKey:@"Content"];
David@0
  1019
		
David@0
  1020
		//Add a menu item for the container
Evan@166
  1021
		if (contentArray.count > 1) {
David@0
  1022
			item = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:([containerName length] ? containerName : AILocalizedString(@"Chats", nil))
David@0
  1023
																		target:nil
David@0
  1024
																		action:nil
David@0
  1025
																 keyEquivalent:@""];
David@0
  1026
			[self _addItemToMainMenuAndDock:item];
David@0
  1027
			[item release];
David@0
  1028
		}
David@0
  1029
		
David@0
  1030
		//Add items for the chats it contains
Evan@166
  1031
		for (AIChat *chat in [contentArray objectEnumerator]) {
David@0
  1032
			NSString		*windowKeyString;
David@0
  1033
			
David@0
  1034
			//Prepare a key equivalent for the controller
David@0
  1035
			if (windowKey < 10) {
Evan@166
  1036
				windowKeyString = [NSString stringWithFormat:@"%ld", (windowKey)];
David@0
  1037
			} else if (windowKey == 10) {
David@0
  1038
				windowKeyString = [NSString stringWithString:@"0"];
David@0
  1039
			} else {
David@0
  1040
				windowKeyString = [NSString stringWithString:@""];
David@0
  1041
			}
David@0
  1042
			
Evan@166
  1043
			item = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:chat.displayName
David@0
  1044
																		target:self
David@0
  1045
																		action:@selector(showChatWindow:)
David@0
  1046
																 keyEquivalent:windowKeyString];
David@0
  1047
			if ([contentArray count] > 1) [item setIndentationLevel:1];
David@0
  1048
			[item setRepresentedObject:chat];
Evan@166
  1049
			[item setImage:chat.chatMenuImage];
David@0
  1050
			[self _addItemToMainMenuAndDock:item];
David@0
  1051
			[item release];
David@0
  1052
David@0
  1053
			windowKey++;
David@0
  1054
		}
David@0
  1055
	}
David@0
  1056
David@0
  1057
	[self updateActiveWindowMenuItem];
David@0
  1058
}
David@0
  1059
David@0
  1060
/*!
David@0
  1061
 * brief Adds a menu item to the internal array, dock menu, and main menu
David@0
  1062
 *
David@0
  1063
 * Should be used for adding a new window to the window menu (and dock menu)
David@0
  1064
 */
David@0
  1065
- (void)_addItemToMainMenuAndDock:(NSMenuItem *)item
David@0
  1066
{
David@0
  1067
	//Add to main menu first
David@100
  1068
	[adium.menuController addMenuItem:item toLocation:LOC_Window_Fixed];
David@0
  1069
	[windowMenuArray addObject:item];
David@0
  1070
	
David@0
  1071
	//Make a copy, and add to the dock
David@0
  1072
	item = [item copy];
David@0
  1073
	[item setKeyEquivalent:@""];
David@100
  1074
	[adium.menuController addMenuItem:item toLocation:LOC_Dock_Status];
David@0
  1075
	[windowMenuArray addObject:item];
David@0
  1076
	[item release];
David@0
  1077
}
David@0
  1078
David@0
  1079
David@0
  1080
//Chat Cycling ---------------------------------------------------------------------------------------------------------
David@0
  1081
#pragma mark Chat Cycling
David@0
  1082
David@0
  1083
/*!
David@0
  1084
 * @brief Cycles to the next active chat
David@0
  1085
 */
David@0
  1086
- (void)nextChat:(id)sender
David@0
  1087
{
David@0
  1088
	NSArray	*openChats = [self openChats];
David@0
  1089
David@0
  1090
	if ([openChats count]) {
David@0
  1091
		if (activeChat) {
David@3
  1092
			NSInteger chatIndex = [openChats indexOfObject:activeChat]+1;
David@0
  1093
			[self setActiveChat:[openChats objectAtIndex:(chatIndex < [openChats count] ? chatIndex : 0)]];
David@0
  1094
		} else {
David@0
  1095
			[self setActiveChat:[openChats objectAtIndex:0]];
David@0
  1096
		}
David@0
  1097
	}
David@0
  1098
}
David@0
  1099
David@0
  1100
/*!
David@0
  1101
 * @brief Cycles to the previus active chat
David@0
  1102
 */
David@0
  1103
- (void)previousChat:(id)sender
David@0
  1104
{
David@0
  1105
	NSArray	*openChats = [self openChats];
David@0
  1106
	
David@0
  1107
	if ([openChats count]) {
David@0
  1108
		if (activeChat) {
David@3
  1109
			NSInteger chatIndex = [openChats indexOfObject:activeChat]-1;
David@0
  1110
			[self setActiveChat:[openChats objectAtIndex:(chatIndex >= 0 ? chatIndex : [openChats count]-1)]];
David@0
  1111
		} else {
David@0
  1112
			[self setActiveChat:[openChats lastObject]];
David@0
  1113
		}
David@0
  1114
	}
David@0
  1115
}
David@0
  1116
David@0
  1117
//Selected contact ------------------------------------------------
David@0
  1118
#pragma mark Selected contact
David@0
  1119
- (id)_performSelectorOnFirstAvailableResponder:(SEL)selector
David@0
  1120
{
David@0
  1121
    NSResponder	*responder = [[[NSApplication sharedApplication] mainWindow] firstResponder];
David@0
  1122
    //Check the first responder
David@0
  1123
    if ([responder respondsToSelector:selector]) {
David@0
  1124
        return [responder performSelector:selector];
David@0
  1125
    }
David@0
  1126
	
David@0
  1127
    //Search the responder chain
David@0
  1128
    do{
David@0
  1129
        responder = [responder nextResponder];
David@0
  1130
        if ([responder respondsToSelector:selector]) {
David@0
  1131
            return [responder performSelector:selector];
David@0
  1132
        }
David@0
  1133
		
David@0
  1134
    } while (responder != nil);
David@0
  1135
	
David@0
  1136
    //None found, return nil
David@0
  1137
    return nil;
David@0
  1138
}
David@0
  1139
- (id)_performSelectorOnFirstAvailableResponder:(SEL)selector conformingToProtocol:(Protocol *)protocol
David@0
  1140
{
David@0
  1141
	NSResponder *responder = [[[NSApplication sharedApplication] mainWindow] firstResponder];
David@0
  1142
	//Check the first responder
David@0
  1143
	if ([responder conformsToProtocol:protocol] && [responder respondsToSelector:selector]) {
David@0
  1144
		return [responder performSelector:selector];
David@0
  1145
	}
David@0
  1146
	
David@0
  1147
    //Search the responder chain
David@0
  1148
    do{
David@0
  1149
        responder = [responder nextResponder];
David@0
  1150
        if ([responder conformsToProtocol:protocol] && [responder respondsToSelector:selector]) {
David@0
  1151
            return [responder performSelector:selector];
David@0
  1152
        }
David@0
  1153
		
David@0
  1154
    } while (responder != nil);
David@0
  1155
	
David@0
  1156
    //None found, return nil
David@0
  1157
    return nil;
David@0
  1158
}
David@0
  1159
David@0
  1160
/*!
David@0
  1161
 * @returns The "selected"(represented) contact (By finding the first responder that returns a contact)
David@0
  1162
 * If no listObject is found, try to find a list object selected in a group chat
David@0
  1163
 */
David@0
  1164
- (AIListObject *)selectedListObject
David@0
  1165
{
David@0
  1166
	AIListObject *listObject = [self _performSelectorOnFirstAvailableResponder:@selector(listObject)];
David@0
  1167
	if ( !listObject) {
David@0
  1168
		listObject = [self _performSelectorOnFirstAvailableResponder:@selector(preferredListObject)];
David@0
  1169
	}
David@0
  1170
	return listObject;
David@0
  1171
}
David@0
  1172
David@0
  1173
- (AIListObject *)selectedListObjectInContactList
David@0
  1174
{
David@0
  1175
	return [self _performSelectorOnFirstAvailableResponder:@selector(listObject) conformingToProtocol:@protocol(ContactListOutlineView)];
David@0
  1176
}
David@0
  1177
- (NSArray *)arrayOfSelectedListObjectsInContactList
David@0
  1178
{
David@0
  1179
	return [self _performSelectorOnFirstAvailableResponder:@selector(arrayOfListObjects) conformingToProtocol:@protocol(ContactListOutlineView)];
David@0
  1180
}
zacw@2131
  1181
- (NSArray *)arrayOfSelectedListObjectsWithGroupsInContactList
zacw@2131
  1182
{
zacw@2131
  1183
	return [self _performSelectorOnFirstAvailableResponder:@selector(arrayOfListObjectsWithGroups) conformingToProtocol:@protocol(ContactListOutlineView)];
zacw@2131
  1184
}
David@0
  1185
David@0
  1186
//Message View ---------------------------------------------------------------------------------------------------------
David@0
  1187
//Message view is abstracted from the containing interface, since they're not directly related to eachother
David@0
  1188
#pragma mark Message View
David@0
  1189
//Registers a view to handle the contact list
David@0
  1190
- (void)registerMessageDisplayPlugin:(id <AIMessageDisplayPlugin>)inPlugin
David@0
  1191
{
David@0
  1192
    [messageViewArray addObject:inPlugin];
David@0
  1193
}
David@0
  1194
- (void)unregisterMessageDisplayPlugin:(id <AIMessageDisplayPlugin>)inPlugin
David@0
  1195
{
David@0
  1196
    [messageViewArray removeObject:inPlugin];
David@0
  1197
}
David@0
  1198
- (id <AIMessageDisplayController>)messageDisplayControllerForChat:(AIChat *)inChat
David@0
  1199
{
David@0
  1200
	//Sometimes our users find it amusing to disable plugins that are located within the Adium bundle.  This error
David@0
  1201
	//trap prevents us from crashing if they happen to disable all the available message view plugins.
David@0
  1202
	//PUT THAT PLUGIN BACK IT WAS IMPORTANT!
David@0
  1203
	if ([messageViewArray count] == 0) {
David@0
  1204
		NSRunCriticalAlertPanel(@"No Message View Plugin Installed",
David@0
  1205
								@"Adium cannot find its message view plugin. Please re-install.  If you've manually disabled Adium's message view plugin, please re-enable it.",
David@0
  1206
								@"Quit",
David@0
  1207
								nil,
David@0
  1208
								nil);
David@0
  1209
		[NSApp terminate:nil];
David@0
  1210
	}
David@0
  1211
	
David@0
  1212
	return [[messageViewArray objectAtIndex:0] messageDisplayControllerForChat:inChat];
David@0
  1213
}
David@0
  1214
David@0
  1215
David@0
  1216
//Error Display --------------------------------------------------------------------------------------------------------
David@0
  1217
#pragma mark Error Display
David@0
  1218
- (void)handleErrorMessage:(NSString *)inTitle withDescription:(NSString *)inDesc
David@0
  1219
{
David@0
  1220
    [self handleMessage:inTitle withDescription:inDesc withWindowTitle:ERROR_MESSAGE_WINDOW_TITLE];
David@0
  1221
}
David@0
  1222
David@0
  1223
- (void)handleMessage:(NSString *)inTitle withDescription:(NSString *)inDesc withWindowTitle:(NSString *)inWindowTitle;
David@0
  1224
{
David@0
  1225
    NSDictionary	*errorDict;
David@0
  1226
    
David@0
  1227
    //Post a notification that an error was recieved
David@0
  1228
    errorDict = [NSDictionary dictionaryWithObjectsAndKeys:inTitle,@"Title",inDesc,@"Description",inWindowTitle,@"Window Title",nil];
David@1109
  1229
    [[NSNotificationCenter defaultCenter] postNotificationName:Interface_ShouldDisplayErrorMessage object:nil userInfo:errorDict];
David@0
  1230
}
David@0
  1231
David@0
  1232
//Display then clear the last disconnection error
David@0
  1233
- (void)account:(AIAccount *)inAccount disconnectedWithError:(NSString *)disconnectionError
David@0
  1234
{
zacw@1370
  1235
David@0
  1236
}
David@0
  1237
David@0
  1238
//Question Display -----------------------------------------------------------------------------------------------------
David@0
  1239
#pragma mark Question Display
David@0
  1240
- (void)displayQuestion:(NSString *)inTitle withAttributedDescription:(NSAttributedString *)inDesc withWindowTitle:(NSString *)inWindowTitle
wixardy@2118
  1241
		  defaultButton:(NSString *)inDefaultButton alternateButton:(NSString *)inAlternateButton otherButton:(NSString *)inOtherButton suppression:(NSString *)inSuppression
David@0
  1242
				 target:(id)inTarget selector:(SEL)inSelector userInfo:(id)inUserInfo
David@0
  1243
{
David@0
  1244
	NSMutableDictionary *questionDict = [NSMutableDictionary dictionary];
David@0
  1245
	
David@0
  1246
	if(inTitle != nil)
David@0
  1247
		[questionDict setObject:inTitle forKey:@"Title"];
David@0
  1248
	if(inDesc != nil)
David@0
  1249
		[questionDict setObject:inDesc forKey:@"Description"];
David@0
  1250
	if(inWindowTitle != nil)
David@0
  1251
		[questionDict setObject:inWindowTitle forKey:@"Window Title"];
David@0
  1252
	if(inDefaultButton != nil)
David@0
  1253
		[questionDict setObject:inDefaultButton forKey:@"Default Button"];
David@0
  1254
	if(inAlternateButton != nil)
David@0
  1255
		[questionDict setObject:inAlternateButton forKey:@"Alternate Button"];
David@0
  1256
	if(inOtherButton != nil)
David@0
  1257
		[questionDict setObject:inOtherButton forKey:@"Other Button"];
wixardy@2118
  1258
	if(inSuppression != nil)
wixardy@2118
  1259
		[questionDict setObject:inSuppression forKey:@"Suppression Checkbox"];
David@0
  1260
	if(inTarget != nil)
David@0
  1261
		[questionDict setObject:inTarget forKey:@"Target"];
David@0
  1262
	if(inSelector != NULL)
David@0
  1263
		[questionDict setObject:NSStringFromSelector(inSelector) forKey:@"Selector"];
David@0
  1264
	if(inUserInfo != nil)
David@0
  1265
		[questionDict setObject:inUserInfo forKey:@"Userinfo"];
David@0
  1266
	
David@1109
  1267
	[[NSNotificationCenter defaultCenter] postNotificationName:Interface_ShouldDisplayQuestion object:nil userInfo:questionDict];
David@0
  1268
}
David@0
  1269
David@0
  1270
- (void)displayQuestion:(NSString *)inTitle withDescription:(NSString *)inDesc withWindowTitle:(NSString *)inWindowTitle
wixardy@2118
  1271
		  defaultButton:(NSString *)inDefaultButton alternateButton:(NSString *)inAlternateButton otherButton:(NSString *)inOtherButton suppression:(NSString *)inSuppression
David@0
  1272
				 target:(id)inTarget selector:(SEL)inSelector userInfo:(id)inUserInfo
David@0
  1273
{
David@0
  1274
	[self displayQuestion:inTitle
David@0
  1275
withAttributedDescription:[[[NSAttributedString alloc] initWithString:inDesc
David@0
  1276
														   attributes:[NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:0]
David@0
  1277
																								  forKey:NSFontAttributeName]] autorelease]
David@0
  1278
		  withWindowTitle:inWindowTitle
David@0
  1279
			defaultButton:inDefaultButton
David@0
  1280
		  alternateButton:inAlternateButton
David@0
  1281
			  otherButton:inOtherButton
wixardy@2118
  1282
			  suppression:inSuppression
David@0
  1283
				   target:inTarget
David@0
  1284
				 selector:inSelector
David@0
  1285
				 userInfo:inUserInfo];
David@0
  1286
}
David@0
  1287
//Synchronized Flashing ------------------------------------------------------------------------------------------------
David@0
  1288
#pragma mark Synchronized Flashing
David@0
  1289
//Register to observe the synchronized flashing
David@0
  1290
- (void)registerFlashObserver:(id <AIFlashObserver>)inObserver
David@0
  1291
{
David@0
  1292
    //Setup the timer if we don't have one yet
David@0
  1293
    if (!flashObserverArray) {
David@0
  1294
        flashObserverArray = [[NSMutableArray alloc] init];
David@0
  1295
        flashTimer = [[NSTimer scheduledTimerWithTimeInterval:(1.0/2.0) 
David@0
  1296
                                                       target:self 
David@0
  1297
                                                     selector:@selector(flashTimer:) 
David@0
  1298
                                                     userInfo:nil
David@0
  1299
                                                      repeats:YES] retain];
David@0
  1300
    }
David@0
  1301
    
David@0
  1302
    //Add the new observer to the array
David@0
  1303
    [flashObserverArray addObject:inObserver];
David@0
  1304
}
David@0
  1305
David@0
  1306
//Unregister from observing flashing
David@0
  1307
- (void)unregisterFlashObserver:(id <AIFlashObserver>)inObserver
David@0
  1308
{
David@0
  1309
    //Remove the observer from our array
David@0
  1310
    [flashObserverArray removeObject:inObserver];
David@0
  1311
    
David@0
  1312
    //Release the observer array and uninstall the timer
David@0
  1313
    if ([flashObserverArray count] == 0) {
David@0
  1314
        [flashObserverArray release]; flashObserverArray = nil;
David@0
  1315
        [flashTimer invalidate];
David@0
  1316
        [flashTimer release]; flashTimer = nil;
David@0
  1317
    }
David@0
  1318
}
David@0
  1319
David@0
  1320
//Timer, invoke a flash
David@0
  1321
- (void)flashTimer:(NSTimer *)inTimer
David@0
  1322
{
David@79
  1323
	flashState++;
David@79
  1324
David@81
  1325
	for (id<AIFlashObserver>observer in [[flashObserverArray copy] autorelease]) {
David@79
  1326
		[observer flash:flashState];
David@79
  1327
	}
David@0
  1328
}
David@0
  1329
David@0
  1330
//Current state of flashing.  This is an integer the increases by 1 with every flash.  Mod to whatever range is desired
David@0
  1331
- (int)flashState
David@0
  1332
{
David@0
  1333
    return flashState;
David@0
  1334
}
David@0
  1335
David@0
  1336
David@0
  1337
//Tooltips -------------------------------------------------------------------------------------------------------------
David@0
  1338
#pragma mark Tooltips
David@0
  1339
//Registers code to display tooltip info about a contact
David@0
  1340
- (void)registerContactListTooltipEntry:(id <AIContactListTooltipEntry>)inEntry secondaryEntry:(BOOL)isSecondary
David@0
  1341
{
David@0
  1342
    if (isSecondary)
David@0
  1343
        [contactListTooltipSecondaryEntryArray addObject:inEntry];
David@0
  1344
    else
David@0
  1345
        [contactListTooltipEntryArray addObject:inEntry];
David@0
  1346
}
David@0
  1347
David@0
  1348
//Unregisters code to display tooltip info about a contact
David@0
  1349
- (void)unregisterContactListTooltipEntry:(id <AIContactListTooltipEntry>)inEntry secondaryEntry:(BOOL)isSecondary
David@0
  1350
{
David@0
  1351
    if (isSecondary)
David@0
  1352
        [contactListTooltipSecondaryEntryArray removeObject:inEntry];
David@0
  1353
    else
David@0
  1354
        [contactListTooltipEntryArray removeObject:inEntry];
David@0
  1355
}
David@0
  1356
David@0
  1357
- (NSArray *)contactListTooltipPrimaryEntries
David@0
  1358
{
David@0
  1359
	return contactListTooltipEntryArray;
David@0
  1360
}
David@0
  1361
David@0
  1362
- (NSArray *)contactListTooltipSecondaryEntries
David@0
  1363
{
David@0
  1364
	return contactListTooltipSecondaryEntryArray;
David@0
  1365
}
David@0
  1366
David@0
  1367
//list object tooltips
David@0
  1368
- (void)showTooltipForListObject:(AIListObject *)object atScreenPoint:(NSPoint)point onWindow:(NSWindow *)inWindow 
David@0
  1369
{
David@0
  1370
    if (object) {
David@0
  1371
        if (object == tooltipListObject) { //If we already have this tooltip open
David@0
  1372
                                         //Move the existing tooltip
David@0
  1373
            [AITooltipUtilities showTooltipWithTitle:tooltipTitle
David@0
  1374
												body:tooltipBody
David@0
  1375
											   image:tooltipImage 
David@0
  1376
										imageOnRight:DISPLAY_IMAGE_ON_RIGHT 
David@0
  1377
											onWindow:inWindow
David@0
  1378
											 atPoint:point 
David@0
  1379
										 orientation:TooltipBelow];
David@0
  1380
            
David@0
  1381
        } else { //This is a new tooltip
David@0
  1382
            NSArray                     *tabArray;
David@0
  1383
            NSMutableParagraphStyle     *paragraphStyleTitle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
David@0
  1384
            NSMutableParagraphStyle     *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
David@0
  1385
            
David@0
  1386
            //Hold onto the new object
David@0
  1387
            [tooltipListObject release]; tooltipListObject = [object retain];
David@0
  1388
            
David@0
  1389
            //Buddy Icon
David@0
  1390
            [tooltipImage release];
David@0
  1391
			tooltipImage = [[tooltipListObject userIcon] retain];
David@0
  1392
			if (!tooltipImage) tooltipImage = [[AIServiceIcons serviceIconForObject:tooltipListObject
David@0
  1393
																			 type:AIServiceIconLarge
David@0
  1394
																		direction:AIIconNormal] retain];
David@0
  1395
            
David@0
  1396
            //Reset the maxLabelWidth for the tooltip generation
David@0
  1397
            maxLabelWidth = 0;
David@0
  1398
            
David@0
  1399
            //Build a tooltip string for the primary information
David@0
  1400
            [tooltipTitle release]; tooltipTitle = [[self _tooltipTitleForObject:object] retain];
David@0
  1401
            
David@0
  1402
            //If there is an image, set the title tab and indentation settings independently
David@0
  1403
            if (tooltipImage) {
David@0
  1404
                //Set a right-align tab at the maximum label width and a left-align just past it
David@0
  1405
                tabArray = [[NSArray alloc] initWithObjects:[[[NSTextTab alloc] initWithType:NSRightTabStopType 
David@0
  1406
																					location:maxLabelWidth] autorelease]
David@0
  1407
                                                            ,[[[NSTextTab alloc] initWithType:NSLeftTabStopType 
David@0
  1408
                                                                                   location:maxLabelWidth + LABEL_ENTRY_SPACING] autorelease]
David@0
  1409
                                                            ,nil];
David@0
  1410
                
David@0
  1411
                [paragraphStyleTitle setTabStops:tabArray];
David@0
  1412
                [tabArray release];
David@0
  1413
                tabArray = nil;
David@0
  1414
                [paragraphStyleTitle setHeadIndent:(maxLabelWidth + LABEL_ENTRY_SPACING)];
David@0
  1415
                
David@0
  1416
                [tooltipTitle addAttribute:NSParagraphStyleAttributeName 
David@0
  1417
                                     value:paragraphStyleTitle
David@0
  1418
                                     range:NSMakeRange(0,[tooltipTitle length])];
David@0
  1419
                
David@0
  1420
                //Reset the max label width since the body will be independent
David@0
  1421
                maxLabelWidth = 0;
David@0
  1422
            }
David@0
  1423
            
David@0
  1424
            //Build a tooltip string for the secondary information
David@0
  1425
            [tooltipBody release]; tooltipBody = nil;
David@0
  1426
            tooltipBody = [[self _tooltipBodyForObject:object] retain];
David@0
  1427
            
David@0
  1428
            //Set a right-align tab at the maximum label width for the body and a left-align just past it
David@0
  1429
            tabArray = [[NSArray alloc] initWithObjects:[[[NSTextTab alloc] initWithType:NSRightTabStopType 
David@0
  1430
                                                                                 location:maxLabelWidth] autorelease]
David@0
  1431
                                                        ,[[[NSTextTab alloc] initWithType:NSLeftTabStopType 
David@0
  1432
                                                                                location:maxLabelWidth + LABEL_ENTRY_SPACING] autorelease]
David@0
  1433
                                                        ,nil];
David@0
  1434
            [paragraphStyle setTabStops:tabArray];
David@0
  1435
            [tabArray release];
David@0
  1436
            [paragraphStyle setHeadIndent:(maxLabelWidth + LABEL_ENTRY_SPACING)];
David@0
  1437
            
David@0
  1438
            [tooltipBody addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0,[tooltipBody length])];
David@0
  1439
            //If there is no image, also use these settings for the top part
David@0
  1440
            if (!tooltipImage) {
David@0
  1441
                [tooltipTitle addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0,[tooltipTitle length])];
David@0
  1442
            }
David@0
  1443
            
David@0
  1444
            //Display the new tooltip
David@0
  1445
            [AITooltipUtilities showTooltipWithTitle:tooltipTitle
David@0
  1446
                                                body:tooltipBody 
David@0
  1447
                                               image:tooltipImage
David@0
  1448
                                        imageOnRight:DISPLAY_IMAGE_ON_RIGHT
David@0
  1449
                                            onWindow:inWindow
David@0
  1450
                                             atPoint:point 
David@0
  1451
                                         orientation:TooltipBelow];
David@0
  1452
			
David@0
  1453
			[paragraphStyleTitle release];
David@0
  1454
			[paragraphStyle release];
David@0
  1455
        }
David@0
  1456
        
David@0
  1457
    } else {
David@0
  1458
        //Hide the existing tooltip
David@0
  1459
        if (tooltipListObject) {
David@0
  1460
            [AITooltipUtilities showTooltipWithTitle:nil 
David@0
  1461
                                                body:nil
David@0
  1462
                                               image:nil 
David@0
  1463
                                            onWindow:nil
David@0
  1464
                                             atPoint:point
David@0
  1465
                                         orientation:TooltipBelow];
David@0
  1466
            [tooltipListObject release]; tooltipListObject = nil;
David@0
  1467
			
David@0
  1468
			[tooltipTitle release]; tooltipTitle = nil;
David@0
  1469
			[tooltipBody release]; tooltipBody = nil;
David@0
  1470
			[tooltipImage release]; tooltipImage = nil;
David@0
  1471
        }
David@0
  1472
    }
David@0
  1473
}
David@0
  1474
David@0
  1475
- (NSAttributedString *)_tooltipTitleForObject:(AIListObject *)object
David@0
  1476
{
David@0
  1477
    NSMutableAttributedString           *titleString = [[NSMutableAttributedString alloc] init];
David@0
  1478
    
David@0
  1479
    id <AIContactListTooltipEntry>		tooltipEntry;
David@0
  1480
    NSEnumerator                        *labelEnumerator;
David@0
  1481
    NSMutableArray                      *labelArray = [NSMutableArray array];
David@0
  1482
    NSMutableArray                      *entryArray = [NSMutableArray array];
David@0
  1483
    NSMutableAttributedString           *entryString;
David@3
  1484
    CGFloat                               labelWidth;
David@0
  1485
    BOOL                                isFirst = YES;
David@0
  1486
    
David@837
  1487
    NSString                            *formattedUID = object.formattedUID;
David@0
  1488
    
David@0
  1489
    //Configure fonts and attributes
David@0
  1490
    NSFontManager                       *fontManager = [NSFontManager sharedFontManager];
David@0
  1491
    NSFont                              *toolTipsFont = [NSFont toolTipsFontOfSize:10];
David@0
  1492
    NSMutableDictionary                 *titleDict = [NSMutableDictionary dictionaryWithObject:[fontManager convertFont:[NSFont toolTipsFontOfSize:12] toHaveTrait:NSBoldFontMask]
David@0
  1493
	                                                                                    forKey:NSFontAttributeName];
David@0
  1494
    NSMutableDictionary                 *labelDict = [NSMutableDictionary dictionaryWithObject:[fontManager convertFont:[NSFont toolTipsFontOfSize:9] toHaveTrait:NSBoldFontMask]
David@0
  1495
	                                                                                    forKey:NSFontAttributeName];
David@0
  1496
    NSMutableDictionary                 *labelEndLineDict = [NSMutableDictionary dictionaryWithObject:[NSFont toolTipsFontOfSize:2]
David@0
  1497
	                                                                                           forKey:NSFontAttributeName];
David@0
  1498
    NSMutableDictionary                 *entryDict = [NSMutableDictionary dictionaryWithObject:toolTipsFont
David@0
  1499
	                                                                                    forKey:NSFontAttributeName];
David@0
  1500
	
David@0
  1501
	//Get the user's display name as an attributed string
David@837
  1502
    NSAttributedString                  *displayName = [[NSAttributedString alloc] initWithString:object.displayName
David@0
  1503
																					   attributes:titleDict];
David@95
  1504
	NSAttributedString					*filteredDisplayName = [adium.contentController filterAttributedString:displayName
David@0
  1505
																								 usingFilterType:AIFilterTooltips
David@0
  1506
																									   direction:AIFilterIncoming
David@0
  1507
																										 context:nil];
David@0
  1508
	
David@0
  1509
	//Append the user's display name
David@0
  1510
	if (filteredDisplayName) {
David@0
  1511
		[titleString appendAttributedString:filteredDisplayName];
David@0
  1512
	}
David@0
  1513
	
David@0
  1514
	//Append the user's formatted UID if there is one that's different to the display name
David@0
  1515
	if (formattedUID && (!([[[displayName string] compactedString] isEqualToString:[formattedUID compactedString]]))) {
David@0
  1516
		[titleString appendString:[NSString stringWithFormat:@" (%@)", formattedUID] withAttributes:titleDict];
David@0
  1517
	}
David@0
  1518
	[displayName release];
David@0
  1519
    
David@0
  1520
    if ([object isKindOfClass:[AIListContact class]]) {
David@0
  1521
		if ((![object isKindOfClass:[AIMetaContact class]] || [(AIMetaContact *)object containsOnlyOneService]) &&
David@0
  1522
			[object userIcon]) {
David@0
  1523
			NSImage *serviceIcon = [[AIServiceIcons serviceIconForObject:object type:AIServiceIconSmall direction:AIIconNormal]
David@0
  1524
									imageByScalingToSize:NSMakeSize(14,14)];
David@0
  1525
			if (serviceIcon) {
David@0
  1526
				NSTextAttachment		*attachment;
David@0
  1527
				NSTextAttachmentCell	*cell;
David@0
  1528
				
David@0
  1529
				cell = [[NSTextAttachmentCell alloc] init];
David@0
  1530
				[cell setImage:serviceIcon];
David@0
  1531
				
David@0
  1532
				attachment = [[NSTextAttachment alloc] init];
David@0
  1533
				[attachment setAttachmentCell:cell];
David@0
  1534
				[cell release];
David@0
  1535
	
David@0
  1536
				[titleString appendString:@" " withAttributes:nil];
David@0
  1537
				[titleString appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
David@0
  1538
				[attachment release];
David@0
  1539
			}
David@0
  1540
		}
David@0
  1541
	}
David@0
  1542
		
David@0
  1543
    if ([object isKindOfClass:[AIListGroup class]]) {
Peter@1061
  1544
        [titleString appendString:[NSString stringWithFormat:@" (%ld/%ld)",[(AIListGroup *)object visibleCount],[(AIListGroup *)object countOfContainedObjects]] 
David@0
  1545
                   withAttributes:titleDict];
David@0
  1546
    }
David@0
  1547
    
David@0
  1548
    //Entries from plugins
David@0
  1549
    
David@0
  1550
    //Calculate the widest label while loading the arrays
David@0
  1551
    
David@76
  1552
    for (tooltipEntry in contactListTooltipEntryArray) {
David@0
  1553
        
David@0
  1554
        entryString = [[tooltipEntry entryForObject:object] mutableCopy];
David@0
  1555
        if (entryString && [entryString length]) {
David@0
  1556
            
David@0
  1557
            NSString        *labelString = [tooltipEntry labelForObject:object];
David@0
  1558
            if (labelString && [labelString length]) {
David@0
  1559
                
David@0
  1560
                [entryArray addObject:entryString];
David@0
  1561
                [labelArray addObject:labelString];
David@0
  1562
                
David@0
  1563
                NSAttributedString * labelAttribString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@:",labelString] 
David@0
  1564
																						 attributes:labelDict];
David@0
  1565
                
David@0
  1566
                //The largest size should be the label's size plus the distance to the next tab at least a space past its end
David@0
  1567
                labelWidth = [labelAttribString size].width;
David@0
  1568
                [labelAttribString release];
David@0
  1569
                
David@0
  1570
                if (labelWidth > maxLabelWidth)
David@0
  1571
                    maxLabelWidth = labelWidth;
David@0
  1572
            }
David@0
  1573
        }
David@0
  1574
        [entryString release];
David@0
  1575
    }
David@0
  1576
    
David@0
  1577
    //Add labels plus entires to the toolTip
David@0
  1578
    labelEnumerator = [labelArray objectEnumerator];
David@0
  1579
    
David@76
  1580
    for (entryString in entryArray) {        
David@0
  1581
        NSAttributedString * labelAttribString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"\t%@:\t",[labelEnumerator nextObject]]
David@0
  1582
																				 attributes:labelDict];
David@0
  1583
        
David@0
  1584
        //Add a carriage return
David@0
  1585
        [titleString appendString:@"\n" withAttributes:labelEndLineDict];
David@0
  1586
        
David@0
  1587
        if (isFirst) {
David@0
  1588
            //skip a line
David@0
  1589
            [titleString appendString:@"\n" withAttributes:labelEndLineDict];
David@0
  1590
            isFirst = NO;
David@0
  1591
        }
David@0
  1592
        
David@0
  1593
        //Add the label (with its spacing)
David@0
  1594
        [titleString appendAttributedString:labelAttribString];
David@0
  1595
		[labelAttribString release];
David@0
  1596
David@0
  1597
		[entryString addAttributes:entryDict range:NSMakeRange(0,[entryString length])];
David@0
  1598
        [titleString appendAttributedString:entryString];
David@0
  1599
    }
David@0
  1600
David@0
  1601
    return [titleString autorelease];
David@0
  1602
}
David@0
  1603
David@0
  1604
- (NSAttributedString *)_tooltipBodyForObject:(AIListObject *)object
David@0
  1605
{
David@0
  1606
    NSMutableAttributedString       *tipString = [[NSMutableAttributedString alloc] init];
David@0
  1607
    
David@0
  1608
    //Configure fonts and attributes
David@0
  1609
    NSFontManager                   *fontManager = [NSFontManager sharedFontManager];
David@0
  1610
    NSFont                          *toolTipsFont = [NSFont toolTipsFontOfSize:10];
David@0
  1611
    NSMutableDictionary             *labelDict = [NSMutableDictionary dictionaryWithObject:[fontManager convertFont:[NSFont toolTipsFontOfSize:9] toHaveTrait:NSBoldFontMask]
David@0
  1612
	                                                                                forKey:NSFontAttributeName];
David@0
  1613
    NSMutableDictionary             *labelEndLineDict = [NSMutableDictionary dictionaryWithObject:[NSFont toolTipsFontOfSize:1]
David@0
  1614
	                                                                                       forKey:NSFontAttributeName];
David@0
  1615
    NSMutableDictionary             *entryDict = [NSMutableDictionary dictionaryWithObject:toolTipsFont
David@0
  1616
	                                                                                forKey:NSFontAttributeName];
David@0
  1617
    
David@0
  1618
    //Entries from plugins
David@0
  1619
    NSEnumerator                    *labelEnumerator; 
Evan@166
  1620
    NSMutableArray                  *labelArray = [NSMutableArray array]; //Array of NSStrings
Evan@166
  1621
    NSMutableArray                  *entryArray = [NSMutableArray array]; //Array of NSMutableStrings   
Evan@166
  1622
    CGFloat                         labelWidth;
David@0
  1623
    BOOL                            firstEntry = YES;
David@0
  1624
    
David@0
  1625
    //Calculate the widest label while loading the arrays
Evan@166
  1626
	for (id <AIContactListTooltipEntry>tooltipEntry in contactListTooltipSecondaryEntryArray) {
Evan@166
  1627
		NSMutableAttributedString *entryString = [[tooltipEntry entryForObject:object] mutableCopy];
Evan@166
  1628
		if (entryString && entryString.length) {
David@0
  1629
			NSString        *labelString = [tooltipEntry labelForObject:object];
Evan@166
  1630
Evan@166
  1631
			if (labelString && labelString.length) {
David@0
  1632
				[entryArray addObject:entryString];
David@0
  1633
				[labelArray addObject:labelString];
David@0
  1634
				
Evan@166
  1635
				NSAttributedString *labelAttribString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@:",labelString] 
Evan@166
  1636
																						attributes:labelDict];
David@0
  1637
				
David@0
  1638
				//The largest size should be the label's size plus the distance to the next tab at least a space past its end
Evan@166
  1639
				labelWidth = labelAttribString.size.width;
David@0
  1640
				[labelAttribString release];
David@0
  1641
				
David@0
  1642
				if (labelWidth > maxLabelWidth)
David@0
  1643
					maxLabelWidth = labelWidth;
David@0
  1644
			}
David@0
  1645
		}
David@0
  1646
		[entryString release];
David@0
  1647
	}
David@0
  1648
		
David@0
  1649
    //Add labels plus entires to the toolTip
David@0
  1650
    labelEnumerator = [labelArray objectEnumerator];
Evan@166
  1651
    for (NSMutableAttributedString *entryString in entryArray) {
David@0
  1652
        NSMutableAttributedString *labelString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"\t%@:\t",[labelEnumerator nextObject]]
David@0
  1653
																						attributes:labelDict];
David@0
  1654
        
David@0
  1655
        if (firstEntry) {
David@0
  1656
            firstEntry = NO;
David@0
  1657
        } else {
David@0
  1658
            //Add a carriage return and skip a line
David@0
  1659
            [tipString appendString:@"\n\n" withAttributes:labelEndLineDict];
David@0
  1660
        }
David@0
  1661
        
David@0
  1662
        //Add the label (with its spacing)
David@0
  1663
        [tipString appendAttributedString:labelString];
David@0
  1664
        [labelString release];
David@0
  1665
David@0
  1666
        NSRange fullLength = NSMakeRange(0, [entryString length]);
David@0
  1667
        
David@0
  1668
        //remove any background coloration
David@0
  1669
        [entryString removeAttribute:NSBackgroundColorAttributeName range:fullLength];
David@0
  1670
        
David@0
  1671
        //adjust foreground colors for the tooltip background
David@0
  1672
        [entryString adjustColorsToShowOnBackground:[NSColor colorWithCalibratedRed:1.000 green:1.000 blue:0.800 alpha:1.0]];
David@0
  1673
David@0
  1674
        //headIndent doesn't apply to the first line of a paragraph... so when new lines are in the entry, we need to tab over to the proper location
David@0
  1675
		if ([entryString replaceOccurrencesOfString:@"\r" withString:@"\r\t\t" options:NSLiteralSearch range:fullLength])
David@0
  1676
            fullLength = NSMakeRange(0, [entryString length]);
David@0
  1677
        if ([entryString replaceOccurrencesOfString:@"\n" withString:@"\n\t\t" options:NSLiteralSearch range:fullLength])
David@0
  1678
            fullLength = NSMakeRange(0, [entryString length]);
David@0
  1679
		
David@0
  1680
        //Run the entry through the filters and add it to tipString
David@95
  1681
		entryString = [[adium.contentController filterAttributedString:entryString
David@0
  1682
														 usingFilterType:AIFilterTooltips
David@0
  1683
															   direction:AIFilterIncoming
David@0
  1684
																 context:object] mutableCopy];
David@0
  1685
		
David@0
  1686
		[entryString addAttributes:entryDict range:NSMakeRange(0,[entryString length])];
David@0
  1687
        [tipString appendAttributedString:entryString];
David@0
  1688
		[entryString release];
David@0
  1689
    }
David@0
  1690
David@0
  1691
    return [tipString autorelease];
David@0
  1692
}
David@0
  1693
David@0
  1694
//Custom pasting ----------------------------------------------------------------------------------------------------
David@0
  1695
#pragma mark Custom Pasting
David@0
  1696
//Paste, stripping formatting
David@0
  1697
- (IBAction)paste:(id)sender
David@0
  1698
{
David@0
  1699
	[self _pasteWithPreferredSelector:@selector(pasteAsPlainTextWithTraits:) sender:sender];
David@0
  1700
}
David@0
  1701
David@0
  1702
//Paste with formatting
David@0
  1703
- (IBAction)pasteAndMatchStyle:(id)sender
David@0
  1704
{
David@0
  1705
	[self _pasteWithPreferredSelector:@selector(pasteAsPlainText:) sender:sender];
David@0
  1706
}
David@0
  1707
David@0
  1708
- (IBAction)pasteWithImagesAndColors:(id)sender
David@0
  1709
{
David@0
  1710
	[self _pasteWithPreferredSelector:@selector(pasteAsRichText:) sender:sender];	
David@0
  1711
}
David@0
  1712
David@0
  1713
/*!
David@0
  1714
 * @brief Send a paste message, using preferredSelector if possible and paste: if not
David@0
  1715
 *
David@0
  1716
 * Walks the responder chain looking for a responder which can handle pasting, skipping instances of
David@0
  1717
 * WebHTMLView.  These are skipped because we can control what paste does to WebView (by using a custom subclass) but
David@0
  1718
 * have no control over what the WebHTMLView would do.
David@0
  1719
 *
David@0
  1720
 * If no responder is found, repeats the process looking for the simpler paste: selector.
David@0
  1721
 */
David@0
  1722
- (void)_pasteWithPreferredSelector:(SEL)selector sender:(id)sender
David@0
  1723
{
David@0
  1724
	NSWindow	*keyWindow = [[NSApplication sharedApplication] keyWindow];
David@0
  1725
	NSResponder	*responder;
David@0
  1726
David@0
  1727
	//First, look for a responder which can handle the preferred selector
David@0
  1728
	if (!(responder = [keyWindow earliestResponderWhichRespondsToSelector:selector
David@0
  1729
														  andIsNotOfClass:NSClassFromString(@"WebHTMLView")])) {		
David@0
  1730
		//No responder found.  Try again, looking for one which will respond to paste:
David@0
  1731
		selector = @selector(paste:);
David@0
  1732
		responder = [keyWindow earliestResponderWhichRespondsToSelector:selector
David@0
  1733
														andIsNotOfClass:NSClassFromString(@"WebHTMLView")];
David@0
  1734
	}
David@0
  1735
David@0
  1736
	//Sending pasteAsRichText: to a non rich text NSTextView won't do anything; change it to a generic paste:
David@0
  1737
	if ([responder isKindOfClass:[NSTextView class]] && ![(NSTextView *)responder isRichText]) {
David@0
  1738
		selector = @selector(paste:);
David@0
  1739
	}
David@0
  1740
David@0
  1741
	if (selector) {
David@0
  1742
		[keyWindow makeFirstResponder:responder];
David@0
  1743
		[responder performSelector:selector
David@0
  1744
						withObject:sender];
David@0
  1745
	}
David@0
  1746
}
David@0
  1747
David@0
  1748
//Custom Printing ------------------------------------------------------------------------------------------------------
David@0
  1749
#pragma mark Custom Printing
David@0
  1750
- (IBAction)adiumPrint:(id)sender
David@0
  1751
{
David@0
  1752
	//Pass the print command to the window, which is responsible for routing it to the correct place or
David@0
  1753
	//creating a view and printing.  Adium will not print from a window that does not respond to adiumPrint:
David@0
  1754
	NSWindow	*keyWindowController = [[[NSApplication sharedApplication] keyWindow] windowController];
David@0
  1755
	if ([keyWindowController respondsToSelector:@selector(adiumPrint:)]) {
David@0
  1756
		[keyWindowController performSelector:@selector(adiumPrint:)
David@0
  1757
								  withObject:sender];
David@0
  1758
	}
David@0
  1759
}
David@0
  1760
David@0
  1761
#pragma mark Preferences Display
David@0
  1762
- (IBAction)showPreferenceWindow:(id)sender
David@0
  1763
{
David@95
  1764
	[adium.preferenceController showPreferenceWindow:sender];
David@0
  1765
}
David@0
  1766
David@0
  1767
#pragma mark Font Panel
David@0
  1768
- (IBAction)toggleFontPanel:(id)sender
David@0
  1769
{
David@0
  1770
	if ([NSFontPanel sharedFontPanelExists] &&
David@0
  1771
		[[NSFontPanel sharedFontPanel] isVisible]) {
David@0
  1772
		[[NSFontPanel sharedFontPanel] close];
David@0
  1773
David@0
  1774
	} else {
David@0
  1775
		NSFontPanel	*fontPanel = [NSFontPanel sharedFontPanel];
David@0
  1776
		
David@0
  1777
		if (!fontPanelAccessoryView) {
David@0
  1778
			[NSBundle loadNibNamed:@"FontPanelAccessoryView" owner:self];
David@0
  1779
			[fontPanel setAccessoryView:fontPanelAccessoryView];
David@0
  1780
		}
David@0
  1781
		
David@0
  1782
		[fontPanel orderFront:self]; 
David@0
  1783
	}
David@0
  1784
}
David@0
  1785
David@0
  1786
- (IBAction)setFontPanelSettingsAsDefaultFont:(id)sender
David@0
  1787
{
David@0
  1788
	NSFont	*selectedFont = [[NSFontManager sharedFontManager] selectedFont];
David@0
  1789
David@95
  1790
	[adium.preferenceController setPreference:[selectedFont stringRepresentation]
David@0
  1791
										 forKey:KEY_FORMATTING_FONT
David@0
  1792
										  group:PREF_GROUP_FORMATTING];
David@0
  1793
	
David@0
  1794
	//We can't get foreground/background color from the font panel so far as I can tell... so we do the best we can.
David@0
  1795
	NSWindow	*keyWindow = [[NSApplication sharedApplication] keyWindow];
David@0
  1796
	NSResponder *responder = [keyWindow firstResponder]; 
David@0
  1797
	if ([responder isKindOfClass:[NSTextView class]]) {
David@0
  1798
		NSDictionary	*typingAttributes = [(NSTextView *)responder typingAttributes];
David@0
  1799
		NSColor			*foregroundColor, *backgroundColor;
David@0
  1800
David@0
  1801
		if ((foregroundColor = [typingAttributes objectForKey:NSForegroundColorAttributeName])) {
David@95
  1802
			[adium.preferenceController setPreference:[foregroundColor stringRepresentation]
David@0
  1803
												 forKey:KEY_FORMATTING_TEXT_COLOR
David@0
  1804
												  group:PREF_GROUP_FORMATTING];
David@0
  1805
		}
David@0
  1806
David@0
  1807
		if ((backgroundColor = [typingAttributes objectForKey:AIBodyColorAttributeName])) {
David@95
  1808
			[adium.preferenceController setPreference:[backgroundColor stringRepresentation]
David@0
  1809
												 forKey:KEY_FORMATTING_BACKGROUND_COLOR
David@0
  1810
												  group:PREF_GROUP_FORMATTING];
David@0
  1811
		}
David@0
  1812
	}
David@0
  1813
}
David@0
  1814
David@0
  1815
//Custom Dimming menu items --------------------------------------------------------------------------------------------
David@0
  1816
#pragma mark Custom Dimming menu items
David@0
  1817
//The standard ones do not dim correctly when unavailable
David@0
  1818
- (IBAction)toggleFontTrait:(id)sender
David@0
  1819
{
David@0
  1820
    NSFontManager	*fontManager = [NSFontManager sharedFontManager];
David@0
  1821
    
David@0
  1822
    if ([fontManager traitsOfFont:[fontManager selectedFont]] & [sender tag]) {
David@0
  1823
        [fontManager removeFontTrait:sender];
David@0
  1824
    } else {
David@0
  1825
        [fontManager addFontTrait:sender];
David@0
  1826
    }
David@0
  1827
}
David@0
  1828
David@0
  1829
- (void)toggleToolbarShown:(id)sender
David@0
  1830
{
David@0
  1831
	NSWindow	*window = [[NSApplication sharedApplication] keyWindow]; 	
David@0
  1832
	[window toggleToolbarShown:sender];
David@0
  1833
}
David@0
  1834
David@0
  1835
- (void)runToolbarCustomizationPalette:(id)sender
David@0
  1836
{
David@0
  1837
	NSWindow	*window = [[NSApplication sharedApplication] keyWindow]; 	
David@0
  1838
	[window runToolbarCustomizationPalette:sender];
David@0
  1839
}
David@0
  1840
David@0
  1841
//Menu item validation
David@0
  1842
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
David@0
  1843
{
David@0
  1844
	
David@0
  1845
	NSWindow	*keyWindow = [[NSApplication sharedApplication] keyWindow];
David@0
  1846
	NSResponder *responder = [keyWindow firstResponder]; 
David@0
  1847
	
David@0
  1848
    if (menuItem == menuItem_bold || menuItem == menuItem_italic) {
David@0
  1849
		NSFont			*selectedFont = [[NSFontManager sharedFontManager] selectedFont];
David@0
  1850
		
David@0
  1851
		//We must be in a text view, have text on the pasteboard, and have a font that supports bold or italic
David@0
  1852
		if ([responder isKindOfClass:[NSTextView class]]) {
David@0
  1853
			return (menuItem == menuItem_bold ? [selectedFont supportsBold] : [selectedFont supportsItalics]);
David@0
  1854
		}
David@0
  1855
		return NO;
David@0
  1856
		
David@0
  1857
	} else if (menuItem == menuItem_paste || menuItem == menuItem_pasteAndMatchStyle || menuItem == menuItem_pasteWithImagesAndColors) {
David@0
  1858
David@0
  1859
		//The user can paste if the pasteboard contains an image, some text, one or more files, or one or more URLs.
David@0
  1860
		NSPasteboard *pboard = [NSPasteboard generalPasteboard];
David@0
  1861
		NSArray *nonImageTypes = [NSArray arrayWithObjects:
David@0
  1862
			NSStringPboardType,
David@0
  1863
			NSRTFPboardType,
David@0
  1864
			NSURLPboardType,
David@0
  1865
			NSFilenamesPboardType,
David@0
  1866
			NSFilesPromisePboardType,
David@0
  1867
			NSRTFDPboardType,
David@0
  1868
			nil];
David@0
  1869
		return ([pboard availableTypeFromArray:nonImageTypes] != nil) || [NSImage canInitWithPasteboard:pboard];
David@0
  1870
	
David@0
  1871
	} else if (menuItem == menuItem_showToolbar) {
David@0
  1872
		[menuItem_showToolbar setTitle:([[keyWindow toolbar] isVisible] ? 
David@0
  1873
										AILocalizedString(@"Hide Toolbar",nil) : 
David@0
  1874
										AILocalizedString(@"Show Toolbar",nil))];
David@0
  1875
		return [keyWindow toolbar] != nil;
David@0
  1876
	
David@0
  1877
	} else if (menuItem == menuItem_customizeToolbar) {
David@0
  1878
		return ([keyWindow toolbar] != nil && [[keyWindow toolbar] isVisible] && [[keyWindow windowController] canCustomizeToolbar]);
David@0
  1879
David@0
  1880
	} else if (menuItem == menuItem_close) {
David@0
  1881
		return (keyWindow && ([[keyWindow standardWindowButton:NSWindowCloseButton] isEnabled] ||
David@0
  1882
							  ([[keyWindow windowController] respondsToSelector:@selector(windowPermitsClose)] &&
David@0
  1883
							   [[keyWindow windowController] windowPermitsClose])));
David@0
  1884
		
zacw@1588
  1885
	} else if (menuItem == menuItem_closeChat || menuItem == menuItem_clearDisplay) {
David@0
  1886
		return activeChat != nil;
David@0
  1887
		
David@0
  1888
	} else if( menuItem == menuItem_closeAllChats) {
David@0
  1889
		return [[self openChats] count] > 0;
David@0
  1890
David@0
  1891
	} else if (menuItem == menuItem_print) {
David@0
  1892
		NSWindowController *windowController = [keyWindow windowController];
David@0
  1893
David@0
  1894
		return ([windowController respondsToSelector:@selector(adiumPrint:)] &&
David@0
  1895
				(![windowController respondsToSelector:@selector(validatePrintMenuItem:)] ||
David@0
  1896
				 [windowController validatePrintMenuItem:menuItem]));
David@0
  1897
		
David@0
  1898
	} else if (menuItem == menuItem_showFonts) {
David@0
  1899
		[menuItem_showFonts setTitle:(([NSFontPanel sharedFontPanelExists] && [[NSFontPanel sharedFontPanel] isVisible]) ?
David@0
  1900
									  AILocalizedString(@"Hide Fonts",nil) :
David@0
  1901
									  AILocalizedString(@"Show Fonts",nil))];
David@0
  1902
		return YES;
zacw@1587
  1903
	} else if (menuItem == menuItem_toggleUserlist || menuItem == menuItem_toggleUserlistSide) {
David@812
  1904
			return self.activeChat.isGroupChat;
David@0
  1905
	} else {
David@0
  1906
		return YES;
David@0
  1907
	}
David@0
  1908
}
David@0
  1909
David@0
  1910
#pragma mark Window levels
David@0
  1911
- (NSMenu *)menuForWindowLevelsNotifyingTarget:(id)target
David@0
  1912
{
David@0
  1913
	NSMenu		*windowPositionMenu = [[NSMenu allocWithZone:[NSMenu zone]] init];
David@0
  1914
	NSMenuItem	*menuItem;
David@0
  1915
	
David@0
  1916
	menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Above other windows",nil)
David@0
  1917
																	target:target
David@0
  1918
																	action:@selector(selectedWindowLevel:)
David@0
  1919
															 keyEquivalent:@""];
David@0
  1920
	[menuItem setEnabled:YES];
David@0
  1921
	[menuItem setTag:AIFloatingWindowLevel];
David@0
  1922
	[windowPositionMenu addItem:menuItem];
David@0
  1923
	[menuItem release];
David@0
  1924
	
David@0
  1925
	menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Normally",nil)
David@0
  1926
																	target:target
David@0
  1927
																	action:@selector(selectedWindowLevel:)
David@0
  1928
															 keyEquivalent:@""];
David@0
  1929
	[menuItem setEnabled:YES];
David@0
  1930
	[menuItem setTag:AINormalWindowLevel];
David@0
  1931
	[windowPositionMenu addItem:menuItem];
David@0
  1932
	[menuItem release];
David@0
  1933
	
David@0
  1934
	menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Below other windows",nil)
David@0
  1935
																	target:target
David@0
  1936
																	action:@selector(selectedWindowLevel:)
David@0
  1937
															 keyEquivalent:@""];
David@0
  1938
	[menuItem setEnabled:YES];
David@0
  1939
	[menuItem setTag:AIDesktopWindowLevel];
David@0
  1940
	[windowPositionMenu addItem:menuItem];
David@0
  1941
	[menuItem release];
David@0
  1942
	
David@0
  1943
	[windowPositionMenu setAutoenablesItems:NO];
David@0
  1944
David@0
  1945
	return [windowPositionMenu autorelease];
David@0
  1946
}
David@0
  1947
David@0
  1948
-(void)toggleUserlist:(id)sender
David@0
  1949
{
zacw@1588
  1950
	[self.activeChat.chatContainer.chatViewController toggleUserList];
zacw@1244
  1951
}
zacw@1244
  1952
zacw@1587
  1953
-(void)toggleUserlistSide:(id)sender
zacw@1587
  1954
{
zacw@1588
  1955
	[self.activeChat.chatContainer.chatViewController toggleUserListSide];
zacw@1588
  1956
}
zacw@1588
  1957
zacw@1588
  1958
-(void)clearDisplay:(id)sender
zacw@1588
  1959
{
zacw@1588
  1960
	[self.activeChat.chatContainer.messageViewController.messageDisplayController clearView];
zacw@1587
  1961
}
David@0
  1962
David@0
  1963
@end