root/tags/2D16/JVChatController.m

Revision 2684, 42.1 kB (checked in by eridius, 3 years ago)

Repeat [2558] for new whitespace

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