|
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 |