root/WiredClient/trunk/WCChat.m

Revision 5319, 47.1 kB (checked in by morris, 6 months ago)

Ignore user joins coming before the user list

  • Property svn:keywords set to author date id revision
Line 
1 /* $Id$ */
2
3 /*
4  *  Copyright (c) 2003-2007 Axel Andersson
5  *  All rights reserved.
6  *
7  *  Redistribution and use in source and binary forms, with or without
8  *  modification, are permitted provided that the following conditions
9  *  are met:
10  *  1. Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  *  2. Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
20  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
24  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
25  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import "WCAccount.h"
30 #import "WCAccountEditor.h"
31 #import "WCAccounts.h"
32 #import "WCApplicationController.h"
33 #import "WCChat.h"
34 #import "WCMessage.h"
35 #import "WCMessages.h"
36 #import "WCPreferences.h"
37 #import "WCServer.h"
38 #import "WCStats.h"
39 #import "WCTopic.h"
40 #import "WCUser.h"
41 #import "WCUserCell.h"
42 #import "WCUserInfo.h"
43
44 #define WCPublicChatID                          1
45
46 #define WCLastChatFormat                        @"WCLastChatFormat"
47 #define WCLastChatEncoding                      @"WCLastChatEncoding"
48
49
50 enum _WCChatFormat {
51         WCChatPlainText,
52         WCChatRTF,
53         WCChatRTFD,
54 };
55 typedef enum _WCChatFormat                      WCChatFormat;
56
57
58 @interface WCChat(Private)
59
60 - (void)_update;
61 - (void)_updateTopic;
62 - (void)_updateSaveChatForPanel:(NSSavePanel *)savePanel;
63
64 - (void)_printString:(NSString *)message;
65 - (void)_printTimestamp;
66 - (void)_printUserJoin:(WCUser *)user;
67 - (void)_printUserLeave:(WCUser *)user;
68 - (void)_printUserChange:(WCUser *)user nick:(NSString *)nick;
69 - (void)_printUserChange:(WCUser *)user status:(NSString *)status;
70 - (void)_printUserKick:(WCUser *)victim by:(WCUser *)killer message:(NSString *)message;
71 - (void)_printUserBan:(WCUser *)victim by:(WCUser *)killer message:(NSString *)message;
72 - (void)_printChat:(NSString *)chat by:(WCUser *)user;
73 - (void)_printActionChat:(NSString *)chat by:(WCUser *)user;
74
75 - (NSArray *)_commands;
76 - (BOOL)_runCommand:(NSString *)command;
77
78 - (NSString *)_stringByCompletingString:(NSString *)string;
79 - (NSString *)_stringByDecomposingAttributedString:(NSAttributedString *)attributedString;
80
81 - (BOOL)_isHighlightedChat:(NSString *)chat;
82
83 @end
84
85
86 @implementation WCChat(Private)
87
88 - (void)_update {
89         NSFont          *font;
90         NSColor         *color;
91
92         font = [NSUnarchiver unarchiveObjectWithData:[WCSettings objectForKey:WCChatFont]];
93
94         if(![[_chatOutputTextView font] isEqualTo:font]) {
95                 [_chatOutputTextView setFont:font];
96                 [_chatInputTextView setFont:font];
97                 [_setTopicTextView setFont:font];
98         }
99        
100         color = [NSUnarchiver unarchiveObjectWithData:[WCSettings objectForKey:WCChatBackgroundColor]];
101
102         if(![[_chatOutputTextView backgroundColor] isEqualTo:color]) {
103                 [_chatOutputTextView setBackgroundColor:color];
104                 [_chatInputTextView setBackgroundColor:color];
105                 [_setTopicTextView setBackgroundColor:color];
106         }
107
108         color = [NSUnarchiver unarchiveObjectWithData:[WCSettings objectForKey:WCChatTextColor]];
109
110         if(![[_chatOutputTextView textColor] isEqualTo:color]) {
111                 [_chatOutputTextView setTextColor:color];
112                 [_chatInputTextView setTextColor:color];
113                 [_chatInputTextView setInsertionPointColor:color];
114                 [_setTopicTextView setTextColor:color];
115                 [_setTopicTextView setInsertionPointColor:color];
116
117                 [_chatOutputTextView setString:[_chatOutputTextView string] withFilter:_chatFilter];
118         }
119        
120         [_chatOutputTextView setLinkTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
121                 [NSUnarchiver unarchiveObjectWithData:[WCSettings objectForKey:WCChatURLsColor]],
122                         NSForegroundColorAttributeName,
123                 [NSNumber numberWithInt:NSSingleUnderlineStyle],
124                         NSUnderlineStyleAttributeName,
125                 NULL]];
126
127         [_userListTableView setFont:[NSUnarchiver unarchiveObjectWithData:[WCSettings objectForKey:WCChatUserListFont]]];
128         [_userListTableView setUsesAlternatingRowBackgroundColors:[WCSettings boolForKey:WCChatUserListAlternateRows]];
129        
130         switch([WCSettings intForKey:WCChatUserListIconSize]) {
131                 case WCChatUserListIconSizeLarge:
132                         [_userListTableView setRowHeight:35.0];
133                        
134                         [[_nickTableColumn dataCell] setControlSize:NSRegularControlSize];
135                         break;
136
137                 case WCChatUserListIconSizeSmall:
138                         [_userListTableView setRowHeight:17.0];
139
140                         [[_nickTableColumn dataCell] setControlSize:NSSmallControlSize];
141                         break;
142         }
143        
144         [_chatOutputTextView setNeedsDisplay:YES];
145         [_chatInputTextView setNeedsDisplay:YES];
146         [_setTopicTextView setNeedsDisplay:YES];
147         [_userListTableView setNeedsDisplay:YES];
148 }
149
150
151
152 - (void)_updateTopic {
153         NSMutableAttributedString       *string;
154        
155         if(_topic) {
156                 string = [NSMutableAttributedString attributedStringWithString:[_topic topic]];
157                
158                 [_topicTextField setToolTip:[_topic topic]];
159                 [_topicTextField setAttributedStringValue:[string attributedStringByApplyingFilter:_topicFilter]];
160                 [_topicNickTextField setStringValue:[NSSWF:
161                         NSLS(@"%@ %C %@", @"Chat topic set by (nick, time)"),
162                         [_topic nick],
163                         0x2014,
164                         [_topicDateFormatter stringFromDate:[_topic date]]]];
165         } else {
166                 [_topicTextField setToolTip:NULL];
167                 [_topicTextField setStringValue:@""];
168                 [_topicNickTextField setStringValue:@""];
169         }
170 }
171
172
173
174 - (void)_updateSaveChatForPanel:(NSSavePanel *)savePanel {
175         WCChatFormat            format;
176        
177         format = [_saveChatFileFormatPopUpButton tagOfSelectedItem];
178        
179         switch(format) {
180                 case WCChatPlainText:
181                         [savePanel setRequiredFileType:@"txt"];
182                         break;
183                        
184                 case WCChatRTF:
185                         [savePanel setRequiredFileType:@"rtf"];
186                         break;
187
188                 case WCChatRTFD:
189                         [savePanel setRequiredFileType:@"rtfd"];
190                         break;
191         }
192
193         [_saveChatPlainTextEncodingPopUpButton setEnabled:(format == WCChatPlainText)];
194 }
195
196
197
198 #pragma mark -
199
200 - (void)_printString:(NSString *)string {
201         float           position;
202        
203         position = [[_chatOutputScrollView verticalScroller] floatValue];
204        
205         if([[_chatOutputTextView textStorage] length] > 0)
206                 [[[_chatOutputTextView textStorage] mutableString] appendString:@"\n"];
207        
208         [_chatOutputTextView appendString:string withFilter:_chatFilter];
209        
210         if(position == 1.0)
211                 [_chatOutputTextView performSelectorOnce:@selector(scrollToBottom) withObject:NULL afterDelay:0.05];
212 }
213
214
215
216 - (void)_printTimestamp {
217         NSDate                  *date;
218         NSTimeInterval  interval;
219        
220         if(!_timestamp)
221                 _timestamp = [[NSDate date] retain];
222        
223         interval = [[WCSettings objectForKey:WCTimestampChatInterval] doubleValue];
224         date = [NSDate dateWithTimeIntervalSinceNow:-interval];
225        
226         if([date compare:_timestamp] == NSOrderedDescending) {
227                 [self printEvent:[_timestampDateFormatter stringFromDate:[NSDate date]]];
228                
229                 [_timestamp release];
230                 _timestamp = [[NSDate date] retain];
231         }
232 }
233
234
235
236 - (void)_printUserJoin:(WCUser *)user {
237         [self printEvent:[NSSWF:
238                 NSLS(@"%@ has joined", @"Client has joined message (nick)"),
239                 [user nick]]];
240 }
241
242
243
244 - (void)_printUserLeave:(WCUser *)user {
245         [self printEvent:[NSSWF:
246                 NSLS(@"%@ has left", @"Client has left message (nick)"),
247                 [user nick]]];
248 }
249
250
251
252 - (void)_printUserChange:(WCUser *)user nick:(NSString *)nick {
253         [self printEvent:[NSSWF:
254                 NSLS(@"%@ is now known as %@", @"Client rename message (oldnick, newnick)"),
255                 [user nick],
256                 nick]];
257 }
258
259
260
261 - (void)_printUserChange:(WCUser *)user status:(NSString *)status {
262         [self printEvent:[NSSWF:
263                 NSLS(@"%@ changed status to %@", @"Client status changed message (nick, status)"),
264                 [user nick],
265                 status]];
266 }
267
268
269
270 - (void)_printUserKick:(WCUser *)victim by:(WCUser *)killer message:(NSString *)message {
271         if([message length] > 0) {
272                 [self printEvent:[NSSWF:
273                         NSLS(@"%@ was kicked by %@ (%@)", @"Client kicked message (victim, killer, message)"),
274                         [victim nick],
275                         [killer nick],
276                         message]];
277         } else {
278                 [self printEvent:[NSSWF:
279                         NSLS(@"%@ was kicked by %@", @"Client kicked message (victim, killer)"),
280                         [victim nick],
281                         [killer nick]]];
282         }
283 }
284
285
286
287 - (void)_printUserBan:(WCUser *)victim by:(WCUser *)killer message:(NSString *)message {
288         if([message length] > 0) {
289                 [self printEvent:[NSSWF:
290                         NSLS(@"%@ was banned by %@ (%@)", @"Client banned message (victim, killer, message)"),
291                         [victim nick],
292                         [killer nick],
293                         message]];
294         } else {
295                 [self printEvent:[NSSWF:
296                         NSLS(@"%@ was banned by %@", @"Client banned message (victim, killer)"),
297                         [victim nick],
298                         [killer nick]]];
299         }
300 }
301
302
303
304 - (void)_printChat:(NSString *)chat by:(WCUser *)user {
305         NSString        *output, *nick;
306         NSInteger       offset, length;
307        
308         switch([WCSettings intForKey:WCChatStyle]) {
309                 case WCChatStyleWired:
310                 default:
311                         offset = [WCSettings boolForKey:WCTimestampEveryLine] ? WCChatPrepend - 4 : WCChatPrepend;
312                         nick = [user nick];
313                         length = offset - [nick length];
314
315                         if(length < 0)
316                                 nick = [nick substringToIndex:offset];
317                        
318                         output = [NSSWF:NSLS(@"%@: %@", @"Chat message, Wired style (nick, message)"),
319                                 nick, chat];
320                        
321                         if(length > 0)
322                                 output = [NSSWF:@"%*s%@", length, " ", output];
323                         break;
324                        
325                 case WCChatStyleIRC:
326                         output = [NSSWF:NSLS(@"<%@> %@", @"Chat message, IRC style (nick, message)"),
327                                 [user nick], chat];
328                         break;
329         }
330        
331         if([WCSettings boolForKey:WCTimestampEveryLine])
332                 output = [NSSWF:@"%@ %@", [_timestampEveryLineDateFormatter stringFromDate:[NSDate date]], output];
333
334         [self _printString:output];
335 }
336
337
338
339 - (void)_printActionChat:(NSString *)chat by:(WCUser *)user {
340         NSString        *output;
341
342         switch([WCSettings intForKey:WCChatStyle]) {
343                 case WCChatStyleWired:
344                 default:
345                         output = [NSSWF:NSLS(@" *** %@ %@", @"Action chat message, Wired style (nick, message)"),
346                                 [user nick], chat];
347                         break;
348                        
349                 case WCChatStyleIRC:
350                         output = [NSSWF:NSLS(@" * %@ %@", @"Action chat message, IRC style (nick, message)"),
351                                 [user nick], chat];
352                         break;
353         }
354
355         if([WCSettings boolForKey:WCTimestampEveryLine])
356                 output = [NSSWF:@"%@ %@", [_timestampEveryLineDateFormatter stringFromDate:[NSDate date]], output];
357        
358         [self _printString:output];
359 }
360
361
362
363 #pragma mark -
364
365 - (NSArray *)_commands {
366         return [NSArray arrayWithObjects:
367                 @"/me",
368                 @"/exec",
369                 @"/nick",
370                 @"/status",
371                 @"/stats",
372                 @"/clear",
373                 @"/topic",
374                 @"/broadcast",
375                 @"/ping",
376                 NULL];
377 }
378
379
380
381 - (BOOL)_runCommand:(NSString *)string {
382         NSString        *command, *argument;
383         NSRange         range;
384        
385         range = [string rangeOfString:@" "];
386        
387         if(range.location == NSNotFound) {
388                 command = string;
389                 argument = @"";
390         } else {
391                 command = [string substringToIndex:range.location];
392                 argument = [string substringFromIndex:range.location + 1];
393         }
394        
395         if([command isEqualToString:@"/me"] && [argument length] > 0) {
396                 if([argument length] > WCChatLimit)
397                         argument = [argument substringToIndex:WCChatLimit];
398                
399                 [[self connection] sendCommand:WCMeCommand
400                                                   withArgument:[NSSWF:@"%u", [self chatID]]
401                                                   withArgument:argument];
402                
403                 [[WCStats stats] addUnsignedLongLong:[argument length] forKey:WCStatsChat];
404                
405                 return YES;
406         }
407         else if([command isEqualToString:@"/exec"] && [argument length] > 0) {
408                 NSTask                          *task;
409                 NSPipe                          *pipe;
410                 NSFileHandle            *fileHandle;
411                 NSDictionary            *environment;
412                 NSData                          *data;
413                 NSString                        *output;
414                 double                          timeout = 10.0;
415                
416                 pipe = [NSPipe pipe];
417                 fileHandle = [pipe fileHandleForReading];
418                
419                 environment     = [NSDictionary dictionaryWithObjectsAndKeys:
420                         @"/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin",
421                                 @"PATH",
422                         NULL];
423                
424                 task = [[NSTask alloc] init];
425                 [task setLaunchPath:@"/bin/sh"];
426                 [task setArguments:[NSArray arrayWithObjects:@"-c", argument, NULL]];
427                 [task setStandardOutput:pipe];
428                 [task setStandardError:pipe];
429                 [task setEnvironment:environment];
430                 [task launch];
431                
432                 while([task isRunning]) {
433                         sleep(1);
434                         timeout -= 1.0;
435                        
436                         if(timeout <= 0.0) {
437                                 [task terminate];
438                                
439                                 break;
440                         }
441                 }
442
443                 data = [fileHandle readDataToEndOfFile];
444                 output = [NSString stringWithData:data encoding:NSUTF8StringEncoding];
445                
446                 if(output && [output length] > 0) {
447                         if([output length] > WCChatLimit)
448                                 output = [output substringToIndex:WCChatLimit];
449
450                         [[self connection] sendCommand:WCSayCommand
451                                                           withArgument:[NSSWF:@"%u", [self chatID]]
452                                                           withArgument:output];
453
454                         [[WCStats stats] addUnsignedLongLong:[output length] forKey:WCStatsChat];
455                 }
456                
457                 [task release];
458                
459                 return YES;
460         }
461         else if(([command isEqualToString:@"/nick"] ||
462                          [command isEqualToString:@"/n"]) && [argument length] > 0) {
463                 [[self connection] sendCommand:WCNickCommand withArgument:argument];
464                
465                 return YES;
466         }
467         else if([command isEqualToString:@"/status"] || [command isEqualToString:@"/s"]){
468                 [[self connection] sendCommand:WCStatusCommand withArgument:argument];
469                
470                 return YES;
471         }
472         else if([command isEqualToString:@"/stats"]) {
473                 [[self connection] sendCommand:WCSayCommand
474                                                   withArgument:[NSSWF:@"%u", [self chatID]]
475                                                   withArgument:[[WCStats stats] stringValue]];
476                
477                 return YES;
478         }
479         else if([command isEqualToString:@"/clear"]) {
480                 [[[_chatOutputTextView textStorage] mutableString] setString:@""];
481                
482                 return YES;
483         }
484         else if([command isEqualToString:@"/topic"]) {
485                 [[self connection] sendCommand:WCTopicCommand
486                                                   withArgument:[NSSWF:@"%u", [self chatID]]
487                                                   withArgument:argument];
488                
489                 return YES;
490         }
491         else if([command isEqualToString:@"/broadcast"] && [argument length] > 0) {
492                 [[self connection] sendCommand:WCBroadcastCommand withArgument:argument];
493                
494                 return YES;
495         }
496 #ifndef RELEASE
497         else if([command isEqualToString:@"/dump"]) {
498                 [[self connection] sendCommand:WCDumpCommand];
499                
500                 return YES;
501         }
502 #endif
503         else if([command isEqualToString:@"/ping"]) {
504                 if(!_receivingPings) {
505                         [[self connection] addObserver:self
506                                                                   selector:@selector(serverConnectionReceivedPing:)
507                                                                           name:WCServerConnectionReceivedPing];
508                        
509                         _receivingPings = YES;
510                 }
511                
512                 _pingInterval = [NSDate timeIntervalSinceReferenceDate];
513                
514                 [[self connection] sendCommand:WCPingCommand];
515                
516                 return YES;
517         }
518
519         return NO;
520 }
521
522
523
524 #pragma mark -
525
526 - (NSString *)_stringByCompletingString:(NSString *)string {
527         NSEnumerator    *enumerator, *setEnumerator;
528         NSArray                 *nicks, *commands, *set, *matchingSet = NULL;
529         NSString                *match, *prefix = NULL;
530         NSUInteger              matches = 0;
531        
532         nicks = [self nicks];
533         commands = [self _commands];
534         enumerator = [[NSArray arrayWithObjects:nicks, commands, NULL] objectEnumerator];
535        
536         while((set = [enumerator nextObject])) {
537                 setEnumerator = [set objectEnumerator];
538                
539                 while((match = [setEnumerator nextObject])) {
540                         if([match rangeOfString:string options:NSCaseInsensitiveSearch].location == 0) {
541                                 if(matches == 0) {
542                                         prefix = match;
543                                         matches = 1;
544                                 } else {
545                                         prefix = [prefix commonPrefixWithString:match options:NSCaseInsensitiveSearch];
546                                
547                                         if([prefix length] < [match length])
548                                                 matches++;
549                                 }
550                                
551                                 matchingSet = set;
552                         }
553                 }
554         }
555        
556         if(matches > 1)
557                 return prefix;
558
559         if(matches == 1) {
560                 if(matchingSet == nicks) {
561                         return [prefix stringByAppendingString:
562                                 [WCSettings objectForKey:WCTabCompleteNicksString]];
563                 }
564                 else if(matchingSet == commands) {
565                         return [prefix stringByAppendingString:@" "];
566                 }
567         }
568        
569         return string;
570 }
571
572
573
574 - (NSString *)_stringByDecomposingAttributedString:(NSAttributedString *)attributedString {
575         if(![attributedString containsAttachments])
576                 return [attributedString string];
577        
578         return [[attributedString attributedStringByReplacingAttachmentsWithStrings] string];
579 }
580
581
582
583 #pragma mark -
584
585 - (BOOL)_isHighlightedChat:(NSString *)chat {
586         NSEnumerator            *enumerator;
587         NSDictionary            *highlight;
588        
589         enumerator = [[WCSettings objectForKey:WCHighlights] objectEnumerator];
590        
591         while((highlight = [enumerator nextObject])) {
592                 if([chat rangeOfString:[highlight objectForKey:WCHighlightsPattern] options:NSCaseInsensitiveSearch].location != NSNotFound)
593                         return YES;
594         }
595        
596         return NO;
597 }
598
599 @end
600
601
602 @implementation WCChat
603
604 + (id)allocWithZone:(NSZone *)zone {
605         if([self isEqual:[WCChat class]]) {
606                 NSLog(@"*** -[%@ allocWithZone:]: attempt to instantiate abstract class", self);
607
608                 return NULL;
609         }
610
611         return [super allocWithZone:zone];
612 }
613
614
615
616 - (id)initChatWithConnection:(WCServerConnection *)connection windowNibName:(NSString *)windowNibName name:(NSString *)name {
617         self = [super initWithWindowNibName:windowNibName name:name connection:connection];
618
619         _commandHistory = [[NSMutableArray alloc] init];
620         _users                  = [[NSMutableDictionary alloc] init];
621         _allUsers               = [[NSMutableArray alloc] init];
622         _shownUsers             = [[NSMutableArray alloc] init];
623        
624         [self window];
625
626         [[NSNotificationCenter defaultCenter]
627                 addObserver:self
628                    selector:@selector(preferencesDidChange:)
629                            name:WCPreferencesDidChange];
630
631         [[NSNotificationCenter defaultCenter]
632                 addObserver:self
633                    selector:@selector(dateDidChange:)
634                            name:WCDateDidChange];
635
636         [[self connection] addObserver:self
637                                                   selector:@selector(chatUsersDidChange:)
638                                                           name:WCChatUsersDidChange];
639
640         [[self connection] addObserver:self
641                                                   selector:@selector(chatReceivedUser:)
642                                                           name:WCChatReceivedUser];
643
644         [[self connection] addObserver:self
645                                                   selector:@selector(chatCompletedUsers:)
646                                                           name:WCChatCompletedUsers];
647
648         [[self connection] addObserver:self
649                                                   selector:@selector(chatReceivedUserJoin:)
650                                                           name:WCChatReceivedUserJoin];
651
652         [[self connection] addObserver:self
653                                                   selector:@selector(chatReceivedUserLeave:)
654                                                           name:WCChatReceivedUserLeave];
655
656         [[self connection] addObserver:self
657                                                   selector:@selector(chatReceivedUserChange:)
658                                                           name:WCChatReceivedUserChange];
659
660         [[self connection] addObserver:self
661                                                   selector:@selector(chatReceivedUserIconChange:)
662                                                           name:WCChatReceivedUserIconChange];
663
664         [[self connection] addObserver:self
665                                                   selector:@selector(chatReceivedUserKick:)
666                                                           name:WCChatReceivedUserKick];
667
668         [[self connection] addObserver:self
669                                                   selector:@selector(chatReceivedUserBan:)
670                                                           name:WCChatReceivedUserBan];
671
672         [[self connection] addObserver:self
673                                                   selector:@selector(chatReceivedChat:)
674                                                           name:WCChatReceivedChat];
675
676         [[self connection] addObserver:self
677                                                   selector:@selector(chatReceivedActionChat:)
678                                                           name:WCChatReceivedActionChat];
679
680         [[self connection] addObserver:self
681                                                   selector:@selector(chatReceivedTopic:)
682                                                           name:WCChatReceivedTopic];
683        
684         [self retain];
685
686         return self;
687 }
688
689
690
691 - (void)dealloc {
692         [[NSNotificationCenter defaultCenter] removeObserver:self];
693        
694         [_saveChatView release];
695        
696         [_users release];
697         [_allUsers release];
698         [_shownUsers release];
699
700         [_commandHistory release];
701
702         [_chatFilter release];
703         [_topicFilter release];
704         [_timestamp release];
705         [_topic release];
706        
707         [_timestampDateFormatter release];
708         [_timestampEveryLineDateFormatter release];
709         [_topicDateFormatter release];
710
711         [super dealloc];
712 }
713
714
715
716 #pragma mark -
717
718 - (void)windowDidLoad {
719         NSImageCell             *imageCell;
720         WCUserCell              *userCell;
721
722         imageCell = [[NSImageCell alloc] init];
723         [imageCell setImageAlignment:NSImageAlignCenter];
724         [_iconTableColumn setDataCell:imageCell];
725         [imageCell release];
726
727         userCell = [[WCUserCell alloc] init];
728         [_nickTableColumn setDataCell:userCell];
729         [userCell release];
730
731         [_chatOutputTextView setEditable:NO];
732         [_chatOutputTextView setUsesFindPanel:YES];
733         [_userListTableView setDoubleAction:@selector(sendPrivateMessage:)];
734
735         _chatFilter = [[WITextFilter alloc] initWithSelectors:@selector(filterWiredChat:), @selector(filterURLs:), @selector(filterWiredSmilies:), 0];
736         _topicFilter = [[WITextFilter alloc] initWithSelectors:@selector(filterURLs:), @selector(filterWiredSmallSmilies:), 0];
737
738         _timestampDateFormatter = [[WIDateFormatter alloc] init];
739         [_timestampDateFormatter setTimeStyle:NSDateFormatterShortStyle];
740         [_timestampDateFormatter setDateStyle:NSDateFormatterShortStyle];
741        
742         _timestampEveryLineDateFormatter = [[WIDateFormatter alloc] init];
743         [_timestampEveryLineDateFormatter setTimeStyle:NSDateFormatterShortStyle];
744        
745         _topicDateFormatter = [[WIDateFormatter alloc] init];
746         [_topicDateFormatter setTimeStyle:NSDateFormatterShortStyle];
747         [_topicDateFormatter setDateStyle:NSDateFormatterMediumStyle];
748         [_topicDateFormatter setNaturalLanguageStyle:WIDateFormatterCapitalizedNaturalLanguageStyle];
749        
750         [self _update];
751         [self validate];
752        
753         [super windowDidLoad];
754 }
755
756
757
758 - (void)windowWillClose:(NSNotification *)notification {
759         [_userListTableView setDataSource:NULL];
760 }
761
762
763
764 - (void)windowTemplateShouldLoad:(NSMutableDictionary *)windowTemplate {
765         [_userListSplitView setPropertiesFromDictionary:[windowTemplate objectForKey:@"WCChatUserListSplitView"]];
766         [_chatSplitView setPropertiesFromDictionary:[windowTemplate objectForKey:@"WCChatSplitView"]];
767 }
768
769
770
771 - (void)windowTemplateShouldSave:(NSMutableDictionary *)windowTemplate {
772         [windowTemplate setObject:[_userListSplitView propertiesDictionary] forKey:@"WCChatUserListSplitView"];
773         [windowTemplate setObject:[_chatSplitView propertiesDictionary] forKey:@"WCChatSplitView"];
774 }
775
776
777
778 - (void)connectionDidClose:(NSNotification *)notification {
779         [self validate];
780 }
781
782
783
784 - (void)serverConnectionServerInfoDidChange:(NSNotification *)notification {
785         if([[self connection] isReconnecting]) {
786                 [_topic release];
787                 _topic = NULL;
788                
789                 [self _updateTopic];
790         }
791 }
792
793
794
795 - (void)serverConnectionLoggedIn:(NSNotification *)notification {
796         [self windowTemplate];
797
798         [_users removeAllObjects];
799         [_shownUsers removeAllObjects];
800         [_userListTableView reloadData];
801
802         [self validate];
803 }
804
805
806
807 - (void)serverConnectionReceivedPing:(NSNotification *)notification {
808         NSTimeInterval          interval;
809        
810         interval = [NSDate timeIntervalSinceReferenceDate];
811        
812         [self printEvent:[NSSWF:
813                 NSLS(@"Received ping reply after %.2fms", @"Ping received message (interval)"),
814                 (interval - _pingInterval) * 1000.0]];
815        
816         [[self connection] removeObserver:self name:WCServerConnectionReceivedPing];
817
818         _receivingPings = NO;
819 }
820
821
822
823 - (void)serverConnectionPrivilegesDidChange:(NSNotification *)notification {
824         [self validate];
825 }
826
827
828
829 - (void)preferencesDidChange:(NSNotification *)notification {
830         [self _update];
831 }
832
833
834
835 - (void)dateDidChange:(NSNotification *)notification {
836         [self _updateTopic];
837 }
838
839
840
841 - (void)chatUsersDidChange:(NSNotification *)notification {
842         [_userListTableView reloadData];
843 }
844
845
846
847 - (void)chatReceivedUser:(NSNotification *)notification {
848         WCUser                  *user;
849
850         user = [WCUser userWithArguments:[[notification userInfo] objectForKey:WCArgumentsKey]];
851        
852         if([user chatID] != [self chatID])
853                 return;
854
855         [_allUsers addObject:user];
856         [_users setObject:user forKey:[NSNumber numberWithUnsignedInt:[user userID]]];
857 }
858
859
860
861 - (void)chatCompletedUsers:(NSNotification *)notification {
862         NSArray                 *fields;
863         NSString                *cid;
864
865         fields = [[notification userInfo] objectForKey:WCArgumentsKey];
866         cid = [fields safeObjectAtIndex:0];
867
868         if([cid unsignedIntValue] != [self chatID])
869                 return;
870
871         [_shownUsers addObjectsFromArray:_allUsers];
872         [_allUsers removeAllObjects];
873
874         [[self connection] postNotificationName:WCChatUsersDidChange object:[self connection]];
875        
876         _receivedUserList = YES;
877 }
878
879
880
881 - (void)chatReceivedUserJoin:(NSNotification *)notification {
882         WCUser                  *user;
883
884         if(!_receivedUserList)
885                 return;
886        
887         user = [WCUser userWithArguments:[[notification userInfo] objectForKey:WCArgumentsKey]];
888
889         if([user chatID] != [self chatID])
890                 return;
891
892         [_shownUsers addObject:user];
893         [_users setObject:user forKey:[NSNumber numberWithUnsignedInt:[user userID]]];
894
895         if([[WCSettings eventForTag:WCEventsUserJoined] boolForKey:WCEventsPostInChat])
896                 [self _printUserJoin:user];
897        
898         [[self connection] postNotificationName:WCChatUsersDidChange object:[self connection]];
899
900         [[self connection] triggerEvent:WCEventsUserJoined info1:user];
901 }
902
903
904
905 - (void)chatReceivedUserLeave:(NSNotification *)notification {
906         NSString                *cid, *uid;
907         NSArray                 *fields;
908         WCUser                  *user;
909
910         fields  = [[notification userInfo] objectForKey:WCArgumentsKey];
911         cid             = [fields safeObjectAtIndex:0];
912         uid             = [fields safeObjectAtIndex:1];
913
914         if([cid unsignedIntValue] != WCPublicChatID && [cid unsignedIntValue] != [self chatID])
915                 return;
916
917         user = [self userWithUserID:[uid unsignedIntValue]];
918        
919         if(!user)
920                 return;
921
922         if([[WCSettings eventForTag:WCEventsUserLeft] boolForKey:WCEventsPostInChat])
923                 [self _printUserLeave:user];
924
925         [[self connection] triggerEvent:WCEventsUserLeft info1:user];
926
927         [_shownUsers removeObject:user];
928         [_users removeObjectForKey:[NSNumber numberWithUnsignedInt:[user userID]]];
929        
930         [[self connection] postNotificationName:WCChatUsersDidChange object:[self connection]];
931 }
932
933
934
935 - (void)chatReceivedUserChange:(NSNotification *)notification {
936         NSString                *uid, *idle, *admin, *nick, *status;
937         NSArray                 *fields;
938         WCUser                  *user;
939
940         fields  = [[notification userInfo] objectForKey:WCArgumentsKey];
941         uid             = [fields safeObjectAtIndex:0];
942         idle    = [fields safeObjectAtIndex:1];
943         admin   = [fields safeObjectAtIndex:2];
944         nick    = [fields safeObjectAtIndex:4];
945         status  = [fields safeObjectAtIndex:5];
946
947         user = [self userWithUserID:[uid unsignedIntValue]];
948
949         if(!user)
950                 return;
951
952         if(![nick isEqualToString:[user nick]]) {
953                 if([[WCSettings eventForTag:WCEventsUserChangedNick] boolForKey:WCEventsPostInChat])
954                         [self _printUserChange:user nick:nick];
955
956                 [[self connection] triggerEvent:WCEventsUserChangedNick info1:user info2:nick];
957         }
958
959         if(![status isEqualToString:[user status]]) {
960                 if([[WCSettings eventForTag:WCEventsUserChangedStatus] boolForKey:WCEventsPostInChat])
961                         [self _printUserChange:user status:status];
962                
963                 [[self connection] triggerEvent:WCEventsUserChangedStatus info1:user info2:status];
964         }
965        
966         [user setNick:nick];
967         [user setStatus:status];
968         [user setIdle:[idle unsignedIntValue]];
969         [user setAdmin:[admin unsignedIntValue]];
970
971         [_userListTableView setNeedsDisplay:YES];
972 }
973
974
975
976 - (void)chatReceivedUserIconChange:(NSNotification *)notification {
977         NSString                *uid, *icon;
978         NSArray                 *fields;
979         NSData                  *data;
980         NSImage                 *image;
981         WCUser                  *user;
982
983         fields  = [[notification userInfo] objectForKey:WCArgumentsKey];
984         uid             = [fields safeObjectAtIndex:0];
985         icon    = [fields safeObjectAtIndex:1];
986
987         user = [self userWithUserID:[uid unsignedIntValue]];
988
989         if(!user)
990                 return;
991
992         data = [NSData dataWithBase64EncodedString:icon];
993         image = [[NSImage alloc] initWithData:data];
994         [user setIcon:image];
995         [image release];
996
997         [_userListTableView setNeedsDisplay:YES];
998 }
999
1000
1001
1002 - (void)chatReceivedUserKick:(NSNotification *)notification {
1003         NSString        *uid1, *uid2, *message;
1004         NSArray         *fields;
1005         WCUser          *victim, *killer;
1006
1007         fields  = [[notification userInfo] objectForKey:WCArgumentsKey];
1008         uid1    = [fields safeObjectAtIndex:0];
1009         uid2    = [fields safeObjectAtIndex:1];
1010         message = [fields safeObjectAtIndex:2];
1011
1012         victim = [self userWithUserID:[uid1 unsignedIntValue]];
1013         killer = [self userWithUserID:[uid2 unsignedIntValue]];
1014
1015         if(!victim || !killer)
1016                 return;
1017
1018         [self _printUserKick:victim by:killer message:message];
1019        
1020         if([victim userID] == [[self co