Source/AIInterfaceController.m
author Thijs Alkemade <thijsalkemade@gmail.com>
Sat Aug 28 22:01:12 2010 +0200 (21 months ago)
changeset 3277 21ab21e877e0
parent 3092 ffb42621b742
child 3310 72ccf4b32d4d
permissions -rw-r--r--
Add a Reopen Closed Tab menu item to the File menu that will restore the most recently closed tab, similar to Chrome. Fixes #12537

Does not work with MSN group chats (and probably other protocols that have unnamed MUCs).

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