Plugins/Dual Window Interface/AIMessageViewController.m
author Zachary West <zacw@adium.im>
Sun Oct 25 21:21:42 2009 -0400 (2009-10-25)
changeset 2776 862f4e3f05e1
parent 2118 9fbe80565319
child 2838 2d3f7242a158
permissions -rw-r--r--
Control more directly the completion range for group chats. Fixes #13237.

We now go back to the nearest whitespace and see if it would autocomplete any nicks at that point. If it would, we perform the autocompletion starting at that point. If it doesn't, we let the OS do its normal thing.

This fixes autocompleting things like "[mynick]" or the channel name (such as "#adium") since they start with non-word characters, but are perfectly valid.
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
#import "AIMessageViewController.h"
David@0
    18
#import "AIAccountSelectionView.h"
David@0
    19
#import "AIMessageWindowController.h"
David@0
    20
#import "ESGeneralPreferencesPlugin.h"
David@0
    21
#import "AIDualWindowInterfacePlugin.h"
David@0
    22
#import "AIContactInfoWindowController.h"
David@0
    23
#import "AIMessageTabSplitView.h"
David@0
    24
#import "AIMessageWindowOutgoingScrollView.h"
David@0
    25
#import "KNShelfSplitView.h"
David@0
    26
#import "ESChatUserListController.h"
David@0
    27
David@0
    28
#import <Adium/AIChatControllerProtocol.h>
David@0
    29
#import <Adium/AIContactAlertsControllerProtocol.h>
David@0
    30
#import <Adium/AIContactControllerProtocol.h>
David@0
    31
#import <Adium/AIContentControllerProtocol.h>
David@0
    32
#import <Adium/AIContentControllerProtocol.h>
David@0
    33
#import <Adium/AIInterfaceControllerProtocol.h>
David@0
    34
#import <Adium/AIMenuControllerProtocol.h>
David@0
    35
#import <Adium/AIToolbarControllerProtocol.h>
David@0
    36
#import <Adium/AIAccount.h>
David@0
    37
#import <Adium/AIChat.h>
David@0
    38
#import <Adium/AIContentMessage.h>
David@0
    39
#import <Adium/AIListContact.h>
David@586
    40
#import <Adium/AIMetaContact.h>
David@0
    41
#import <Adium/AIListObject.h>
David@0
    42
#import <Adium/AIListOutlineView.h>
David@547
    43
#import <Adium/AIServiceIcons.h>
David@0
    44
David@0
    45
#import <AIUtilities/AIApplicationAdditions.h>
David@0
    46
#import <AIUtilities/AIAttributedStringAdditions.h>
David@0
    47
#import <AIUtilities/AIAutoScrollView.h>
David@0
    48
#import <AIUtilities/AIDictionaryAdditions.h>
David@0
    49
#import <AIUtilities/AISplitView.h>
David@0
    50
David@0
    51
#import <PSMTabBarControl/NSBezierPath_AMShading.h>
David@0
    52
David@0
    53
#import "RBSplitView.h"
David@0
    54
David@0
    55
//Heights and Widths
David@0
    56
#define MESSAGE_VIEW_MIN_HEIGHT_RATIO		.50						//Mininum height ratio of the message view
David@0
    57
#define MESSAGE_VIEW_MIN_WIDTH_RATIO		.50						//Mininum width ratio of the message view
David@0
    58
#define ENTRY_TEXTVIEW_MIN_HEIGHT			20						//Mininum height of the text entry view
David@0
    59
#define USER_LIST_DEFAULT_WIDTH				120						//Default width of the user list
David@0
    60
David@0
    61
//Preferences and files
David@0
    62
#define MESSAGE_VIEW_NIB					@"MessageView"			//Filename of the message view nib
David@0
    63
#define	USERLIST_THEME						@"UserList Theme"		//File name of the user list theme
David@0
    64
#define	USERLIST_LAYOUT						@"UserList Layout"		//File name of the user list layout
David@0
    65
#define	KEY_ENTRY_TEXTVIEW_MIN_HEIGHT		@"Minimum Text Height"	//Preference key for text entry height
David@0
    66
#define	KEY_ENTRY_USER_LIST_MIN_WIDTH		@"UserList Width"		//Preference key for user list width
zacw@1175
    67
#define KEY_USER_LIST_VISIBLE_PREFIX		@"Userlist Visible Chat:" //Preference key prefix for user list visibility
zacw@1587
    68
#define KEY_USER_LIST_ON_RIGHT				@"UserList On Right"	// Preference key for user list being on the right
David@0
    69
David@0
    70
#define TEXTVIEW_HEIGHT_DEBUG
David@0
    71
David@84
    72
@interface AIMessageViewController ()
David@0
    73
- (id)initForChat:(AIChat *)inChat;
David@0
    74
- (void)chatStatusChanged:(NSNotification *)notification;
David@0
    75
- (void)chatParticipatingListObjectsChanged:(NSNotification *)notification;
David@0
    76
- (void)_configureMessageDisplay;
David@0
    77
- (void)_createAccountSelectionView;
David@0
    78
- (void)_destroyAccountSelectionView;
David@0
    79
- (void)_configureTextEntryView;
David@0
    80
- (void)_updateTextEntryViewHeight;
David@3
    81
- (NSInteger)_textEntryViewProperHeightIgnoringUserMininum:(BOOL)ignoreUserMininum;
David@0
    82
- (void)_showUserListView;
David@0
    83
- (void)_hideUserListView;
David@0
    84
- (void)_configureUserList;
David@0
    85
- (void)_updateUserListViewWidth;
zacw@1583
    86
- (NSInteger)_userListViewProperWidth;
David@0
    87
- (void)updateFramesForAccountSelectionView;
David@0
    88
- (void)saveUserListMinimumSize;
zacw@1175
    89
- (BOOL)userListInitiallyVisible;
Evan@231
    90
- (void)setUserListVisible:(BOOL)inVisible;
zacw@1420
    91
- (void)setupShelfView;
zacw@1420
    92
- (void)updateUserCount;
zacw@2776
    93
zacw@2776
    94
- (NSArray *)contactsMatchingBeginningString:(NSString *)partialWord;
David@0
    95
@end
David@0
    96
David@0
    97
@implementation AIMessageViewController
David@0
    98
David@0
    99
/*!
David@0
   100
 * @brief Create a new message view controller
David@0
   101
 */
David@0
   102
+ (AIMessageViewController *)messageDisplayControllerForChat:(AIChat *)inChat
David@0
   103
{
David@0
   104
    return [[[self alloc] initForChat:inChat] autorelease];
David@0
   105
}
David@0
   106
David@0
   107
David@0
   108
/*!
David@0
   109
 * @brief Initialize
David@0
   110
 */
David@0
   111
- (id)initForChat:(AIChat *)inChat
David@0
   112
{
David@0
   113
    if ((self = [super init])) {
David@0
   114
		AIListContact	*contact;
David@0
   115
		//Init
David@0
   116
		chat = [inChat retain];
David@426
   117
		contact = chat.listObject;
David@0
   118
		view_accountSelection = nil;
David@0
   119
		userListController = nil;
David@0
   120
		suppressSendLaterPrompt = NO;
David@0
   121
		retainingScrollViewUserList = NO;
David@0
   122
		
David@0
   123
		//Load the view containing our controls
David@0
   124
		[NSBundle loadNibNamed:MESSAGE_VIEW_NIB owner:self];
David@0
   125
		
David@0
   126
		//Register for the various notification we need
David@1109
   127
		[[NSNotificationCenter defaultCenter] addObserver:self
David@0
   128
									   selector:@selector(sendMessage:) 
David@0
   129
										   name:Interface_SendEnteredMessage
David@0
   130
										 object:chat];
David@1109
   131
		[[NSNotificationCenter defaultCenter] addObserver:self
David@0
   132
									   selector:@selector(didSendMessage:)
David@0
   133
										   name:Interface_DidSendEnteredMessage 
David@0
   134
										 object:chat];
David@1109
   135
		[[NSNotificationCenter defaultCenter] addObserver:self
David@0
   136
									   selector:@selector(chatStatusChanged:) 
David@0
   137
										   name:Chat_StatusChanged
David@0
   138
										 object:chat];
David@1109
   139
		[[NSNotificationCenter defaultCenter] addObserver:self 
David@0
   140
									   selector:@selector(chatParticipatingListObjectsChanged:)
David@0
   141
										   name:Chat_ParticipatingListObjectsChanged
David@0
   142
										 object:chat];
David@1109
   143
		[[NSNotificationCenter defaultCenter] addObserver:self
David@0
   144
									   selector:@selector(redisplaySourceAndDestinationSelector:) 
David@0
   145
										   name:Chat_SourceChanged
David@0
   146
										 object:chat];
David@1109
   147
		[[NSNotificationCenter defaultCenter] addObserver:self
David@0
   148
									   selector:@selector(redisplaySourceAndDestinationSelector:) 
David@0
   149
										   name:Chat_DestinationChanged
David@0
   150
										 object:chat];
David@0
   151
David@0
   152
		//Observe general preferences for sending keys
David@95
   153
		[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_GENERAL];
zacw@1583
   154
		[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_DUAL_WINDOW_INTERFACE];
David@0
   155
David@0
   156
		/* Update chat status and participating list objects to configure the user list if necessary
David@0
   157
		 * Call chatParticipatingListObjectsChanged first, which will set up the user list. This allows other sizing to match.
David@0
   158
		 */
zacw@1176
   159
		[self setUserListVisible:(chat.isGroupChat && [self userListInitiallyVisible])];
David@0
   160
		
David@0
   161
		[self chatParticipatingListObjectsChanged:nil];
David@0
   162
		[self chatStatusChanged:nil];
David@0
   163
		
David@0
   164
		//Configure our views
David@0
   165
		[self _configureMessageDisplay];
David@0
   166
		[self _configureTextEntryView];
David@0
   167
David@0
   168
		//Set our base writing direction
David@0
   169
		if (contact) {
David@0
   170
			initialBaseWritingDirection = [contact baseWritingDirection];
David@0
   171
			[textView_outgoing setBaseWritingDirection:initialBaseWritingDirection];
zacw@1533
   172
		}
David@0
   173
	}
David@0
   174
David@0
   175
	return self;
David@0
   176
}
David@0
   177
David@0
   178
/*!
David@0
   179
 * @brief Deallocate
David@0
   180
 */
David@0
   181
- (void)dealloc
David@0
   182
{   
David@426
   183
	AIListContact	*contact = chat.listObject;
David@0
   184
	
David@95
   185
	[adium.preferenceController unregisterPreferenceObserver:self];
David@0
   186
David@0
   187
	//Store our minimum height for the text entry area, and minimim width for the user list
David@95
   188
	[adium.preferenceController setPreference:[NSNumber numberWithInteger:entryMinHeight]
David@0
   189
										 forKey:KEY_ENTRY_TEXTVIEW_MIN_HEIGHT
David@0
   190
										  group:PREF_GROUP_DUAL_WINDOW_INTERFACE];
David@0
   191
David@0
   192
	if (userListController) {
David@0
   193
		[self saveUserListMinimumSize];
David@0
   194
	}
David@0
   195
	
David@0
   196
	//Save the base writing direction
David@0
   197
	if (contact && initialBaseWritingDirection != [textView_outgoing baseWritingDirection])
David@0
   198
		[contact setBaseWritingDirection:[textView_outgoing baseWritingDirection]];
David@0
   199
David@0
   200
	[chat release]; chat = nil;
David@0
   201
David@1109
   202
	//remove observers
David@1109
   203
	[[NSNotificationCenter defaultCenter] removeObserver:self];
David@0
   204
	
David@0
   205
    //Account selection view
David@0
   206
	[self _destroyAccountSelectionView];
David@0
   207
	
David@0
   208
	[messageDisplayController messageViewIsClosing];
David@0
   209
    [messageDisplayController release];
David@0
   210
	[userListController release];
David@0
   211
David@0
   212
	[controllerView_messages release];
David@0
   213
	
David@0
   214
	//Release the views for which we are responsible (because we loaded them via -[NSBundle loadNibNamed:owner])
David@0
   215
	[nibrootView_messageView release];
David@0
   216
	[nibrootView_shelfVew release];
David@0
   217
	[nibrootView_userList release];
David@0
   218
David@0
   219
	//Release the hidden user list view
David@0
   220
	if (retainingScrollViewUserList) {
David@0
   221
		[scrollView_userList release];
David@0
   222
	}
David@0
   223
	//release menuItem
David@0
   224
	[showHide release];
David@0
   225
	
David@0
   226
	[undoManager release]; undoManager = nil;
David@0
   227
David@0
   228
    [super dealloc];
David@0
   229
}
David@0
   230
David@0
   231
- (void)saveUserListMinimumSize
David@0
   232
{
David@95
   233
	[adium.preferenceController setPreference:[NSNumber numberWithInteger:userListMinWidth]
David@0
   234
										 forKey:KEY_ENTRY_USER_LIST_MIN_WIDTH
David@0
   235
										  group:PREF_GROUP_DUAL_WINDOW_INTERFACE];
David@0
   236
}
David@0
   237
David@0
   238
- (void)updateGradientColors
David@0
   239
{
David@0
   240
	NSColor *darkerColor = [NSColor colorWithCalibratedWhite:0.90 alpha:1.0];
David@0
   241
	NSColor *lighterColor = [NSColor colorWithCalibratedWhite:0.92 alpha:1.0];
David@0
   242
	NSColor *leftColor = nil, *rightColor = nil;
David@0
   243
David@0
   244
	switch ([messageWindowController tabPosition]) {
David@0
   245
		case AdiumTabPositionBottom:
David@0
   246
		case AdiumTabPositionTop:
David@0
   247
		case AdiumTabPositionLeft:
David@0
   248
			leftColor = lighterColor;
David@0
   249
			rightColor = darkerColor;
David@0
   250
			break;
David@0
   251
		case AdiumTabPositionRight:
David@0
   252
			leftColor = darkerColor;
David@0
   253
			rightColor = lighterColor;
David@0
   254
			break;
David@0
   255
	}
David@0
   256
David@0
   257
	[view_accountSelection setLeftColor:leftColor rightColor:rightColor];
David@0
   258
	//XXX
David@0
   259
//	[splitView_textEntryHorizontal setLeftColor:leftColor rightColor:rightColor];
David@0
   260
}
David@0
   261
David@0
   262
/*!
David@0
   263
 * @brief Invoked before the message view closes
David@0
   264
 *
David@0
   265
 * This method is invoked before our message view controller's message view leaves a window.
David@0
   266
 * We need to clean up our user list to invalidate cursor tracking before the view closes.
David@0
   267
 */
David@0
   268
- (void)messageViewWillLeaveWindowController:(AIMessageWindowController *)inWindowController
David@0
   269
{
David@0
   270
	if (inWindowController) {
David@0
   271
		[userListController contactListWillBeRemovedFromWindow];
David@0
   272
	}
David@0
   273
	
David@0
   274
	[messageWindowController release]; messageWindowController = nil;
David@0
   275
}
David@0
   276
David@0
   277
- (void)messageViewAddedToWindowController:(AIMessageWindowController *)inWindowController
David@0
   278
{
David@0
   279
	if (inWindowController) {
David@0
   280
		[userListController contactListWasAddedBackToWindow];
David@0
   281
	}
David@0
   282
	
David@0
   283
	if (inWindowController != messageWindowController) {
David@0
   284
		[messageWindowController release];
David@0
   285
		messageWindowController = [inWindowController retain];
David@0
   286
		
David@0
   287
		[self updateGradientColors];
David@0
   288
	}
David@0
   289
}
David@0
   290
David@0
   291
/*!
David@0
   292
 * @brief Retrieve the chat represented by this message view
David@0
   293
 */
David@0
   294
- (AIChat *)chat
David@0
   295
{
David@0
   296
    return chat;
David@0
   297
}
David@0
   298
David@0
   299
/*!
David@0
   300
 * @brief Retrieve the source account associated with this chat
David@0
   301
 */
David@0
   302
- (AIAccount *)account
David@0
   303
{
David@426
   304
    return chat.account;
David@0
   305
}
David@0
   306
David@0
   307
/*!
David@0
   308
 * @brief Retrieve the destination list object associated with this chat
David@0
   309
 */
David@0
   310
- (AIListContact *)listObject
David@0
   311
{
David@426
   312
    return chat.listObject;
David@0
   313
}
David@0
   314
David@0
   315
/*!
David@0
   316
 * @brief Returns the selected list object in our participants list
David@0
   317
 */
David@0
   318
- (AIListObject *)preferredListObject
David@0
   319
{
David@0
   320
	if (userListView) { //[[shelfView subviews] containsObject:scrollView_userList] && ([userListView selectedRow] != -1)
David@0
   321
		return [userListView itemAtRow:[userListView selectedRow]];
David@0
   322
	}
David@0
   323
	
David@0
   324
	return nil;
David@0
   325
}
David@0
   326
David@0
   327
/*!
David@0
   328
 * @brief Invoked when the status of our chat changes
David@0
   329
 *
David@0
   330
 * The only chat status change we're interested in is one to the disallow account switching flag.  When this flag 
David@0
   331
 * changes we update the visibility of our account status menus accordingly.
David@0
   332
 */
David@0
   333
- (void)chatStatusChanged:(NSNotification *)notification
David@0
   334
{
David@0
   335
    NSArray	*modifiedKeys = [[notification userInfo] objectForKey:@"Keys"];
David@0
   336
	
David@0
   337
    if (notification == nil || [modifiedKeys containsObject:@"DisallowAccountSwitching"]) {
David@0
   338
		[self setAccountSelectionMenuVisibleIfNeeded:YES];
David@0
   339
    }
David@0
   340
}
David@0
   341
David@0
   342
David@0
   343
//Message Display ------------------------------------------------------------------------------------------------------
David@0
   344
#pragma mark Message Display
David@0
   345
/*!
David@0
   346
 * @brief Configure the message display view
David@0
   347
 */
David@0
   348
- (void)_configureMessageDisplay
David@0
   349
{
David@0
   350
	//Create the message view
David@100
   351
	messageDisplayController = [[adium.interfaceController messageDisplayControllerForChat:chat] retain];
David@0
   352
	//Get the messageView from the controller
David@0
   353
	controllerView_messages = [[messageDisplayController messageView] retain];
David@0
   354
David@0
   355
	/* customView_messages is really just a placeholder.  It's a subview of scrollView_messages, which exists just
David@0
   356
	 * to draw a box around itself to give the desired border. NSBox could be used for the same purpose.
David@0
   357
	 * We replace customView_messages with the actual message view we want to use, controllerView_messages.
David@0
   358
	 *
David@0
   359
	 * Note that this does -not- change the documentView of scrollView_messages, which remains NULL.
David@0
   360
	 * This is because the controllerView_messages supplies its own scroll view (within the WebView).
David@0
   361
	 * We therefore use -[AIMessageWindowOutgoingScrollView setAccessibilityChild:] to manage the accessibility
David@0
   362
	 * heirarchy.
David@0
   363
	 */
David@0
   364
	[controllerView_messages setFrame:[scrollView_messages documentVisibleRect]];
David@0
   365
	[scrollView_messages setAccessibilityChild:controllerView_messages];
David@0
   366
	[[customView_messages superview] replaceSubview:customView_messages with:controllerView_messages];
David@0
   367
David@0
   368
	//This is what draws our transparent background
David@0
   369
	//Technically, it could be set in MessageView.nib, too
David@0
   370
	[scrollView_messages setBackgroundColor:[NSColor clearColor]];
David@0
   371
zacw@1548
   372
	[textView_outgoing setNextResponder:view_contents];
zacw@1533
   373
	
David@0
   374
	[controllerView_messages setNextResponder:textView_outgoing];
David@0
   375
}
David@0
   376
David@0
   377
/*!
David@0
   378
 * @brief The message display controller
David@0
   379
 */
David@0
   380
- (NSObject<AIMessageDisplayController> *)messageDisplayController
David@0
   381
{
David@0
   382
	return messageDisplayController;
David@0
   383
}
David@0
   384
David@0
   385
/*!
David@0
   386
 * @brief Access to our view
David@0
   387
 */
David@0
   388
- (NSView *)view
David@0
   389
{
David@0
   390
    return view_contents;
David@0
   391
}
David@0
   392
David@0
   393
- (NSScrollView *)messagesScrollView
David@0
   394
{
David@0
   395
	return scrollView_messages;
David@0
   396
}
David@0
   397
David@0
   398
/*!
David@0
   399
 * @brief Support for printing.  Forward the print command to our message display view
David@0
   400
 */
David@0
   401
- (void)adiumPrint:(id)sender
David@0
   402
{
David@0
   403
	if ([messageDisplayController respondsToSelector:@selector(adiumPrint:)]) {
David@0
   404
		[messageDisplayController adiumPrint:sender];
David@0
   405
	}
David@0
   406
}
David@0
   407
David@0
   408
David@0
   409
//Messaging ------------------------------------------------------------------------------------------------------------
David@0
   410
#pragma mark Messaging
David@0
   411
/*!
David@0
   412
 * @brief Send the entered message
David@0
   413
 */
David@0
   414
- (IBAction)sendMessage:(id)sender
David@0
   415
{
David@0
   416
	NSAttributedString	*attributedString = [textView_outgoing textStorage];
David@0
   417
	
David@0
   418
	//Only send if we have a non-zero-length string
David@0
   419
    if ([attributedString length] != 0) { 
David@426
   420
		AIListObject				*listObject = chat.listObject;
David@0
   421
David@0
   422
		//If user typed command /clear, reset the content of the view
David@0
   423
		if ([[attributedString string] caseInsensitiveCompare:AILocalizedString(@"/clear", "Command which will clear the message area of a chat. Please include the '/' at the front of your translation.")] == NSOrderedSame) {
David@0
   424
			//Reset the content of the view
David@0
   425
			[messageDisplayController clearView];
David@0
   426
David@0
   427
			//Reset the content of the text field, removing the command as it has been executed
David@0
   428
			[self clearTextEntryView];
David@0
   429
David@0
   430
			//Commands are not messages, so they don't have to be sent
David@0
   431
			return;
David@0
   432
		}
David@0
   433
		
David@428
   434
		if (chat.isGroupChat && !chat.account.online) {
David@0
   435
			//Refuse to do anything with a group chat for an offline account.
David@0
   436
			NSBeep();
David@0
   437
			return;
David@0
   438
		}
David@0
   439
David@428
   440
		AIChatSendingAbilityType messageSendingAbility = chat.messageSendingAbility;
David@0
   441
		if (suppressSendLaterPrompt || (messageSendingAbility == AIChatCanSendMessageNow) ||
David@428
   442
			((messageSendingAbility == AIChatCanSendViaServersideOfflineMessage) && chat.account.sendOfflineMessagesWithoutPrompting)) {
David@0
   443
			AIContentMessage		*message;
David@0
   444
			NSAttributedString		*outgoingAttributedString;
David@426
   445
			AIAccount				*account = chat.account;
David@0
   446
			//Send the message
David@1109
   447
			[[NSNotificationCenter defaultCenter] postNotificationName:Interface_WillSendEnteredMessage
David@0
   448
													  object:chat
David@0
   449
													userInfo:nil];
David@0
   450
			
David@0
   451
			outgoingAttributedString = [attributedString copy];
David@0
   452
			message = [AIContentMessage messageInChat:chat
David@0
   453
										   withSource:account
David@426
   454
										  destination:chat.listObject
David@0
   455
												 date:nil //created for us by AIContentMessage
David@0
   456
											  message:outgoingAttributedString
David@0
   457
											autoreply:NO];
David@0
   458
			[outgoingAttributedString release];
David@0
   459
			
David@95
   460
			if ([adium.contentController sendContentObject:message]) {
David@1109
   461
				[[NSNotificationCenter defaultCenter] postNotificationName:Interface_DidSendEnteredMessage 
David@0
   462
														  object:chat
David@0
   463
														userInfo:nil];
David@0
   464
			}
Evan@672
   465
			/* If we sent with AIChatCanSendViaServersideOfflineMessage, we should probably show a status message to
Evan@672
   466
			 * the effect AILocalizedString(@"Your message has been sent. %@ will receive it when online.", nil)
Evan@672
   467
			 */
David@0
   468
		} else {
David@837
   469
			NSString							*formattedUID = listObject.formattedUID;
David@0
   470
			
David@0
   471
			NSAlert *alert = [[NSAlert alloc] init];
David@0
   472
			NSImage *icon = ([listObject userIcon] ? [listObject userIcon] : [AIServiceIcons serviceIconForObject:listObject
David@0
   473
																											 type:AIServiceIconLarge
David@0
   474
																										direction:AIIconNormal]);
David@0
   475
			icon = [[icon copy] autorelease];
David@0
   476
			[icon setScalesWhenResized:NO];
David@0
   477
			[alert setIcon:icon];
David@0
   478
			[alert setAlertStyle:NSInformationalAlertStyle];
David@0
   479
			
David@0
   480
			[alert setMessageText:[NSString stringWithFormat:AILocalizedString(@"%@ appears to be offline. How do you want to send this message?", nil),
David@0
   481
								   formattedUID]];
David@0
   482
David@0
   483
			switch (messageSendingAbility) {
David@0
   484
				case AIChatCanSendViaServersideOfflineMessage:
David@0
   485
				{
David@0
   486
					[alert setInformativeText:[NSString stringWithFormat:
David@0
   487
											   AILocalizedString(@"Send Now will deliver your message to the server immediately. %@ will receive the message the next time he or she signs on, even if you are no longer online.\n\nSend When Both Online will send the message the next time both you and %@ are known to be online and you are connected using Adium on this computer.", "Send Later dialogue explanation text for accounts supporting offline messaging support."),
David@0
   488
											   formattedUID, formattedUID]];
David@0
   489
					[alert addButtonWithTitle:AILocalizedString(@"Send Now", nil)];
David@0
   490
					
David@0
   491
					[alert addButtonWithTitle:AILocalizedString(@"Send When Both Online", nil)];
David@0
   492
					[[[alert buttons] objectAtIndex:1] setKeyEquivalent:@"b"];
David@0
   493
					[[[alert buttons] objectAtIndex:1] setKeyEquivalentModifierMask:0];
David@0
   494
David@0
   495
					break;
David@0
   496
				}
David@0
   497
				case AIChatMayNotBeAbleToSendMessage:
David@0
   498
				{
David@0
   499
					[alert setInformativeText:[NSString stringWithFormat:
David@0
   500
											   AILocalizedString(@"Send Later will send the message the next time both you and %@ are online. Send Now may work if %@ is invisible or is not on your contact list and so only appears to be offline.", "Send Later dialogue explanation text"),
David@0
   501
											   formattedUID, formattedUID, formattedUID]];
David@0
   502
					[alert addButtonWithTitle:AILocalizedString(@"Send Now", nil)];
David@0
   503
					
David@0
   504
					[alert addButtonWithTitle:AILocalizedString(@"Send Later", nil)];
David@0
   505
					[[[alert buttons] objectAtIndex:1] setKeyEquivalent:@"l"];
David@0
   506
					[[[alert buttons] objectAtIndex:1] setKeyEquivalentModifierMask:0];
David@0
   507
					
David@0
   508
					break;
David@0
   509
				}
David@0
   510
				case AIChatCanNotSendMessage:
David@0
   511
				{
David@0
   512
					[alert setInformativeText:[NSString stringWithFormat:
David@0
   513
											   AILocalizedString(@"Send Later will send the message the next time both you and %@ are online.", "Send Later dialogue explanation text"),
David@0
   514
											   formattedUID, formattedUID, formattedUID]];					
David@0
   515
					[alert addButtonWithTitle:AILocalizedString(@"Send Later", nil)];
David@0
   516
					[[[alert buttons] objectAtIndex:0] setKeyEquivalent:@"l"];
David@0
   517
					[[[alert buttons] objectAtIndex:0] setKeyEquivalentModifierMask:0];
David@0
   518
					
David@0
   519
					break;
David@0
   520
				}
David@0
   521
				case AIChatCanSendMessageNow:
David@0
   522
				{
David@0
   523
					//We will never get here.
David@0
   524
					break;
David@0
   525
				}
David@0
   526
			}
David@0
   527
David@0
   528
			[alert addButtonWithTitle:AILocalizedString(@"Don't Send", nil)];
David@0
   529
David@0
   530
			NSButton *dontSendButton = ((messageSendingAbility == AIChatCanNotSendMessage) ?
David@0
   531
										[[alert buttons] objectAtIndex:1] :
David@0
   532
										[[alert buttons] objectAtIndex:2]);
David@0
   533
			[dontSendButton setKeyEquivalent:@"\E"];
David@0
   534
			[dontSendButton setKeyEquivalentModifierMask:0];
David@0
   535
			
David@0
   536
			[alert beginSheetModalForWindow:[view_contents window]
David@0
   537
							  modalDelegate:[self retain] /* Will release after the sheet ends */
David@0
   538
							 didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
David@3
   539
                                contextInfo:[[NSNumber numberWithInteger:messageSendingAbility] retain] /* Will release after the sheet ends */];
David@0
   540
			[alert release];
David@0
   541
		}
David@0
   542
    }
David@0
   543
}
David@0
   544
David@0
   545
/*!
David@0
   546
 * @brief Send Later button was pressed
David@0
   547
 */ 
David@3
   548
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
David@0
   549
{
David@3
   550
	AIChatSendingAbilityType messageSendingAbility = [(NSNumber *)contextInfo integerValue];
David@0
   551
David@0
   552
	switch (returnCode) {
David@0
   553
		case NSAlertFirstButtonReturn:
David@0
   554
			/* The AIChatCanNotSendMessage dalogue has Send Later as the first choice;
David@0
   555
			 * all others have Send Now as the first choice.
David@0
   556
			 */
David@0
   557
			if (messageSendingAbility == AIChatCanNotSendMessage) {
David@0
   558
				 /* Send Later */
David@0
   559
				[self sendMessageLater:nil];
David@0
   560
David@0
   561
			} else {
David@0
   562
				 /* Send Now */
David@0
   563
				suppressSendLaterPrompt = YES;
David@0
   564
				[self sendMessage:nil];
David@0
   565
			}
David@0
   566
			break;
David@0
   567
			
David@0
   568
		case NSAlertSecondButtonReturn:
David@0
   569
			/* The AIChatCanNotSendMessage dalogue has Cancel as the second choice;
David@0
   570
			 * all others have Send Later as the first choice.
David@0
   571
			 */
David@0
   572
			if (messageSendingAbility != AIChatCanNotSendMessage) {
David@0
   573
				/* Send Later */
David@0
   574
				[self sendMessageLater:nil];
David@0
   575
			}			
David@0
   576
			break;
David@0
   577
David@0
   578
		case NSAlertThirdButtonReturn: /* Don't Send */
David@0
   579
			break;		
David@0
   580
	}
David@0
   581
	
David@0
   582
	//Retained when the alert was created to guard against a crash if the chat tab being closed while we are open
David@0
   583
	[self release];
David@0
   584
	[(NSNumber *)contextInfo release];
David@0
   585
}
David@0
   586
David@0
   587
/*!
David@0
   588
 * @brief Invoked after our entered message sends
David@0
   589
 *
David@0
   590
 * This method hides the account selection view and clears the entered message after our message sends
David@0
   591
 */
David@0
   592
- (IBAction)didSendMessage:(id)sender
David@0
   593
{
David@0
   594
    [self setAccountSelectionMenuVisibleIfNeeded:NO];
David@0
   595
    [self clearTextEntryView];
David@0
   596
}
David@0
   597
David@0
   598
/*!
David@0
   599
 * @brief Offline messaging
David@0
   600
 */
David@0
   601
- (IBAction)sendMessageLater:(id)sender
David@0
   602
{
David@0
   603
	//If the chat can _now_ send a message, send it immediately instead of waiting for "later".
David@0
   604
	if ([chat messageSendingAbility] == AIChatCanSendMessageNow) {
David@0
   605
		[self sendMessage:sender];
David@0
   606
		return;
David@0
   607
	}
David@0
   608
David@0
   609
	//Put the alert on the metaContact containing this listContact if applicable
David@586
   610
	AIMetaContact *listContact = chat.listObject.metaContact;
David@0
   611
David@0
   612
	if (listContact) {
David@0
   613
		NSMutableDictionary *detailsDict, *alertDict;
David@0
   614
		
David@0
   615
		detailsDict = [NSMutableDictionary dictionary];
David@428
   616
		[detailsDict setObject:chat.account.internalObjectID forKey:@"Account ID"];
David@0
   617
		[detailsDict setObject:[NSNumber numberWithBool:YES] forKey:@"Allow Other"];
David@428
   618
		[detailsDict setObject:listContact.internalObjectID forKey:@"Destination ID"];
David@0
   619
David@0
   620
		alertDict = [NSMutableDictionary dictionary];
David@0
   621
		[alertDict setObject:detailsDict forKey:@"ActionDetails"];
David@0
   622
		[alertDict setObject:CONTACT_SEEN_ONLINE_YES forKey:@"EventID"];
David@0
   623
		[alertDict setObject:@"SendMessage" forKey:@"ActionID"];
David@0
   624
		[alertDict setObject:[NSNumber numberWithBool:YES] forKey:@"OneTime"]; 
David@0
   625
		
David@0
   626
		[alertDict setObject:listContact forKey:@"TEMP-ListContact"];
David@0
   627
		
David@95
   628
		[adium.contentController filterAttributedString:[[[textView_outgoing textStorage] copy] autorelease]
David@0
   629
										  usingFilterType:AIFilterContent
David@0
   630
												direction:AIFilterOutgoing
David@0
   631
											filterContext:listContact
David@0
   632
										  notifyingTarget:self
David@0
   633
												 selector:@selector(gotFilteredMessageToSendLater:receivingContext:)
David@0
   634
												  context:alertDict];
David@0
   635
David@0
   636
		[self didSendMessage:nil];
David@0
   637
	}
David@0
   638
}
David@0
   639
David@0
   640
/*!
David@0
   641
 * @brief Offline messaging
David@0
   642
 */
David@0
   643
//XXX - Offline messaging code SHOULD NOT BE IN HERE! -ai
David@0
   644
- (void)gotFilteredMessageToSendLater:(NSAttributedString *)filteredMessage receivingContext:(NSMutableDictionary *)alertDict
David@0
   645
{
David@0
   646
	NSMutableDictionary	*detailsDict;
David@0
   647
	AIListContact		*listContact;
David@0
   648
	
David@0
   649
	detailsDict = [alertDict objectForKey:@"ActionDetails"];
David@0
   650
	[detailsDict setObject:[filteredMessage dataRepresentation] forKey:@"Message"];
David@0
   651
David@0
   652
	listContact = [[alertDict objectForKey:@"TEMP-ListContact"] retain];
David@0
   653
	[alertDict removeObjectForKey:@"TEMP-ListContact"];
David@0
   654
	
David@100
   655
	[adium.contactAlertsController addAlert:alertDict 
David@0
   656
								 toListObject:listContact
David@0
   657
							 setAsNewDefaults:NO];
David@0
   658
	[listContact release];
David@0
   659
}
David@0
   660
David@0
   661
//Account Selection ----------------------------------------------------------------------------------------------------
David@0
   662
#pragma mark Account Selection
David@0
   663
/*!
David@0
   664
 * @brief
David@0
   665
 */
David@0
   666
- (void)accountSelectionViewFrameDidChange:(NSNotification *)notification
David@0
   667
{
David@0
   668
	[self updateFramesForAccountSelectionView];
David@0
   669
}
David@0
   670
David@0
   671
/*!
David@0
   672
 * @brief Redisplay the source/destination account selector
David@0
   673
 */
David@0
   674
- (void)redisplaySourceAndDestinationSelector:(NSNotification *)notification
David@0
   675
{
zacw@1682
   676
	// Update the textView's chat source, in case any attributes it monitors changed.
zacw@1682
   677
	[textView_outgoing setChat:chat];
David@0
   678
	[self setAccountSelectionMenuVisibleIfNeeded:YES];
David@0
   679
}
David@0
   680
David@0
   681
/*!
David@0
   682
 * @brief Toggle visibility of the account selection menus
David@0
   683
 *
David@0
   684
 * Invoking this method with NO will hide the account selection menus.  Invoking it with YES will show the account
David@0
   685
 * selection menus if they are needed.
David@0
   686
 */
David@0
   687
- (void)setAccountSelectionMenuVisibleIfNeeded:(BOOL)makeVisible
David@0
   688
{
David@0
   689
	//Hide or show the account selection view as requested
David@0
   690
	if (makeVisible) {
David@0
   691
		[self _createAccountSelectionView];
David@0
   692
	} else {
David@0
   693
		[self _destroyAccountSelectionView];
David@0
   694
	}
David@0
   695
}
David@0
   696
David@0
   697
/*!
David@0
   698
 * @brief Show the account selection view
David@0
   699
 */
David@0
   700
- (void)_createAccountSelectionView
David@0
   701
{
David@0
   702
	if (!view_accountSelection) {
David@0
   703
		NSRect	contentFrame = [splitView_textEntryHorizontal frame];
David@0
   704
David@0
   705
		//Create the account selection view and insert it into our window
David@0
   706
		view_accountSelection = [[AIAccountSelectionView alloc] initWithFrame:contentFrame];
David@0
   707
David@0
   708
		[view_accountSelection setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
David@0
   709
		
David@0
   710
		[self updateGradientColors];
David@0
   711
		
David@0
   712
		//Insert the account selection view at the top of our view
David@0
   713
		[[shelfView contentView] addSubview:view_accountSelection];
David@0
   714
		[view_accountSelection setChat:chat];
David@0
   715
David@0
   716
		[[NSNotificationCenter defaultCenter] addObserver:self
David@0
   717
												 selector:@selector(accountSelectionViewFrameDidChange:)
David@0
   718
													 name:AIViewFrameDidChangeNotification
David@0
   719
												   object:view_accountSelection];
David@0
   720
		
David@0
   721
		[self updateFramesForAccountSelectionView];
David@0
   722
			
David@0
   723
		//Redisplay everything
David@0
   724
		[[shelfView contentView] setNeedsDisplay:YES];
David@0
   725
	} else {
David@0
   726
		[view_accountSelection setChat:chat];
David@0
   727
	}
David@0
   728
}
David@0
   729
David@0
   730
/*!
David@0
   731
 * @brief Hide the account selection view
David@0
   732
 */
David@0
   733
- (void)_destroyAccountSelectionView
David@0
   734
{
David@0
   735
	if (view_accountSelection) {
David@0
   736
		//Remove the observer
David@0
   737
		[[NSNotificationCenter defaultCenter] removeObserver:self
David@0
   738
														name:AIViewFrameDidChangeNotification
David@0
   739
													  object:view_accountSelection];
David@0
   740
David@0
   741
		//Remove the account selection view from our window, clean it up
David@0
   742
		[view_accountSelection removeFromSuperview];
David@0
   743
		[view_accountSelection release]; view_accountSelection = nil;
David@0
   744
David@0
   745
		//Redisplay everything
David@0
   746
		[self updateFramesForAccountSelectionView];
David@0
   747
	}
David@0
   748
}
David@0
   749
David@0
   750
/*!
David@0
   751
 * @brief Position the account selection view, if it is present, and the messages/text entry splitview appropriately
David@0
   752
 */
David@0
   753
- (void)updateFramesForAccountSelectionView
David@0
   754
{
David@3
   755
	NSInteger 	accountSelectionHeight = (view_accountSelection ? [view_accountSelection frame].size.height : 0);
David@0
   756
David@0
   757
	if (view_accountSelection) {
David@0
   758
		[view_accountSelection setFrameOrigin:NSMakePoint(NSMinX([view_accountSelection frame]), NSHeight([[view_accountSelection superview] frame]) - accountSelectionHeight)];
David@0
   759
		[view_accountSelection setNeedsDisplay:YES];
David@0
   760
	}
David@0
   761
David@0
   762
	NSRect splitView_textEntryHorizontalFrame = [splitView_textEntryHorizontal frame];
David@0
   763
	splitView_textEntryHorizontalFrame.size.height = NSHeight([[splitView_textEntryHorizontal superview] frame]) - accountSelectionHeight - NSMinY(splitView_textEntryHorizontalFrame);
David@0
   764
	[splitView_textEntryHorizontal setFrame:splitView_textEntryHorizontalFrame];
David@0
   765
David@0
   766
	[splitView_textEntryHorizontal setNeedsDisplay:YES];
David@0
   767
}	
David@0
   768
David@0
   769
David@0
   770
//Text Entry -----------------------------------------------------------------------------------------------------------
David@0
   771
#pragma mark Text Entry
David@0
   772
/*!
David@0
   773
 * @brief Preferences changed, update sending keys
David@0
   774
 */
David@0
   775
- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object
David@0
   776
					preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
David@0
   777
{
zacw@1583
   778
	if ([group isEqualToString:PREF_GROUP_GENERAL]) {
zacw@1583
   779
		[textView_outgoing setSendOnReturn:[[prefDict objectForKey:SEND_ON_RETURN] boolValue]];
zacw@1583
   780
		[textView_outgoing setSendOnEnter:[[prefDict objectForKey:SEND_ON_ENTER] boolValue]];
zacw@1583
   781
	} else if ([group isEqualToString:PREF_GROUP_DUAL_WINDOW_INTERFACE]) {
zacw@1583
   782
		
zacw@1587
   783
		if (firstTime || [key isEqualToString:KEY_ENTRY_USER_LIST_MIN_WIDTH]) {
zacw@1587
   784
			NSInteger oldWidth = userListMinWidth;
zacw@1587
   785
			
zacw@1587
   786
			userListMinWidth = [[prefDict objectForKey:KEY_ENTRY_USER_LIST_MIN_WIDTH] integerValue];
zacw@1587
   787
			
zacw@1587
   788
			if (oldWidth != userListMinWidth) {
zacw@1587
   789
				[shelfView setShelfWidth:userListMinWidth];
zacw@1587
   790
			}
zacw@1587
   791
		}
zacw@1583
   792
		
zacw@1587
   793
		if (firstTime || [key isEqualToString:KEY_USER_LIST_ON_RIGHT]) {
zacw@1587
   794
			userListOnRight = [[prefDict objectForKey:KEY_USER_LIST_ON_RIGHT] boolValue];
zacw@1587
   795
zacw@1587
   796
			[shelfView setShelfOnRight:userListOnRight];
zacw@1583
   797
		}
zacw@1583
   798
	}
David@0
   799
}
David@0
   800
David@0
   801
/*!
David@0
   802
 * @brief Configure the text entry view
David@0
   803
 */
David@0
   804
- (void)_configureTextEntryView
David@0
   805
{	
David@0
   806
	//Configure the text entry view
David@0
   807
    [textView_outgoing setTarget:self action:@selector(sendMessage:)];
David@0
   808
David@0
   809
	//This is necessary for tab completion.
David@0
   810
	[textView_outgoing setDelegate:self];
David@0
   811
    
David@0
   812
	[textView_outgoing setTextContainerInset:NSMakeSize(0,2)];
David@0
   813
    if ([textView_outgoing respondsToSelector:@selector(setUsesFindPanel:)]) {
David@0
   814
		[textView_outgoing setUsesFindPanel:YES];
David@0
   815
    }
David@0
   816
	[textView_outgoing setClearOnEscape:YES];
David@95
   817
	[textView_outgoing setTypingAttributes:[adium.contentController defaultFormattingAttributes]];
David@0
   818
	
David@0
   819
	//User's choice of mininum height for their text entry view
David@95
   820
	entryMinHeight = [[adium.preferenceController preferenceForKey:KEY_ENTRY_TEXTVIEW_MIN_HEIGHT
David@3
   821
															   group:PREF_GROUP_DUAL_WINDOW_INTERFACE] integerValue];
David@0
   822
	if (entryMinHeight <= 0) entryMinHeight = [self _textEntryViewProperHeightIgnoringUserMininum:YES];
David@0
   823
	
David@0
   824
	//Associate the view with our message view so it knows which view to scroll in response to page up/down
David@0
   825
	//and other special key-presses.
David@0
   826
	[textView_outgoing setAssociatedView:[messageDisplayController messageScrollView]];
David@0
   827
	
David@0
   828
	//Associate the text entry view with our chat and inform Adium that it exists.
David@0
   829
	//This is necessary for text entry filters to work correctly.
David@0
   830
	[textView_outgoing setChat:chat];
David@0
   831
	
David@0
   832
    //Observe text entry view size changes so we can dynamically resize as the user enters text
David@0
   833
    [[NSNotificationCenter defaultCenter] addObserver:self
David@0
   834
											 selector:@selector(outgoingTextViewDesiredSizeDidChange:)
David@0
   835
												 name:AIViewDesiredSizeDidChangeNotification 
David@0
   836
											   object:textView_outgoing];
David@0
   837
David@0
   838
	[self _updateTextEntryViewHeight];
David@0
   839
}
David@0
   840
David@0
   841
/*!
David@0
   842
 * @brief Sets our text entry view as the first responder
David@0
   843
 */
David@0
   844
- (void)makeTextEntryViewFirstResponder
David@0
   845
{
David@0
   846
    [[textView_outgoing window] makeFirstResponder:textView_outgoing];
David@0
   847
}
David@0
   848
Evan@1430
   849
- (void)didSelect
Evan@1430
   850
{
Evan@1430
   851
	[self makeTextEntryViewFirstResponder];
Evan@1430
   852
	
Evan@1430
   853
	/* When we're selected, it's as if the user list controller is back in the window */
Evan@1430
   854
	[userListController contactListWasAddedBackToWindow];
Evan@1430
   855
}
Evan@1430
   856
Evan@1430
   857
- (void)willDeselect
Evan@1430
   858
{
Evan@1430
   859
	/* When we're deselected (backgrounded), the user list controller is effectively out of the window */
Evan@1430
   860
	[userListController contactListWillBeRemovedFromWindow];
zacw@1712
   861
	// Mark the current location in the message display for this change, if it's not an inactive-switch.
zacw@1712
   862
	if (messageWindowController.window.isKeyWindow) {
zacw@1712
   863
		[messageDisplayController markForFocusChange];
zacw@1712
   864
	}
Evan@1430
   865
}
Evan@1430
   866
David@0
   867
/*!
zacw@847
   868
 * @brief Returns the Text Entry View
zacw@847
   869
 *
zacw@847
   870
 * Make sure you need to use this. If you just need to enter text, see -addToTextEntryView:
zacw@847
   871
 */
zacw@847
   872
- (AIMessageEntryTextView *)textEntryView
zacw@847
   873
{
zacw@847
   874
	return textView_outgoing;
zacw@847
   875
}
zacw@847
   876
zacw@847
   877
/*!
David@0
   878
 * @brief Clear the message entry text view
David@0
   879
 */
David@0
   880
- (void)clearTextEntryView
David@0
   881
{
David@0
   882
	NSWritingDirection	writingDirection;
David@0
   883
David@0
   884
	writingDirection = [textView_outgoing baseWritingDirection];
David@0
   885
	
David@0
   886
	[textView_outgoing setString:@""];
David@95
   887
	[textView_outgoing setTypingAttributes:[adium.contentController defaultFormattingAttributes]];
David@0
   888
	
David@0
   889
	[textView_outgoing setBaseWritingDirection:writingDirection];	//Preserve the writing diraction
David@0
   890
David@0
   891
    [[NSNotificationCenter defaultCenter] postNotificationName:NSTextDidChangeNotification
David@0
   892
														object:textView_outgoing];
David@0
   893
}
David@0
   894
David@0
   895
/*!
David@0
   896
 * @brief Add text to the message entry text view 
David@0
   897
 *
David@0
   898
 * Adds the passed string to the entry text view at the insertion point.  If there is selected text in the view, it
David@0
   899
 * will be replaced.
David@0
   900
 */
David@0
   901
- (void)addToTextEntryView:(NSAttributedString *)inString
David@0
   902
{
David@0
   903
    [textView_outgoing insertText:inString];
David@0
   904
    [[NSNotificationCenter defaultCenter] postNotificationName:NSTextDidChangeNotification object:textView_outgoing];
David@0
   905
}
David@0
   906
David@0
   907
/*!
David@0
   908
 * @brief Add data to the message entry text view 
David@0
   909
 *
David@0
   910
 * Adds the passed pasteboard data to the entry text view at the insertion point.  If there is selected text in the
David@0
   911
 * view, it will be replaced.
David@0
   912
 */
David@0
   913
- (void)addDraggedDataToTextEntryView:(id <NSDraggingInfo>)draggingInfo
David@0
   914
{
David@0
   915
    [textView_outgoing performDragOperation:draggingInfo];
David@0
   916
    [[NSNotificationCenter defaultCenter] postNotificationName:NSTextDidChangeNotification object:textView_outgoing];
David@0
   917
}
David@0
   918
David@0
   919
/*!
David@0
   920
 * @brief Update the text entry view's height when its desired size changes
David@0
   921
 */
David@0
   922
- (void)outgoingTextViewDesiredSizeDidChange:(NSNotification *)notification
David@0
   923
{
David@0
   924
	[self _updateTextEntryViewHeight];
David@0
   925
}
David@0
   926
David@0
   927
- (void)tabViewDidChangeVisibility
David@0
   928
{
David@0
   929
	[self _updateTextEntryViewHeight];
David@0
   930
}
David@0
   931
David@0
   932
/* 
David@0
   933
 * @brief Update the height of our text entry view
David@0
   934
 *
David@0
   935
 * This method sets the height of the text entry view to the most ideal value, and adjusts the other views in our
David@0
   936
 * window to fill the remaining space.
David@0
   937
 */
David@0
   938
- (void)_updateTextEntryViewHeight
David@0
   939
{
David@3
   940
	NSInteger		height = [self _textEntryViewProperHeightIgnoringUserMininum:NO];
David@0
   941
	//Display the vertical scroller if our view is not tall enough to display all the entered text
David@0
   942
	[scrollView_outgoing setHasVerticalScroller:(height < [textView_outgoing desiredSize].height)];
David@0
   943
	
David@0
   944
	//First, set the text entry subview to the exact height we want
David@0
   945
	[[splitView_textEntryHorizontal subviewAtPosition:1] setMinDimension:height andMaxDimension:height];
David@0
   946
	[splitView_textEntryHorizontal adjustSubviews];
David@0
   947
	
David@0
   948
	//Now, allow it to be resized again between the text view's minimum size and the max size which is based on the splitview's height
David@0
   949
	[[splitView_textEntryHorizontal subviewAtPosition:1] setMinDimension:[self _textEntryViewProperHeightIgnoringUserMininum:YES] andMaxDimension:([splitView_textEntryHorizontal frame].size.height * MESSAGE_VIEW_MIN_HEIGHT_RATIO)];
David@0
   950
}
David@0
   951
David@0
   952
/*!
David@0
   953
 * @brief Returns the height our text entry view should be
David@0
   954
 *
David@0
   955
 * This method takes into account user preference, the amount of entered text, and the current window size to return
David@0
   956
 * a height which is most ideal for the text entry view.
David@0
   957
 *
David@0
   958
 * @param ignoreUserMininum If YES, the user's preference for mininum height will be ignored
David@0
   959
 */
David@3
   960
- (NSInteger)_textEntryViewProperHeightIgnoringUserMininum:(BOOL)ignoreUserMininum
David@0
   961
{
David@3
   962
	NSInteger dividerThickness = [splitView_textEntryHorizontal dividerThickness];
David@3
   963
	NSInteger allowedHeight = ([splitView_textEntryHorizontal frame].size.height / 2.0) - dividerThickness;
David@3
   964
	NSInteger	height;
David@0
   965
	
David@0
   966
	//Our primary goal is to display all the entered text
David@0
   967
	height = [textView_outgoing desiredSize].height;
David@0
   968
David@0
   969
	//But we must never fall below the user's prefered mininum or above the allowed height
David@0
   970
	if (!ignoreUserMininum && height < entryMinHeight) {
David@0
   971
		height = entryMinHeight;
David@0
   972
	}
David@0
   973
	if (height > allowedHeight) height = allowedHeight;
David@0
   974
	
David@0
   975
	return height;
David@0
   976
}
David@0
   977
David@0
   978
#pragma mark Autocompletion
zacw@2776
   979
- (BOOL)canTabCompleteForPartialWord:(NSString *)partialWord
zacw@2776
   980
{
zacw@2776
   981
	return ([self contactsMatchingBeginningString:partialWord].count > 0 ||
zacw@2776
   982
			[self.chat.displayName rangeOfString:partialWord options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound);
zacw@2776
   983
}
zacw@2776
   984
David@0
   985
/*!
David@0
   986
 * @brief Should the tab key cause an autocompletion if possible?
David@0
   987
 *
David@0
   988
 * We only tab to autocomplete for a group chat
David@0
   989
 */
David@0
   990
- (BOOL)textViewShouldTabComplete:(NSTextView *)inTextView
David@0
   991
{
zacw@2776
   992
	if (self.chat.isGroupChat) {
zacw@2776
   993
		NSRange completionRange = inTextView.rangeForUserCompletion;
zacw@2776
   994
		NSString *partialWord = [inTextView.textStorage.string substringWithRange:completionRange];
zacw@2776
   995
		return [self canTabCompleteForPartialWord:partialWord]; 
zacw@2776
   996
	}
zacw@2776
   997
	
zacw@2776
   998
	return NO;
zacw@2776
   999
}
zacw@2776
  1000
zacw@2776
  1001
- (NSRange)textView:(NSTextView *)inTextView rangeForCompletion:(NSRange)charRange
zacw@2776
  1002
{
zacw@2776
  1003
	if (self.chat.isGroupChat && charRange.location > 0) {
zacw@2776
  1004
		NSString *partialWord = nil;
zacw@2776
  1005
		NSString *allText = [inTextView.textStorage.string substringWithRange:NSMakeRange(0, NSMaxRange(charRange))];
zacw@2776
  1006
		NSRange whitespacePosition = [allText rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet] options:NSBackwardsSearch];
zacw@2776
  1007
		
zacw@2776
  1008
		if (whitespacePosition.location == NSNotFound) {
zacw@2776
  1009
			// We went back to the beginning of the string and still didn't find a whitespace; use the whole thing.
zacw@2776
  1010
			partialWord = allText;
zacw@2776
  1011
			whitespacePosition = NSMakeRange(0, 0);
zacw@2776
  1012
		} else {
zacw@2776
  1013
			// We found a whitespace, use from it until our current position.
zacw@2776
  1014
			partialWord = [allText substringWithRange:NSMakeRange(NSMaxRange(whitespacePosition), allText.length - NSMaxRange(whitespacePosition))];
zacw@2776
  1015
		}
zacw@2776
  1016
		
zacw@2776
  1017
		// If this matches any contacts or the room name, use this new range for autocompletion.
zacw@2776
  1018
		if ([self canTabCompleteForPartialWord:partialWord]) {
zacw@2776
  1019
			charRange = NSMakeRange(NSMaxRange(whitespacePosition), allText.length - NSMaxRange(whitespacePosition));
zacw@2776
  1020
		}
zacw@2776
  1021
	}
zacw@2776
  1022
	
zacw@2776
  1023
	return charRange;
zacw@2776
  1024
}
zacw@2776
  1025
zacw@2776
  1026
- (NSArray *)contactsMatchingBeginningString:(NSString *)partialWord
zacw@2776
  1027
{
zacw@2776
  1028
	NSMutableArray *contacts = [NSMutableArray array];
zacw@2776
  1029
	
zacw@2776
  1030
	for (AIListContact *listContact in self.chat) {
zacw@2776
  1031
		if ([listContact.UID rangeOfString:partialWord
zacw@2776
  1032
								   options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound ||
zacw@2776
  1033
			[listContact.formattedUID rangeOfString:partialWord
zacw@2776
  1034
											options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound ||
zacw@2776
  1035
			[listContact.displayName rangeOfString:partialWord
zacw@2776
  1036
										   options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound) {
zacw@2776
  1037
				[contacts addObject:listContact];
zacw@2776
  1038
		}
zacw@2776
  1039
	}
zacw@2776
  1040
	
zacw@2776
  1041
	return contacts;
David@0
  1042
}
David@0
  1043
David@3
  1044
- (NSArray *)textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index
David@0
  1045
{
zacw@2776
  1046
	NSMutableArray	*completions = nil;
David@0
  1047
	
David@812
  1048
	if (self.chat.isGroupChat) {
zacw@2776
  1049
		NSString *suffix = nil;
zacw@2776
  1050
		NSString *partialWord = [textView.textStorage.string substringWithRange:charRange];
zacw@963
  1051
		BOOL autoCompleteUID = [self.chat.account chatShouldAutocompleteUID:self.chat];
zacw@963
  1052
		
zacw@2776
  1053
		//At the start of a line, append ": "
David@0
  1054
		if (charRange.location == 0) {
David@0
  1055
			suffix = @": ";
David@0
  1056
		}
David@0
  1057
		
David@0
  1058
		completions = [NSMutableArray array];
zacw@2776
  1059
		
zacw@2776
  1060
		for (AIListContact *listContact in [self contactsMatchingBeginningString:partialWord]) {
zacw@2776
  1061
			NSString *displayName = [self.chat aliasForContact:listContact];
zacw@2776
  1062
			
zacw@2776
  1063
			if (!displayName)
zacw@2776
  1064
				displayName = autoCompleteUID ? listContact.formattedUID : listContact.displayName;
zacw@2776
  1065
			
zacw@2776
  1066
			[completions addObject:(suffix ? [displayName stringByAppendingString:suffix] : displayName)];
David@0
  1067
		}
zacw@1401
  1068
		
zacw@2776
  1069
		if ([self.chat.displayName rangeOfString:partialWord options:(NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound) {
zacw@2776
  1070
			[completions addObject:self.chat.displayName];
zacw@1401
  1071
		}
David@0
  1072
David@0
  1073
		if ([completions count]) {			
David@0
  1074
			*index = 0;
David@0
  1075
		}
David@0
  1076
	}
David@0
  1077
David@612
  1078
	return [completions count] ? completions : words;
David@0
  1079
}
David@0
  1080
David@0
  1081
//User List ------------------------------------------------------------------------------------------------------------
David@0
  1082
#pragma mark User List
David@0
  1083
/*!
zacw@1612
  1084
 * @brief Selected list objects
zacw@1612
  1085
 *
zacw@1612
  1086
 * An array of the list objects selected in the user list.
zacw@1612
  1087
 */
zacw@1612
  1088
- (NSArray *)selectedListObjects
zacw@1612
  1089
{
zacw@1612
  1090
	return [userListView arrayOfListObjects];
zacw@1612
  1091
}
zacw@1612
  1092
zacw@1612
  1093
/*!
zacw@1175
  1094
 * @brief Is the user list initially visible?
zacw@1175
  1095
 */
zacw@1175
  1096
- (BOOL)userListInitiallyVisible
zacw@1175
  1097
{
zacw@1175
  1098
	NSNumber *visibility = [adium.preferenceController preferenceForKey:[KEY_USER_LIST_VISIBLE_PREFIX stringByAppendingFormat:@"%@.%@",
zacw@1175
  1099
																		 chat.account.internalObjectID,
zacw@1175
  1100
																		 chat.name]
zacw@1175
  1101
																  group:PREF_GROUP_DUAL_WINDOW_INTERFACE];
zacw@1175
  1102
	
zacw@1175
  1103
	return visibility ? [visibility boolValue] : YES;
zacw@1175
  1104
}
zacw@1175
  1105
zacw@1175
  1106
/*!
David@0
  1107
 * @brief Set visibility of the user list
David@0
  1108
 */
David@0
  1109
- (void)setUserListVisible:(BOOL)inVisible
David@0
  1110
{
David@0
  1111
	if (inVisible) {
David@0
  1112
		[self _showUserListView];
David@0
  1113
	} else {
David@0
  1114
		[self _hideUserListView];
David@0
  1115
	}
zacw@1175
  1116
	
zacw@1175
  1117
	[adium.preferenceController setPreference:[NSNumber numberWithBool:inVisible]
zacw@1175
  1118
									   forKey:[KEY_USER_LIST_VISIBLE_PREFIX stringByAppendingFormat:@"%@.%@",
zacw@1175
  1119
											   chat.account.internalObjectID,
zacw@1175
  1120
											   chat.name]
zacw@1175
  1121
										group:PREF_GROUP_DUAL_WINDOW_INTERFACE];
David@0
  1122
}
David@0
  1123
David@0
  1124
/*!
David@0
  1125
 * @brief Returns YES if the user list is currently visible
David@0
  1126
 */
David@0
  1127
- (BOOL)userListVisible
David@0
  1128
{
David@0
  1129
	return [shelfView isShelfVisible];
David@0
  1130
}
David@0
  1131
Evan@231
  1132
/* @name	toggleUserlist
Evan@231
  1133
 * @brief	toggles the state of the userlist shelf
Evan@231
  1134
 */
Evan@231
  1135
- (void)toggleUserList
Evan@231
  1136
{
David@428
  1137
	if (chat.isGroupChat)
Evan@231
  1138
		[self setUserListVisible:![self userListVisible]];
Evan@231
  1139
}
Evan@231
  1140
zacw@1587
  1141
- (void)toggleUserListSide
zacw@1587
  1142
{
zacw@1587
  1143
	if(chat.isGroupChat) {
zacw@1587
  1144
		userListOnRight = !userListOnRight;
zacw@1587
  1145
		
zacw@1587
  1146
		// We'll update the actual side when this preference change is told to us.
zacw@1587
  1147
		[adium.preferenceController setPreference:[NSNumber numberWithInteger:userListOnRight]
zacw@1587
  1148
										   forKey:KEY_USER_LIST_ON_RIGHT
zacw@1587
  1149
											group:PREF_GROUP_DUAL_WINDOW_INTERFACE];
zacw@1587
  1150
	}
zacw@1587
  1151
}
zacw@1587
  1152
David@0
  1153
/*!
David@0
  1154
 * @brief Show the user list
David@0
  1155
 */
David@0
  1156
- (void)_showUserListView
David@0
  1157
{	
David@0
  1158
	[self setupShelfView];
zacw@1566
  1159
	
zacw@1566
  1160
	[shelfView setDrawShelfLine:NO];
David@0
  1161
David@0
  1162
	//Configure the user list
David@0
  1163
	[self _configureUserList];
zacw@1420
  1164
	[self updateUserCount];
David@0
  1165
David@0
  1166
	//Add the user list back to our window if it's missing
David@0
  1167
	if (![self userListVisible]) {
David@0
  1168
		[self _updateUserListViewWidth];
David@0
  1169
		
David@0
  1170
		if (retainingScrollViewUserList) {
David@0
  1171
			[scrollView_userList release];
David@0
  1172
			retainingScrollViewUserList = NO;
David@0
  1173
		}
David@0
  1174
	}
David@0
  1175
}
David@0
  1176
David@0
  1177
/*!
David@0
  1178
 * @brief Hide the user list.
David@0
  1179
 *
David@0
  1180
 * We gain responsibility for releasing scrollView_userList after we hide it
David@0
  1181
 */
David@0
  1182
- (void)_hideUserListView
David@0
  1183
{
David@0
  1184
	if ([self userListVisible]) {
David@0
  1185
		[scrollView_userList retain];
David@0
  1186
		[scrollView_userList removeFromSuperview];
David@0
  1187
		retainingScrollViewUserList = YES;
David@0
  1188
		
David@0
  1189
		[userListController release];
David@0
  1190
		userListController = nil;
David@0
  1191
	
David@0
  1192
		//need to collapse the splitview
David@0
  1193
		[shelfView setShelfIsVisible:NO];
David@0
  1194
	}
David@0
  1195
}
David@0
  1196
David@0
  1197
/*!
David@0
  1198
 * @brief Configure the user list
David@0
  1199
 *
David@0
  1200
 * Configures the user list view and prepares it for display.  If the user list is not being shown, this configuration
David@0
  1201
 * should be avoided for performance.
David@0
  1202
 */
David@0
  1203
- (void)_configureUserList
David@0
  1204
{
David@0
  1205
	if (!userListController) {
David@0
  1206
		NSDictionary	*themeDict = [NSDictionary dictionaryNamed:USERLIST_THEME forClass:[self class]];
David@0
  1207
		NSDictionary	*layoutDict = [NSDictionary dictionaryNamed:USERLIST_LAYOUT forClass:[self class]];
David@0
  1208
		
David@0
  1209
		//Create and configure a controller to manage the user list
David@0
  1210
		userListController = [[ESChatUserListController alloc] initWithContactListView:userListView
David@0
  1211
																		  inScrollView:scrollView_userList 
David@0
  1212
																			  delegate:self];
zacw@1395
  1213
		[userListController setContactListRoot:chat];
David@0
  1214
		[userListController updateLayoutFromPrefDict:layoutDict andThemeFromPrefDict:themeDict];
David@0
  1215
		[userListController setHideRoot:YES];
David@0
  1216
	}
David@0
  1217
}
David@0
  1218
David@0
  1219
/*!
David@0
  1220
 * @brief Update the user list in response to changes
David@0
  1221
 *
David@0
  1222
 * This method is invoked when the chat's participating contacts change.  In resopnse, it sets correct visibility of
David@0
  1223
 * the user list, and updates the displayed users.
David@0
  1224
 */
David@0
  1225
- (void)chatParticipatingListObjectsChanged:(NSNotification *)notification
David@0
  1226
{
David@0
  1227
    //Update the user list
David@0
  1228
	AILogWithSignature(@"%i, so %@ %@",[self userListVisible], ([self userListVisible] ? @"reloading" : @"not reloading"),
David@0
  1229
					   userListController);
zacw@1462
  1230
	
zacw@1462
  1231
	[chat resortParticipants];
zacw@1462
  1232
	
David@0
  1233
    if ([self userListVisible]) {
David@0
  1234
        [userListController reloadData];
zacw@1414
  1235
		
zacw@1420
  1236
		[self updateUserCount];
David@0
  1237
    }
David@0
  1238
}
David@0
  1239
zacw@1420
  1240
- (void)updateUserCount
zacw@1420
  1241
{
zacw@1420
  1242
	NSString *userCount = nil;
zacw@1420
  1243
	
zacw@1420
  1244
	if (self.chat.containedObjects.count == 1) {
zacw@1420
  1245
		userCount = AILocalizedString(@"1 user", nil);
zacw@1420
  1246
	} else {
zacw@1420
  1247
		userCount = AILocalizedString(@"%u users", nil);
zacw@1420
  1248
	}
zacw@1420
  1249
	
zacw@1420
  1250
	[shelfView setResizeThumbStringValue:[NSString stringWithFormat:userCount, self.chat.containedObjects.count]];
zacw@1420
  1251
}
zacw@1420
  1252
David@0
  1253
/*!
David@0
  1254
 * @brief The selection in the user list changed
David@0
  1255
 *
David@0
  1256
 * When the user list selection changes, we update the chat's "preferred list object", which is used
David@0
  1257
 * elsewhere to identify the currently 'selected' contact for Get Info, Messaging, etc.
David@0
  1258
 */
David@0
  1259
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
David@0
  1260
{
David@0
  1261
	if ([notification object] == userListView) {
David@188
  1262
		[chat setPreferredListObject:(AIListContact *)[userListView listObject]];
David@0
  1263
	}
David@0
  1264
}
David@0
  1265
David@0
  1266
/*!
David@0
  1267
 * @brief Perform default action on the selected user list object
David@0
  1268
 *
David@0
  1269
 * Here we could open a private message or display info for the user, however we perform no action
David@0
  1270
 * at the moment.
David@0
  1271
 */
David@0
  1272
- (void)performDefaultActionOnSelectedObject:(AIListObject *)listObject sender:(NSOutlineView *)sender
David@0
  1273
{
zacw@873
  1274
	if ([listObject isKindOfClass:[AIListContact class]]) {
zacw@873
  1275
		[adium.interfaceController setActiveChat:[adium.chatController openChatWithContact:(AIListContact *)listObject
zacw@873
  1276
												  onPreferredAccount:YES]];
zacw@873
  1277
	}
David@0
  1278
}
David@0
  1279
David@0
  1280
/* 
David@0
  1281
 * @brief Update the width of our user list view
David@0
  1282
 *
David@0
  1283
 * This method sets the width of the user list view to the most ideal value, and adjusts the other views in our
David@0
  1284
 * window to fill the remaining space.
David@0
  1285
 */
David@0
  1286
- (void)_updateUserListViewWidth
David@0
  1287
{
zacw@1583
  1288
	NSInteger		width = [self _userListViewProperWidth];
David@3
  1289
	NSInteger		widthWithDivider = 1 + width;	//resize bar effective width  
David@0
  1290
	NSRect	tempFrame;
David@0
  1291
David@0
  1292
	//Size the user list view to the desired width
David@0
  1293
	tempFrame = [scrollView_userList frame];
David@0
  1294
	[scrollView_userList setFrame:NSMakeRect([shelfView frame].size.width - width,
David@0
  1295
											 tempFrame.origin.y,
David@0
  1296
											 width,
David@0
  1297
											 tempFrame.size.height)];
David@0
  1298
	
David@0
  1299
	//Size the message view to fill the remaining space
David@0
  1300
	tempFrame = [scrollView_messages frame];
David@0
  1301
	[scrollView_messages setFrame:NSMakeRect(tempFrame.origin.x,
David@0
  1302
											 tempFrame.origin.y,
David@0
  1303
											 [shelfView frame].size.width - widthWithDivider,
David@0
  1304
											 tempFrame.size.height)];
David@0
  1305
David@0
  1306
	//Redisplay both views and the divider
David@0
  1307
	[shelfView setNeedsDisplay:YES];
David@0
  1308
}
David@0
  1309
David@0
  1310
/*!
David@0
  1311
 * @brief Returns the width our user list view should be
David@0
  1312
 *
David@0
  1313
 * This method takes into account user preference and the current window size to return a width which is most
David@0
  1314
 * ideal for the user list view.
David@0
  1315
 */
zacw@1583
  1316
- (NSInteger)_userListViewProperWidth
David@0
  1317
{
zacw@1583
  1318
	NSInteger dividerThickness = 1;
David@3
  1319
	NSInteger allowedWidth = ([shelfView frame].size.width / 2.0) - dividerThickness;
zacw@1679
  1320
	NSInteger width = userListMinWidth;
David@0
  1321
	
David@0
  1322
	//We must never fall below the user's prefered mininum or above the allowed width
David@0
  1323
	if (width > allowedWidth) width = allowedWidth;
David@0
  1324
David@0
  1325
	return width;
David@0
  1326
}
David@0
  1327
zacw@1583
  1328
-(CGFloat)shelfSplitView:(KNShelfSplitView *)shelfSplitView validateWidth:(CGFloat)proposedWidth
zacw@1583
  1329
{
zacw@1679
  1330
	if (userListMinWidth != proposedWidth) {
zacw@1679
  1331
		[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(saveUserListMinimumSize) object:nil];
zacw@1679
  1332
		[self performSelector:@selector(saveUserListMinimumSize) withObject:nil afterDelay:0.5];
zacw@1583
  1333
	}
zacw@1583
  1334
	
zacw@1679
  1335
	userListMinWidth = proposedWidth;
zacw@1583
  1336
	
zacw@1583
  1337
	return userListMinWidth;
zacw@1583
  1338
}
zacw@1583
  1339
David@0
  1340
//Split Views --------------------------------------------------------------------------------------------------
David@0
  1341
#pragma mark Split Views
David@0
  1342
David@0
  1343
// This method will be called after a RBSplitView is resized with setFrameSize: but before
David@0
  1344
// adjustSubviews is called on it.
David@3
  1345
- (void)splitView:(RBSplitView*)sender wasResizedFrom:(CGFloat)oldDimension to:(CGFloat)newDimension
David@0
  1346
{
David@0
  1347
	[[sender subviewAtPosition:0] setDimension:[[sender subviewAtPosition:0] dimension] + (newDimension - oldDimension)];
David@0
  1348
}
David@0
  1349
David@0
  1350
// This method will be called whenever a subview's frame is changed, usually from inside adjustSubviews' final loop.
David@0
  1351
// You'd normally use this to move some auxiliary view to keep it aligned with the subview.
David@0
  1352
- (void)splitView:(RBSplitView*)sender changedFrameOfSubview:(RBSplitSubview*)subview from:(NSRect)fromRect to:(NSRect)toRect
David@0
  1353
{
David@0
  1354
	if ([sender subviewAtPosition:1] == subview) {
David@0
  1355
		if ([sender isDragging])
David@0
  1356
			entryMinHeight = NSHeight(toRect);
David@0
  1357
	}
David@0
  1358
}
David@0
  1359
zacw@1583
  1360
- (void)splitViewDidHaveResizeDoubleClick:(KNShelfSplitView *)sender
zacw@874
  1361
{
zacw@874
  1362
	[self toggleUserList];
zacw@874
  1363
}
zacw@874
  1364
David@0
  1365
#pragma mark Shelfview
David@0
  1366
/* @name	setupShelfView
David@0
  1367
 * @brief	sets up shelfsplitview containing userlist & contentviews
David@0
  1368
 */
David@0
  1369
 -(void)setupShelfView
David@0
  1370
{
zacw@1583
  1371
	[shelfView setShelfWidth:userListMinWidth];
zacw@1565
  1372
	
David@0
  1373
	AILogWithSignature(@"ShelfView %@ (content view is %@) --> superview %@, in window %@; frame %@; content view %@ shelf view %@ in window %@",
David@0
  1374
					   shelfView, [shelfView contentView], [shelfView superview], [shelfView window], NSStringFromRect([[shelfView superview] frame]),
David@0
  1375
					   splitView_textEntryHorizontal,
David@0
  1376
					   scrollView_userList, [scrollView_userList window]);
catfish@1808
  1377
	[shelfView setContextButtonImage:[NSImage imageNamed:@"sidebarActionWidget"]];
zacw@1414
  1378
	
David@0
  1379
	[shelfView setShelfIsVisible:YES];
David@0
  1380
}
David@0
  1381
zacw@1612
  1382
-(NSMenu *)contextMenuForShelfSplitView:(KNShelfSplitView *)shelfSplitView
zacw@1612
  1383
{
zacw@1612
  1384
	return chat.actionMenu;
zacw@1612
  1385
}
zacw@1612
  1386
David@0
  1387
#pragma mark Undo
David@0
  1388
- (NSUndoManager *)undoManagerForTextView:(NSTextView *)aTextView
David@0
  1389
{
David@0
  1390
	if (!undoManager)
David@0
  1391
		undoManager = [[NSUndoManager alloc] init];
David@0
  1392
David@0
  1393
	return undoManager;
David@0
  1394
}
David@0
  1395
David@0
  1396
David@0
  1397
@end