root/tags/2D9/JVChatController.m

Revision 2558, 40.3 kB (checked in by eridius, 3 years ago)

Remove all trailing whitespace from lines
Remove indentation on blank lines

Line 
1 #import <ChatCore/MVChatConnection.h>
2 #import <ChatCore/MVChatRoom.h>
3 #import <ChatCore/MVChatUser.h>
4 #import <ChatCore/NSAttributedStringAdditions.h>
5
6 #import "JVChatController.h"
7 #import "MVConnectionsController.h"
8 #import "JVChatWindowController.h"
9 #import "JVTabbedChatWindowController.h"
10 #import "JVNotificationController.h"
11 #import "JVChatTranscriptPanel.h"
12 #import "JVSmartTranscriptPanel.h"
13 #import "JVDirectChatPanel.h"
14 #import "JVChatRoomPanel.h"
15 #import "JVChatConsolePanel.h"
16 #import "JVChatMessage.h"
17 #import "JVChatRoomMember.h"
18
19 #import <libxml/parser.h>
20
21 static JVChatController *sharedInstance = nil;
22 static NSMenu *smartTranscriptMenu = nil;
23
24 @interface JVChatController (JVChatControllerPrivate)
25 - (void) _addWindowController:(JVChatWindowController *) windowController;
26 - (void) _addViewControllerToPreferedWindowController:(id <JVChatViewController>) controller andFocus:(BOOL) focus;
27 @end
28
29 #pragma mark -
30
31 @implementation JVChatController
32 + (JVChatController *) defaultManager {
33         extern JVChatController *sharedInstance;
34         return ( sharedInstance ? sharedInstance : ( sharedInstance = [[self alloc] init] ) );
35 }
36
37 + (NSMenu *) smartTranscriptMenu {
38         extern NSMenu *smartTranscriptMenu;
39         [self refreshSmartTranscriptMenu];
40         return smartTranscriptMenu;
41 }
42
43 + (void) refreshSmartTranscriptMenu {
44         extern NSMenu *smartTranscriptMenu;
45         if( ! smartTranscriptMenu ) smartTranscriptMenu = [[NSMenu alloc] initWithTitle:@""];
46
47         NSMenuItem *menuItem = nil;
48         NSEnumerator *enumerator = [[[[smartTranscriptMenu itemArray] copy] autorelease] objectEnumerator];
49         while( ( menuItem = [enumerator nextObject] ) )
50                 [smartTranscriptMenu removeItem:menuItem];
51
52         NSMutableArray *items = [NSMutableArray arrayWithArray:[[[self defaultManager] smartTranscripts] allObjects]];
53         [items sortUsingSelector:@selector( compare: )];
54
55         enumerator = [items objectEnumerator];
56
57         JVSmartTranscriptPanel *panel = nil;
58
59         while( ( panel = [enumerator nextObject] ) ) {
60                 menuItem = [[[NSMenuItem alloc] initWithTitle:[panel title] action:@selector( showView: ) keyEquivalent:@""] autorelease];
61                 if( [panel newMessagesWaiting] ) [menuItem setImage:[NSImage imageNamed:@"smartTranscriptTabActivity"]];
62                 else [menuItem setImage:[NSImage imageNamed:@"smartTranscriptTab"]];
63                 [menuItem setTarget:[self defaultManager]];
64                 [menuItem setRepresentedObject:panel];
65                 [smartTranscriptMenu addItem:menuItem];
66         }
67
68         if( ! [items count] ) {
69                 menuItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString( @"No Smart Transcripts", "no smart transcripts menu title" ) action:NULL keyEquivalent:@""] autorelease];
70                 [smartTranscriptMenu addItem:menuItem];
71         }
72
73         [smartTranscriptMenu addItem:[NSMenuItem separatorItem]];
74
75         menuItem = [[[NSMenuItem alloc] initWithTitle:NSLocalizedString( @"New Smart Transcript...", "new smart transcript menu title" ) action:@selector( _newSmartTranscript: ) keyEquivalent:@"n"] autorelease];
76         [menuItem setKeyEquivalentModifierMask:(NSCommandKeyMask | NSAlternateKeyMask)];
77         [menuItem setTarget:[JVChatController defaultManager]];
78         [smartTranscriptMenu addItem:menuItem];
79 }
80
81 #pragma mark -
82
83 - (id) init {
84         if( ( self = [super init] ) ) {
85                 _chatWindows = [[NSMutableArray array] retain];
86                 _chatControllers = [[NSMutableArray array] retain];
87
88                 NSEnumerator *smartTranscriptsEnumerator = [[[NSUserDefaults standardUserDefaults] objectForKey:@"JVSmartTranscripts"] objectEnumerator];
89                 NSData *archivedSmartTranscript = nil;
90                 while( ( archivedSmartTranscript = [smartTranscriptsEnumerator nextObject] ) )
91                         [_chatControllers addObject:[NSKeyedUnarchiver unarchiveObjectWithData:archivedSmartTranscript]];
92
93                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _joinedRoom: ) name:MVChatRoomJoinedNotification object:nil];
94                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _invitedToRoom: ) name:MVChatRoomInvitedNotification object:nil];
95                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _gotPrivateMessage: ) name:MVChatConnectionGotPrivateMessageNotification object:nil];
96         }
97         return self;
98 }
99
100 - (void) dealloc {
101         extern JVChatController *sharedInstance;
102
103         [[NSNotificationCenter defaultCenter] removeObserver:self];
104         if( self == sharedInstance ) sharedInstance = nil;
105
106         [_chatWindows release];
107         [_chatControllers release];
108
109         _chatWindows = nil;
110         _chatControllers = nil;
111
112         [super dealloc];
113 }
114
115 #pragma mark -
116
117 - (NSSet *) allChatWindowControllers {
118         return [[[NSSet setWithArray:_chatWindows] retain] autorelease];
119 }
120
121 - (JVChatWindowController *) newChatWindowController {
122         JVChatWindowController *windowController = nil;
123         if( [[NSUserDefaults standardUserDefaults] boolForKey:@"JVUseTabbedWindows"] )
124                 windowController = [[[JVTabbedChatWindowController alloc] init] autorelease];
125         else windowController = [[[JVChatWindowController alloc] init] autorelease];
126         [self _addWindowController:windowController];
127         return [[windowController retain] autorelease];
128 }
129
130 - (void) disposeChatWindowController:(JVChatWindowController *) controller {
131         NSParameterAssert( controller != nil );
132
133         id view = nil;
134         NSEnumerator *enumerator = [[controller allChatViewControllers] objectEnumerator];
135         while( ( view = [enumerator nextObject] ) )
136                 [self disposeViewController:view];
137
138         [_chatWindows removeObject:controller];
139 }
140
141 #pragma mark -
142
143 - (NSSet *) allChatViewControllers {
144         return [[[NSSet setWithArray:_chatControllers] retain] autorelease];
145 }
146
147 - (NSSet *) chatViewControllersWithConnection:(MVChatConnection *) connection {
148         NSMutableSet *ret = [NSMutableSet set];
149         id <JVChatViewController> item = nil;
150         NSEnumerator *enumerator = nil;
151
152         NSParameterAssert( connection != nil );
153
154         enumerator = [_chatControllers objectEnumerator];
155         while( ( item = [enumerator nextObject] ) )
156                 if( [item connection] == connection )
157                         [ret addObject:item];
158
159         return [[ret retain] autorelease];
160 }
161
162 - (NSSet *) chatViewControllersOfClass:(Class) class {
163         NSMutableSet *ret = [NSMutableSet set];
164         id <JVChatViewController> item = nil;
165         NSEnumerator *enumerator = nil;
166
167         NSParameterAssert( class != NULL );
168
169         enumerator = [_chatControllers objectEnumerator];
170         while( ( item = [enumerator nextObject] ) )
171                 if( [item isMemberOfClass:class] )
172                         [ret addObject:item];
173
174         return [[ret retain] autorelease];
175 }
176
177 - (NSSet *) chatViewControllersKindOfClass:(Class) class {
178         NSMutableSet *ret = [NSMutableSet set];
179         id <JVChatViewController> item = nil;
180         NSEnumerator *enumerator = nil;
181
182         NSParameterAssert( class != NULL );
183
184         enumerator = [_chatControllers objectEnumerator];
185         while( ( item = [enumerator nextObject] ) )
186                 if( [item isKindOfClass:class] )
187                         [ret addObject:item];
188
189         return [[ret retain] autorelease];
190 }
191
192 #pragma mark -
193
194 - (JVChatRoomPanel *) chatViewControllerForRoom:(MVChatRoom *) room ifExists:(BOOL) exists {
195         NSParameterAssert( room != nil );
196
197         NSEnumerator *enumerator = [_chatControllers objectEnumerator];
198         id ret = nil;
199
200         while( ( ret = [enumerator nextObject] ) )
201                 if( [ret isMemberOfClass:[JVChatRoomPanel class]] && [[ret target] isEqual:room] )
202                         break;
203
204         if( ! ret && ! exists ) {
205                 if( ( ret = [[[JVChatRoomPanel alloc] initWithTarget:room] autorelease] ) ) {
206                         [_chatControllers addObject:ret];
207                         [self _addViewControllerToPreferedWindowController:ret andFocus:YES];
208                 }
209         }
210
211         return [[ret retain] autorelease];
212 }
213
214 - (JVDirectChatPanel *) chatViewControllerForUser:(MVChatUser *) user ifExists:(BOOL) exists {
215         return [self chatViewControllerForUser:user ifExists:exists userInitiated:YES];
216 }
217
218 - (JVDirectChatPanel *) chatViewControllerForUser:(MVChatUser *) user ifExists:(BOOL) exists userInitiated:(BOOL) initiated {
219         NSParameterAssert( user != nil );
220
221         NSEnumerator *enumerator = [_chatControllers objectEnumerator];
222         id ret = nil;
223
224         while( ( ret = [enumerator nextObject] ) )
225                 if( [ret isMemberOfClass:[JVDirectChatPanel class]] && [[ret target] isEqual:user] )
226                         break;
227
228         if( ! ret && ! exists ) {
229                 if( ( ret = [[[JVDirectChatPanel alloc] initWithTarget:user] autorelease] ) ) {
230                         [_chatControllers addObject:ret];
231                         [self _addViewControllerToPreferedWindowController:ret andFocus:initiated];
232                 }
233         }
234
235         return [[ret retain] autorelease];
236 }
237
238 - (JVChatTranscriptPanel *) chatViewControllerForTranscript:(NSString *) filename {
239         id ret = nil;
240         if( ( ret = [[[JVChatTranscriptPanel alloc] initWithTranscript:filename] autorelease] ) ) {
241                 [_chatControllers addObject:ret];
242                 [self _addViewControllerToPreferedWindowController:ret andFocus:YES];
243         }
244         return [[ret retain] autorelease];
245 }
246
247 #pragma mark -
248
249 - (JVSmartTranscriptPanel *) newSmartTranscript {
250         JVSmartTranscriptPanel *ret = nil;
251         if( ( ret = [[[JVSmartTranscriptPanel alloc] initWithSettings:nil] autorelease] ) ) {
252                 [_chatControllers addObject:ret];
253                 [self _addViewControllerToPreferedWindowController:ret andFocus:YES];
254                 [ret editSettings:nil];
255         }
256         return [[ret retain] autorelease];
257 }
258
259 - (NSSet *) smartTranscripts {
260         return [self chatViewControllersOfClass:[JVSmartTranscriptPanel class]];
261 }
262
263 - (void) saveSmartTranscripts {
264         NSMutableArray *smartTranscripts = [NSMutableArray array];
265         NSEnumerator *enumerator = [[self smartTranscripts] objectEnumerator];
266         JVSmartTranscriptPanel *smartTranscript = nil;
267
268         while( ( smartTranscript = [enumerator nextObject] ) )
269                 [smartTranscripts addObject:[NSKeyedArchiver archivedDataWithRootObject:smartTranscript]];
270
271         [[self class] refreshSmartTranscriptMenu];
272         [[NSUserDefaults standardUserDefaults] setObject:smartTranscripts forKey:@"JVSmartTranscripts"];
273 }
274
275 - (void) disposeSmartTranscript:(JVSmartTranscriptPanel *) panel {
276         NSParameterAssert( panel != nil );
277
278         if( [panel respondsToSelector:@selector( willDispose )] )
279                 [(NSObject *)panel willDispose];
280
281         [[panel windowController] removeChatViewController:panel];
282         [_chatControllers removeObject:panel];
283
284         [self saveSmartTranscripts];
285 }
286
287 #pragma mark -
288
289 - (JVChatConsolePanel *) chatConsoleForConnection:(MVChatConnection *) connection ifExists:(BOOL) exists {
290         NSParameterAssert( connection != nil );
291
292         NSEnumerator *enumerator = [_chatControllers objectEnumerator];
293         id <JVChatViewController> ret = nil;
294
295         while( ( ret = [enumerator nextObject] ) )
296                 if( [ret isMemberOfClass:[JVChatConsolePanel class]] && [ret connection] == connection )
297                         break;
298
299         if( ! ret && ! exists ) {
300                 if( ( ret = [[[JVChatConsolePanel alloc] initWithConnection:connection] autorelease] ) ) {
301                         [_chatControllers addObject:ret];
302                         [self _addViewControllerToPreferedWindowController:ret andFocus:YES];
303                 }
304         }
305
306         return [[ret retain] autorelease];
307 }
308
309 #pragma mark -
310
311 - (void) disposeViewController:(id <JVChatViewController>) controller {
312         NSParameterAssert( controller != nil );
313
314         if( [controller respondsToSelector:@selector( willDispose )] )
315                 [(NSObject *)controller willDispose];
316
317         [[controller windowController] removeChatViewController:controller];
318
319         if( [controller isKindOfClass:[JVSmartTranscriptPanel class]] ) return;
320
321         [_chatControllers removeObject:controller];
322 }
323
324 - (void) detachViewController:(id <JVChatViewController>) controller {
325         NSParameterAssert( controller != nil );
326
327         [[controller retain] autorelease];
328
329         JVChatWindowController *windowController = [self newChatWindowController];
330         [[controller windowController] removeChatViewController:controller];
331
332         [[windowController window] setFrameUsingName:[NSString stringWithFormat:@"Chat Window %@", [controller identifier]]];
333
334         NSRect frame = [[windowController window] frame];
335         NSPoint point = [[windowController window] cascadeTopLeftFromPoint:NSMakePoint( NSMinX( frame ), NSMaxY( frame ) )];
336         [[windowController window] setFrameTopLeftPoint:point];
337
338         [[windowController window] saveFrameUsingName:[NSString stringWithFormat:@"Chat Window %@", [controller identifier]]];
339
340         [windowController addChatViewController:controller];
341 }
342
343 #pragma mark -
344
345 - (IBAction) showView:(id) sender {
346         id <JVChatViewController> view = [sender representedObject];
347         if( ! view ) return;
348         if( [view windowController] ) [[view windowController] showChatViewController:view];
349         else [self _addViewControllerToPreferedWindowController:view andFocus:YES];
350 }
351
352 - (IBAction) detachView:(id) sender {
353         id <JVChatViewController> view = [sender representedObject];
354         if( ! view ) return;
355         [self detachViewController:view];
356 }
357
358 #pragma mark -
359 #pragma mark Ignores
360
361 - (JVIgnoreMatchResult) shouldIgnoreUser:(MVChatUser *) user withMessage:(NSAttributedString *) message inView:(id <JVChatViewController>) view {
362         JVIgnoreMatchResult ignoreResult = JVNotIgnored;
363         NSEnumerator *renum = [[[MVConnectionsController defaultManager] ignoreRulesForConnection:[view connection]] objectEnumerator];
364         KAIgnoreRule *rule = nil;
365
366         while( ( ignoreResult == JVNotIgnored ) && ( ( rule = [renum nextObject] ) ) )
367                 ignoreResult = [rule matchUser:user message:[message string] inView:view];
368
369         return ignoreResult;
370 }
371 @end
372
373 #pragma mark -
374
375 @implementation JVChatController (JVChatControllerPrivate)
376 - (void) _joinedRoom:(NSNotification *) notification {
377         MVChatRoom *rm = [notification object];
378         if( ! [[MVConnectionsController defaultManager] managesConnection:[rm connection]] ) return;
379         JVChatRoomPanel *room = [self chatViewControllerForRoom:rm ifExists:NO];
380         [room joined];
381 }
382
383 - (void) _invitedToRoom:(NSNotification *) notification {
384         NSString *room = [[notification userInfo] objectForKey:@"room"];
385         MVChatUser *user = [[notification userInfo] objectForKey:@"user"];
386         MVChatConnection *connection = [notification object];
387
388         if( ! [[MVConnectionsController defaultManager] managesConnection:connection] ) return;
389
390         NSString *title = NSLocalizedString( @"Chat Room Invite", "member invited to room title" );
391         NSString *message = [NSString stringWithFormat:NSLocalizedString( @"You were invited to join %@ by %@. Would you like to accept this invitation and join this room?", "you were invited to join a chat room status message" ), room, [user nickname]];
392
393         if( NSRunInformationalAlertPanel( title, message, NSLocalizedString( @"Join", "join button" ), NSLocalizedString( @"Decline", "decline button" ), nil ) == NSOKButton )
394                 [connection joinChatRoomNamed:room];
395
396         NSMutableDictionary *context = [NSMutableDictionary dictionary];
397         [context setObject:NSLocalizedString( @"Invited to Chat", "bubble title invited to room" ) forKey:@"title"];
398         [context setObject:[NSString stringWithFormat:NSLocalizedString( @"You were invited to %@ by %@.", "bubble message invited to room" ), room, [user nickname]] forKey:@"description"];
399         [[JVNotificationController defaultManager] performNotification:@"JVChatRoomInvite" withContextInfo:context];
400 }
401
402 - (void) _gotPrivateMessage:(NSNotification *) notification {
403         BOOL hideFromUser = NO;
404         MVChatUser *user = [notification object];
405         NSData *message = [[notification userInfo] objectForKey:@"message"];
406
407         if( ! [[MVConnectionsController defaultManager] managesConnection:[user connection]] ) return;
408
409         if( [[[notification userInfo] objectForKey:@"notice"] boolValue] ) {
410                 MVChatConnection *connection = [user connection];
411
412                 if( ! [self chatViewControllerForUser:user ifExists:YES] )
413                         hideFromUser = YES;
414
415                 if( [[NSUserDefaults standardUserDefaults] boolForKey:@"JVChatAlwaysShowNotices"] )
416                         hideFromUser = NO;
417
418                 NSMutableDictionary *options = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:[connection encoding]], @"StringEncoding", [NSNumber numberWithBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"JVChatStripMessageColors"]], @"IgnoreFontColors", [NSNumber numberWithBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"JVChatStripMessageFormatting"]], @"IgnoreFontTraits", [NSFont systemFontOfSize:11.], @"BaseFont", nil];
419                 NSAttributedString *messageString = [NSAttributedString attributedStringWithChatFormat:message options:options];
420                 if( ! messageString ) {
421                         [options setObject:[NSNumber numberWithUnsignedInt:[NSString defaultCStringEncoding]] forKey:@"StringEncoding"];
422                         messageString = [NSAttributedString attributedStringWithChatFormat:message options:options];
423                 }
424
425                 if( [[user nickname] isEqualToString:@"NickServ"] || [[user nickname] isEqualToString:@"MemoServ"] ) {
426                         if( [[user nickname] isEqualToString:@"NickServ"] ) {
427                                 if( [[messageString string] rangeOfString:@"password accepted" options:NSCaseInsensitiveSearch].location != NSNotFound ) {
428                                         NSMutableDictionary *context = [NSMutableDictionary dictionary];
429                                         [context setObject:NSLocalizedString( @"You Have Been Identified", "identified bubble title" ) forKey:@"title"];
430                                         [context setObject:[NSString stringWithFormat:@"%@ on %@", [messageString string], [connection server]] forKey:@"description"];
431                                         [context setObject:[NSImage imageNamed:@"Keychain"] forKey:@"image"];
432                                         [[JVNotificationController defaultManager] performNotification:@"JVNickNameIdentifiedWithServer" withContextInfo:context];
433                                 }
434                         } else if( [[user nickname] isEqualToString:@"MemoServ"] ) {
435                                 if( [[messageString string] rangeOfString:@"new memo" options:NSCaseInsensitiveSearch].location != NSNotFound && [[messageString string] rangeOfString:@" no " options:NSCaseInsensitiveSearch].location == NSNotFound ) {
436                                         NSMutableDictionary *context = [NSMutableDictionary dictionary];
437                                         [context setObject:NSLocalizedString( @"You Have New Memos", "new memos bubble title" ) forKey:@"title"];
438                                         [context setObject:messageString forKey:@"description"];
439                                         [context setObject:[NSImage imageNamed:@"Stickies"] forKey:@"image"];
440                                         [context setObject:self forKey:@"target"];
441                                         [context setObject:NSStringFromSelector( @selector( _checkMemos: ) ) forKey:@"action"];
442                                         [context setObject:connection forKey:@"representedObject"];
443                                         [[JVNotificationController defaultManager] performNotification:@"JVNewMemosFromServer" withContextInfo:context];
444                                 }
445                         }
446                 } else {
447                         NSMutableDictionary *context = [NSMutableDictionary dictionary];
448                         [context setObject:[NSString stringWithFormat:NSLocalizedString( @"Notice from %@", "notice message from user title" ), [user displayName]] forKey:@"title"];
449                         [context setObject:messageString forKey:@"description"];
450                         [context setObject:[NSImage imageNamed:@"activityNewImportant"] forKey:@"image"];
451                         NSString *type = ( hideFromUser ? @"JVChatUnhandledNoticeMessage" : @"JVChatNoticeMessage" );
452                         [[JVNotificationController defaultManager] performNotification:type withContextInfo:context];
453                 }
454         }
455
456         if( ! hideFromUser && ( [self shouldIgnoreUser:user withMessage:nil inView:nil] == JVNotIgnored ) ) {
457                 JVDirectChatPanel *controller = [self chatViewControllerForUser:user ifExists:NO userInitiated:NO];
458                 JVChatMessageType type = ( [[[notification userInfo] objectForKey:@"notice"] boolValue] ? JVChatMessageNoticeType : JVChatMessageNormalType );
459                 [controller addMessageToDisplay:message fromUser:user asAction:[[[notification userInfo] objectForKey:@"action"] boolValue] withIdentifier:[[notification userInfo] objectForKey:@"identifier"] andType:type];
460         }
461 }
462
463 - (void) _addWindowController:(JVChatWindowController *) windowController {
464         [_chatWindows addObject:windowController];
465 }
466
467 - (void) _addViewControllerToPreferedWindowController:(id <JVChatViewController>) controller andFocus:(BOOL) focus {
468         JVChatWindowController *windowController = nil;
469         id <JVChatViewController> viewController = nil;
470         Class modeClass = NULL;
471         NSEnumerator *enumerator = nil;
472         BOOL kindOfClass = NO;
473
474         NSParameterAssert( controller != nil );
475
476         int mode = [[NSUserDefaults standardUserDefaults] integerForKey:[NSStringFromClass( [controller class] ) stringByAppendingString:@"PreferredOpenMode"]];
477         BOOL groupByServer = (BOOL) mode & 32;
478         mode &= ~32;
479
480         switch( mode ) {
481         default:
482         case 0:
483         case 1:
484                 enumerator = [_chatWindows objectEnumerator];
485                 while( ( windowController = [enumerator nextObject] ) )
486                         if( [[windowController window] isMainWindow] || ! [[NSApplication sharedApplication] isActive] )
487                                 break;
488                 if( ! windowController ) windowController = [_chatWindows lastObject];
489                 break;
490         case 2:
491                 modeClass = [JVChatRoomPanel class];
492                 goto groupByClass;
493         case 3:
494                 modeClass = [JVDirectChatPanel class];
495                 goto groupByClass;
496         case 4:
497                 modeClass = [JVChatTranscriptPanel class];
498                 goto groupByClass;
499         case 5:
500                 modeClass = [JVChatConsolePanel class];
501                 goto groupByClass;
502         case 6:
503                 modeClass = [JVDirectChatPanel class];
504                 kindOfClass = YES;
505                 goto groupByClass;
506         groupByClass:
507                 if( groupByServer ) {
508                         if( kindOfClass ) enumerator = [[self chatViewControllersKindOfClass:modeClass] objectEnumerator];
509                         else enumerator = [[self chatViewControllersOfClass:modeClass] objectEnumerator];
510                         while( ( viewController = [enumerator nextObject] ) ) {
511                                 if( controller != viewController && [viewController connection] == [controller connection] ) {
512                                         windowController = [viewController windowController];
513                                         if( windowController ) break;
514                                 }
515                         }
516                 } else {
517                         if( kindOfClass ) enumerator = [[self chatViewControllersKindOfClass:modeClass] objectEnumerator];
518                         else enumerator = [[self chatViewControllersOfClass:modeClass] objectEnumerator];
519                         while( ( viewController = [enumerator nextObject] ) ) {
520                                 if( controller != viewController ) {
521                                         windowController = [viewController windowController];
522                                         if( windowController ) break;
523                                 }
524                         }
525                 }
526                 break;
527         }
528
529         if( ! windowController ) windowController = [self newChatWindowController];
530
531         if( [[[NSApplication sharedApplication] currentEvent] modifierFlags] & NSCommandKeyMask ) focus = NO;
532         if( [[[NSApplication sharedApplication] currentEvent] modifierFlags] & NSShiftKeyMask ) focus = NO;
533
534         [windowController addChatViewController:controller];
535         if( focus || [[windowController allChatViewControllers] count] == 1 ) {
536                 [windowController showChatViewController:controller];
537                 if( focus ) [[windowController window] makeKeyAndOrderFront:nil];
538         }
539
540         if( ! [[windowController window] isVisible] )
541                 [[windowController window] orderFront:nil];
542 }
543
544 - (IBAction) _checkMemos:(id) sender {
545         MVChatConnection *connection = [sender representedObject];
546         NSAttributedString *message = [[[NSAttributedString alloc] initWithString:@"read all"] autorelease];
547         MVChatUser *user = [connection chatUserWithUniqueIdentifier:@"MemoServ"];
548         [user sendMessage:message withEncoding:[connection encoding] asAction:NO];
549         [self chatViewControllerForUser:user ifExists:NO];
550 }
551
552 - (IBAction) _newSmartTranscript:(id) sender {
553         [[JVChatController defaultManager] newSmartTranscript];
554 }
555 @end
556
557 #pragma mark -
558
559 @implementation JVChatTranscriptPanel (JVChatTranscriptObjectSpecifier)
560 - (NSScriptObjectSpecifier *) objectSpecifier {
561         id classDescription = [NSClassDescription classDescriptionForClass:[NSApplication class]];
562         NSScriptObjectSpecifier *container = [[NSApplication sharedApplication] objectSpecifier];
563         return [[[NSUniqueIDSpecifier alloc] initWithContainerClassDescription:classDescription containerSpecifier:container key:@"chatTranscripts" uniqueID:[self uniqueIdentifier]] autorelease];
564 }
565 @end
566
567 #pragma mark -
568
569 @implementation JVDirectChatPanel (JVDirectChatPanelObjectSpecifier)
570 - (NSScriptObjectSpecifier *) objectSpecifier {
571         id classDescription = [NSClassDescription classDescriptionForClass:[NSApplication class]];
572         NSScriptObjectSpecifier *container = [[NSApplication sharedApplication] objectSpecifier];
573         return [[[NSUniqueIDSpecifier alloc] initWithContainerClassDescription:classDescription containerSpecifier:container key:@"directChats" uniqueID:[self uniqueIdentifier]] autorelease];
574 }
575 @end
576
577 #pragma mark -
578
579 @implementation JVChatRoomPanel (JVChatRoomPanelObjectSpecifier)
580 - (NSScriptObjectSpecifier *) objectSpecifier {
581         id classDescription = [NSClassDescription classDescriptionForClass:[NSApplication class]];
582         NSScriptObjectSpecifier *container = [[NSApplication sharedApplication] objectSpecifier];
583         return [[[NSUniqueIDSpecifier alloc] initWithContainerClassDescription:classDescription containerSpecifier:container key:@"chatRooms" uniqueID:[self uniqueIdentifier]] autorelease];
584 }
585 @end
586
587 #pragma mark -
588
589 @implementation JVChatConsolePanel (JVChatConsolePanelObjectSpecifier)
590 - (NSScriptObjectSpecifier *) objectSpecifier {
591         id classDescription = [NSClassDescription classDescriptionForClass:[NSApplication class]];
592         NSScriptObjectSpecifier *container = [[NSApplication sharedApplication] objectSpecifier];
593         return [[[NSUniqueIDSpecifier alloc] initWithContainerClassDescription:classDescription containerSpecifier:container key:@"chatConsoles" uniqueID:[self uniqueIdentifier]] autorelease];
594 }
595 @end
596
597 #pragma mark -
598
599 @interface JVStartChatScriptCommand : NSScriptCommand {}
600 @end
601
602 #pragma mark -
603
604 @implementation JVStartChatScriptCommand
605 - (id) performDefaultImplementation {
606         NSDictionary *args = [self evaluatedArguments];
607         id target = [args objectForKey:@"target"];
608
609         if( target && [target isKindOfClass:[NSString class]] ) {
610                 MVChatConnection *connection = [args objectForKey:@"connection"];
611                 if( ! connection ) {
612                         [self setScriptErrorNumber:1000];
613                         [self setScriptErrorString:@"The connection parameter was missing and is required when the user is a nickname string."];
614                         return nil;
615                 }
616
617                 if( ! [connection isConnected] ) {
618                         [self setScriptErrorNumber:1000];
619                         [self setScriptErrorString:@"The connection needs to be connected before you can find a chat user by their nickname."];
620                         return nil;
621                 }
622
623                 NSString *nickname = target;
624                 target = [[connection chatUsersWithNickname:nickname] anyObject];
625
626                 if( ! target ) {
627                         [self setScriptErrorNumber:1000];
628                         [self setScriptErrorString:[NSString stringWithFormat:@"The connection did not find a chat user with the nickname \"%@\".", nickname]];
629                         return nil;
630                 }
631         }
632
633         if( ! target || ( ! [target isKindOfClass:[MVChatUser class]] && ! [target isKindOfClass:[JVChatRoomMember class]] ) ) {
634                 [self setScriptErrorNumber:1000];
635                 [self setScriptErrorString:@"The \"for\" parameter was missing or not a chat user or member object."];
636                 return nil;
637         }
638
639         if( [target isKindOfClass:[MVChatUser class]] && [(MVChatUser *)target type] == MVChatWildcardUserType ) {
640                 [self setScriptErrorNumber:1000];
641                 [self setScriptErrorString:@"The \"for\" parameter cannot be a wildcard user."];
642                 return nil;
643         }
644
645         if( [target isKindOfClass:[JVChatRoomMember class]] )
646                 target = [(JVChatRoomMember *)target user];
647
648         JVDirectChatPanel *panel = [[JVChatController defaultManager] chatViewControllerForUser:target ifExists:NO];
649         [[panel windowController] showChatViewController:panel];
650
651         return panel;
652 }
653 @end
654
655 #pragma mark -
656
657 @implementation NSApplication (JVChatControllerScripting)
658 - (void) scriptErrorChantAddToChatViews {
659         [[NSScriptCommand currentCommand] setScriptErrorString:@"Can't add, insert or replace a panel at the application level."];
660         [[NSScriptCommand currentCommand] setScriptErrorNumber:1000];
661 }
662
663 #pragma mark -
664
665 - (NSArray *) chatViews {
666         return [[[JVChatController defaultManager] allChatViewControllers] allObjects];
667 }
668
669 - (id <JVChatViewController>) valueInChatViewsAtIndex:(unsigned) index {
670         return [[self chatViews] objectAtIndex:index];
671 }
672
673 - (id <JVChatViewController>) valueInChatViewsWithUniqueID:(id) identifier {
674         NSEnumerator *enumerator = [[[JVChatController defaultManager] allChatViewControllers] objectEnumerator];
675         id <JVChatViewController, JVChatListItemScripting> view = nil;
676
677         while( ( view = [enumerator nextObject] ) )
678                 if( [view conformsToProtocol:@protocol( JVChatListItemScripting )] &&
679                         [[view uniqueIdentifier] isEqual:identifier] ) return view;
680
681         return nil;
682 }
683
684 - (id <JVChatViewController>) valueInChatViewsWithName:(NSString *) name {
685         NSEnumerator *enumerator = [[[JVChatController defaultManager] allChatViewControllers] objectEnumerator];
686         id <JVChatViewController> view = nil;
687
688         while( ( view = [enumerator nextObject] ) )
689                 if( [[view title] isEqualToString:name] )
690                         return view;
691
692         return nil;
693 }
694
695 - (void) addInChatViews:(id <JVChatViewController>) view {
696         [self scriptErrorChantAddToChatViews];
697 }
698
699 - (void) insertInChatViews:(id <JVChatViewController>) view {
700         [self scriptErrorChantAddToChatViews];
701 }
702
703 - (void) insertInChatViews:(id <JVChatViewController>) view atIndex:(unsigned) index {
704         [self scriptErrorChantAddToChatViews];
705 }
706
707 - (void) removeFromChatViewsAtIndex:(unsigned) index {
708         id <JVChatViewController> view = [[self chatViews] objectAtIndex:index];
709         if( view ) [[JVChatController defaultManager] disposeViewController:view];
710 }
711
712 - (void) replaceInChatViews:(id <JVChatViewController>) view atIndex:(unsigned) index {
713         [self scriptErrorChantAddToChatViews];
714 }
715
716 #pragma mark -
717
718 - (NSArray *) chatViewsWithClass:(Class) class {
719         return [[[JVChatController defaultManager] chatViewControllersOfClass:class] allObjects];
720 }
721
722 - (id <JVChatViewController>) valueInChatViewsAtIndex:(unsigned) index withClass:(Class) class {
723         return [[self chatViewsWithClass:class] objectAtIndex:index];
724 }
725
726 - (id <JVChatViewController>) valueInChatViewsWithUniqueID:(id) identifier andClass:(Class) class {
727         return [self valueInChatViewsWithUniqueID:identifier];
728 }
729
730 - (id <JVChatViewController>) valueInChatViewsWithName:(NSString *) name andClass:(Class) class {
731         NSEnumerator *enumerator = [[self chatViewsWithClass:class] objectEnumerator];
732         id <JVChatViewController> view = nil;
733
734         while( ( view = [enumerator nextObject] ) )
735                 if( [[view title] isEqualToString:name] )
736                         return view;
737
738         return nil;
739 }
740
741 - (void) removeFromChatViewsAtIndex:(unsigned) index withClass:(Class) class {
742         id <JVChatViewController> view = [[self chatViewsWithClass:class] objectAtIndex:index];
743         if( view ) [[JVChatController defaultManager] disposeViewController:view];
744 }
745
746 #pragma mark -
747
748 - (NSArray *) chatRooms {
749         return [self chatViewsWithClass:[JVChatRoomPanel class]];
750 }
751
752 - (id <JVChatViewController>) valueInChatRoomsAtIndex:(unsigned) index {
753         return [self