| 1 | |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | |
|---|
| 5 | |
|---|
| 6 | |
|---|
| 7 | |
|---|
| 8 | |
|---|
| 9 | |
|---|
| 10 | |
|---|
| 11 | |
|---|
| 12 | |
|---|
| 13 | #import "LPXmlConsoleController.h" |
|---|
| 14 | #import "NSAttributedString+FactoryAdditions.h" |
|---|
| 15 | #import "LPAccount.h" |
|---|
| 16 | |
|---|
| 17 | |
|---|
| 18 | @implementation LPXmlConsoleController |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | - initWithAccount:(LPAccount *)account |
|---|
| 22 | { |
|---|
| 23 | if (self = [self initWithWindowNibName:@"XMLConsole"]) { |
|---|
| 24 | m_account = [account retain]; |
|---|
| 25 | m_recentXMLStanzasBuffer = [[NSMutableArray alloc] init]; |
|---|
| 26 | m_checkXML = YES; |
|---|
| 27 | |
|---|
| 28 | [[NSNotificationCenter defaultCenter] addObserver:self |
|---|
| 29 | selector:@selector(accountDidSendOrReceiveXMLString:) |
|---|
| 30 | name:LPAccountDidReceiveXMLStringNotification |
|---|
| 31 | object:account]; |
|---|
| 32 | [[NSNotificationCenter defaultCenter] addObserver:self |
|---|
| 33 | selector:@selector(accountDidSendOrReceiveXMLString:) |
|---|
| 34 | name:LPAccountDidSendXMLStringNotification |
|---|
| 35 | object:account]; |
|---|
| 36 | } |
|---|
| 37 | return self; |
|---|
| 38 | } |
|---|
| 39 | |
|---|
| 40 | |
|---|
| 41 | - (void)dealloc |
|---|
| 42 | { |
|---|
| 43 | [[NSNotificationCenter defaultCenter] removeObserver:self]; |
|---|
| 44 | |
|---|
| 45 | [m_recentXMLStanzasBuffer release]; |
|---|
| 46 | [m_account release]; |
|---|
| 47 | [super dealloc]; |
|---|
| 48 | } |
|---|
| 49 | |
|---|
| 50 | |
|---|
| 51 | - (void)windowDidLoad |
|---|
| 52 | { |
|---|
| 53 | [[self window] setTitle:[NSString stringWithFormat:@"XML Console for Account \"%@\" (%@)", |
|---|
| 54 | [m_account description], [m_account JID]]]; |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | NSToolbar *tb = [[NSToolbar alloc] initWithIdentifier:@"XMLConsoleToolbar"]; |
|---|
| 58 | [tb setDelegate:self]; |
|---|
| 59 | [tb setAllowsUserCustomization:YES]; |
|---|
| 60 | [tb setAutosavesConfiguration:YES]; |
|---|
| 61 | [[self window] setToolbar:tb]; |
|---|
| 62 | [tb release]; |
|---|
| 63 | |
|---|
| 64 | |
|---|
| 65 | [self setWindowFrameAutosaveName:@"LPXmlConsoleWindow"]; |
|---|
| 66 | } |
|---|
| 67 | |
|---|
| 68 | |
|---|
| 69 | #pragma mark - |
|---|
| 70 | #pragma mark Toolbar Delegate Methods |
|---|
| 71 | |
|---|
| 72 | |
|---|
| 73 | - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar |
|---|
| 74 | { |
|---|
| 75 | return [NSArray arrayWithObjects: |
|---|
| 76 | @"XMLConsoleToolbarItemEnabled", |
|---|
| 77 | NSToolbarSeparatorItemIdentifier, |
|---|
| 78 | @"XMLConsoleToolbarItemSendXML", |
|---|
| 79 | NSToolbarFlexibleSpaceItemIdentifier, |
|---|
| 80 | @"XMLConsoleToolbarItemSave", |
|---|
| 81 | NSToolbarSeparatorItemIdentifier, |
|---|
| 82 | @"XMLConsoleToolbarItemClear", |
|---|
| 83 | nil]; |
|---|
| 84 | } |
|---|
| 85 | |
|---|
| 86 | |
|---|
| 87 | - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar |
|---|
| 88 | { |
|---|
| 89 | return [NSArray arrayWithObjects: |
|---|
| 90 | @"XMLConsoleToolbarItemEnabled", |
|---|
| 91 | @"XMLConsoleToolbarItemSendXML", |
|---|
| 92 | @"XMLConsoleToolbarItemSave", |
|---|
| 93 | @"XMLConsoleToolbarItemClear", |
|---|
| 94 | NSToolbarCustomizeToolbarItemIdentifier, |
|---|
| 95 | NSToolbarFlexibleSpaceItemIdentifier, |
|---|
| 96 | NSToolbarSeparatorItemIdentifier, |
|---|
| 97 | NSToolbarSpaceItemIdentifier, |
|---|
| 98 | nil]; |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | |
|---|
| 102 | - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)identifier willBeInsertedIntoToolbar:(BOOL)willBeInserted |
|---|
| 103 | { |
|---|
| 104 | |
|---|
| 105 | NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:identifier]; |
|---|
| 106 | |
|---|
| 107 | if ([identifier isEqualToString:@"XMLConsoleToolbarItemEnabled"]) |
|---|
| 108 | { |
|---|
| 109 | [item setPaletteLabel:NSLocalizedString(@"Enable/Disable Logging", @"toolbar button label")]; |
|---|
| 110 | [item setView:m_enableCheckbox]; |
|---|
| 111 | [item setMinSize:[m_enableCheckbox frame].size]; |
|---|
| 112 | [item setMaxSize:[m_enableCheckbox frame].size]; |
|---|
| 113 | [item setToolTip:NSLocalizedString(@"Enable the logging of sent and received XML messages.", @"toolbar button")]; |
|---|
| 114 | } |
|---|
| 115 | else if ([identifier isEqualToString:@"XMLConsoleToolbarItemClear"]) |
|---|
| 116 | { |
|---|
| 117 | [item setLabel:NSLocalizedString(@"Clear", @"toolbar button label")]; |
|---|
| 118 | [item setPaletteLabel:NSLocalizedString(@"Clear Console", @"toolbar button label")]; |
|---|
| 119 | [item setImage:[NSImage imageNamed:@"clear_log.tiff"]]; |
|---|
| 120 | [item setToolTip:NSLocalizedString(@"Clear the console log.", @"toolbar button")]; |
|---|
| 121 | [item setAction:@selector(clear:)]; |
|---|
| 122 | [item setTarget:self]; |
|---|
| 123 | } |
|---|
| 124 | else if ([identifier isEqualToString:@"XMLConsoleToolbarItemSave"]) |
|---|
| 125 | { |
|---|
| 126 | [item setLabel:NSLocalizedString(@"Save to File", @"toolbar button label")]; |
|---|
| 127 | [item setPaletteLabel:NSLocalizedString(@"Save to File", @"toolbar button label")]; |
|---|
| 128 | [item setImage:[NSImage imageNamed:@"save_log.icns"]]; |
|---|
| 129 | [item setToolTip:NSLocalizedString(@"Save the console log to a file.", @"toolbar button")]; |
|---|
| 130 | [item setAction:@selector(save:)]; |
|---|
| 131 | [item setTarget:self]; |
|---|
| 132 | } |
|---|
| 133 | else if ([identifier isEqualToString:@"XMLConsoleToolbarItemSendXML"]) |
|---|
| 134 | { |
|---|
| 135 | [item setLabel:NSLocalizedString(@"Send XML", @"toolbar button label")]; |
|---|
| 136 | [item setPaletteLabel:NSLocalizedString(@"Send XML", @"toolbar button label")]; |
|---|
| 137 | [item setImage:[NSImage imageNamed:@"send_xml.tiff"]]; |
|---|
| 138 | [item setToolTip:NSLocalizedString(@"Send an XML stanza.", @"toolbar button")]; |
|---|
| 139 | [item setAction:@selector(showInputSheet:)]; |
|---|
| 140 | [item setTarget:self]; |
|---|
| 141 | } |
|---|
| 142 | |
|---|
| 143 | return [item autorelease]; |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | |
|---|
| 147 | #pragma mark - |
|---|
| 148 | #pragma mark Actions |
|---|
| 149 | |
|---|
| 150 | |
|---|
| 151 | - (IBAction)clear:(id)sender |
|---|
| 152 | { |
|---|
| 153 | [m_xmlTextView setString:@""]; |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | |
|---|
| 157 | - (IBAction)save:(id)sender |
|---|
| 158 | { |
|---|
| 159 | NSSavePanel *savePanel = [NSSavePanel savePanel]; |
|---|
| 160 | [savePanel beginSheetForDirectory:nil |
|---|
| 161 | file:@"xmpplog.rtfd" |
|---|
| 162 | modalForWindow:[self window] |
|---|
| 163 | modalDelegate:self |
|---|
| 164 | didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) |
|---|
| 165 | contextInfo:nil]; |
|---|
| 166 | } |
|---|
| 167 | |
|---|
| 168 | |
|---|
| 169 | - (IBAction)showInputSheet:(id)sender |
|---|
| 170 | { |
|---|
| 171 | NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: |
|---|
| 172 | [NSFont userFixedPitchFontOfSize:10.0], NSFontAttributeName, |
|---|
| 173 | nil]; |
|---|
| 174 | |
|---|
| 175 | |
|---|
| 176 | [m_inputTextView setString:@""]; |
|---|
| 177 | [m_inputTextView setTypingAttributes:attributes]; |
|---|
| 178 | |
|---|
| 179 | [m_invalidXMLLabel setHidden:YES]; |
|---|
| 180 | [m_inputSendButton setEnabled:NO]; |
|---|
| 181 | |
|---|
| 182 | |
|---|
| 183 | [NSApp beginSheet:m_inputSheet |
|---|
| 184 | modalForWindow:[self window] |
|---|
| 185 | modalDelegate:self |
|---|
| 186 | didEndSelector:@selector(inputSheetDidEnd:returnCode:contextInfo:) |
|---|
| 187 | contextInfo:nil]; |
|---|
| 188 | } |
|---|
| 189 | |
|---|
| 190 | |
|---|
| 191 | - (IBAction)inputSheetOK:(id)sender |
|---|
| 192 | { |
|---|
| 193 | BOOL canSendXML = YES; |
|---|
| 194 | |
|---|
| 195 | if ([self isXMLCheckEnabled]) { |
|---|
| 196 | NSError *error = nil; |
|---|
| 197 | NSXMLElement *xmlElem = [[NSXMLElement alloc] initWithXMLString:[m_inputTextView string] error:&error]; |
|---|
| 198 | |
|---|
| 199 | if (xmlElem != nil && error == nil) { |
|---|
| 200 | |
|---|
| 201 | [m_invalidXMLLabel setHidden:YES]; |
|---|
| 202 | canSendXML = YES; |
|---|
| 203 | } |
|---|
| 204 | else { |
|---|
| 205 | |
|---|
| 206 | NSBeep(); |
|---|
| 207 | [m_invalidXMLLabel setHidden:NO]; |
|---|
| 208 | canSendXML = NO; |
|---|
| 209 | } |
|---|
| 210 | [xmlElem release]; |
|---|
| 211 | } |
|---|
| 212 | |
|---|
| 213 | if (canSendXML) { |
|---|
| 214 | [m_account sendXMLString:[m_inputTextView string]]; |
|---|
| 215 | [NSApp endSheet:m_inputSheet]; |
|---|
| 216 | } |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | |
|---|
| 220 | - (IBAction)inputSheetCancel:(id)sender |
|---|
| 221 | { |
|---|
| 222 | [NSApp endSheet:m_inputSheet]; |
|---|
| 223 | } |
|---|
| 224 | |
|---|
| 225 | |
|---|
| 226 | #pragma mark - |
|---|
| 227 | #pragma mark Instance Methods |
|---|
| 228 | |
|---|
| 229 | - (BOOL)isLoggingEnabled |
|---|
| 230 | { |
|---|
| 231 | return m_enabled; |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | - (void)setLoggingEnabled:(BOOL)flag |
|---|
| 235 | { |
|---|
| 236 | if (m_enabled != flag) { |
|---|
| 237 | m_enabled = flag; |
|---|
| 238 | |
|---|
| 239 | |
|---|
| 240 | NSTextStorage *textStorage = [m_xmlTextView textStorage]; |
|---|
| 241 | BOOL wasScrolledToBottom = (NSMaxY([m_xmlTextView visibleRect]) >= (NSMaxY([m_xmlTextView bounds]) - 30.0)); |
|---|
| 242 | NSFont *font = [NSFont userFixedPitchFontOfSize:10.0]; |
|---|
| 243 | |
|---|
| 244 | [textStorage beginEditing]; |
|---|
| 245 | { |
|---|
| 246 | |
|---|
| 247 | if (m_enabled && [m_recentXMLStanzasBuffer count] > 0) { |
|---|
| 248 | NSString *bufferDumpHeaderStr = @"\n\n<!-- ***** Dumping some recently exchanged XML messages: ***** -->\n"; |
|---|
| 249 | [textStorage appendAttributedString:[NSAttributedString attributedStringFromString:bufferDumpHeaderStr |
|---|
| 250 | font:font |
|---|
| 251 | color:[NSColor darkGrayColor]]]; |
|---|
| 252 | |
|---|
| 253 | NSEnumerator *attribStrEnum = [m_recentXMLStanzasBuffer objectEnumerator]; |
|---|
| 254 | NSAttributedString *attribString = nil; |
|---|
| 255 | while (attribString = [attribStrEnum nextObject]) { |
|---|
| 256 | [textStorage appendAttributedString:attribString]; |
|---|
| 257 | } |
|---|
| 258 | } |
|---|
| 259 | |
|---|
| 260 | NSString *liveDumpHeaderStr = [NSString stringWithFormat:@"\n\n<!-- ***** LIVE logging %@ at %@ ***** -->\n", |
|---|
| 261 | (m_enabled ? @"STARTED" : @"STOPPED"), [NSDate date]]; |
|---|
| 262 | |
|---|
| 263 | [textStorage appendAttributedString:[NSAttributedString attributedStringFromString:liveDumpHeaderStr |
|---|
| 264 | font:font |
|---|
| 265 | color:[NSColor darkGrayColor]]]; |
|---|
| 266 | } |
|---|
| 267 | [textStorage endEditing]; |
|---|
| 268 | |
|---|
| 269 | |
|---|
| 270 | if (wasScrolledToBottom) { |
|---|
| 271 | [m_xmlTextView scrollRangeToVisible:NSMakeRange([textStorage length], 0)]; |
|---|
| 272 | } |
|---|
| 273 | |
|---|
| 274 | if (m_enabled) { |
|---|
| 275 | [m_recentXMLStanzasBuffer removeAllObjects]; |
|---|
| 276 | } |
|---|
| 277 | } |
|---|
| 278 | } |
|---|
| 279 | |
|---|
| 280 | - (BOOL)isXMLCheckEnabled |
|---|
| 281 | { |
|---|
| 282 | return m_checkXML; |
|---|
| 283 | } |
|---|
| 284 | |
|---|
| 285 | - (void)setXMLCheckEnabled:(BOOL)flag |
|---|
| 286 | { |
|---|
| 287 | m_checkXML = flag; |
|---|
| 288 | |
|---|
| 289 | if (!flag) { |
|---|
| 290 | [m_invalidXMLLabel setHidden:YES]; |
|---|
| 291 | } |
|---|
| 292 | } |
|---|
| 293 | |
|---|
| 294 | - (NSAttributedString *)attributedStringForConsoleFromXMLString:(NSString *)xmlString inbound:(BOOL)isInbound |
|---|
| 295 | { |
|---|
| 296 | NSMutableAttributedString *resultingAttribStr = [[NSMutableAttributedString alloc] init]; |
|---|
| 297 | |
|---|
| 298 | NSFont *font = [NSFont userFixedPitchFontOfSize:10.0]; |
|---|
| 299 | NSString *colorKey = (isInbound ? @"ChatFriendColor" : @"ChatMyColor"); |
|---|
| 300 | NSData *colorData = [[NSUserDefaults standardUserDefaults] dataForKey:colorKey]; |
|---|
| 301 | NSColor *color = [NSUnarchiver unarchiveObjectWithData:colorData]; |
|---|
| 302 | |
|---|
| 303 | if ([xmlString length] > 0) { |
|---|
| 304 | |
|---|
| 305 | NSString *dateTagStr = [NSString stringWithFormat:@"\n\n<!-- %@ at %@ -->\n", |
|---|
| 306 | (isInbound ? @"Received" : @"Sent"), [NSDate date]]; |
|---|
| 307 | NSAttributedString *attributedDateTagStr = [NSAttributedString attributedStringFromString:dateTagStr |
|---|
| 308 | font:font |
|---|
| 309 | color:[NSColor grayColor]]; |
|---|
| 310 | NSAttributedString *attributedXMLStr = [NSAttributedString attributedStringFromString:xmlString |
|---|
| 311 | font:font |
|---|
| 312 | color:color]; |
|---|
| 313 | |
|---|
| 314 | [resultingAttribStr beginEditing]; |
|---|
| 315 | [resultingAttribStr appendAttributedString:attributedDateTagStr]; |
|---|
| 316 | [resultingAttribStr appendAttributedString:attributedXMLStr]; |
|---|
| 317 | [resultingAttribStr endEditing]; |
|---|
| 318 | } |
|---|
| 319 | |
|---|
| 320 | return [resultingAttribStr autorelease]; |
|---|
| 321 | } |
|---|
| 322 | |
|---|
| 323 | #define RECENT_XML_STANZAS_BUFFER_MAX_COUNT 1000 |
|---|
| 324 | |
|---|
| 325 | - (void)appendXMLString:(NSString *)xmlString inbound:(BOOL)isInbound |
|---|
| 326 | { |
|---|
| 327 | NSAttributedString *attributedXMLStr = [self attributedStringForConsoleFromXMLString:xmlString inbound:isInbound]; |
|---|
| 328 | |
|---|
| 329 | if (m_enabled) { |
|---|
| 330 | NSTextStorage *textStorage = [m_xmlTextView textStorage]; |
|---|
| 331 | BOOL wasScrolledToBottom = (NSMaxY([m_xmlTextView visibleRect]) >= (NSMaxY([m_xmlTextView bounds]) - 30.0)); |
|---|
| 332 | |
|---|
| 333 | [textStorage appendAttributedString:attributedXMLStr]; |
|---|
| 334 | |
|---|
| 335 | |
|---|
| 336 | if (wasScrolledToBottom) { |
|---|
| 337 | [m_xmlTextView scrollRangeToVisible:NSMakeRange([textStorage length], 0)]; |
|---|
| 338 | } |
|---|
| 339 | } |
|---|
| 340 | else { |
|---|
| 341 | |
|---|
| 342 | [m_recentXMLStanzasBuffer addObject:attributedXMLStr]; |
|---|
| 343 | |
|---|
| 344 | NSUInteger newCount = [m_recentXMLStanzasBuffer count]; |
|---|
| 345 | |
|---|
| 346 | if (newCount > RECENT_XML_STANZAS_BUFFER_MAX_COUNT) { |
|---|
| 347 | [m_recentXMLStanzasBuffer removeObjectsInRange:NSMakeRange(0, newCount - RECENT_XML_STANZAS_BUFFER_MAX_COUNT)]; |
|---|
| 348 | } |
|---|
| 349 | } |
|---|
| 350 | } |
|---|
| 351 | |
|---|
| 352 | |
|---|
| 353 | #pragma mark - |
|---|
| 354 | #pragma mark Input Sheet Delegate |
|---|
| 355 | |
|---|
| 356 | |
|---|
| 357 | - (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo |
|---|
| 358 | { |
|---|
| 359 | [m_xmlTextView writeRTFDToFile:[sheet filename] atomically:YES]; |
|---|
| 360 | } |
|---|
| 361 | |
|---|
| 362 | - (void)inputSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo |
|---|
| 363 | { |
|---|
| 364 | [m_inputSheet orderOut:self]; |
|---|
| 365 | } |
|---|
| 366 | |
|---|
| 367 | |
|---|
| 368 | #pragma mark - |
|---|
| 369 | #pragma mark NSTextView Delegate |
|---|
| 370 | |
|---|
| 371 | |
|---|
| 372 | - (void)textDidChange:(NSNotification *)aNotification |
|---|
| 373 | { |
|---|
| 374 | [m_invalidXMLLabel setHidden:YES]; |
|---|
| 375 | [m_inputSendButton setEnabled:([[m_inputTextView string] length] > 0)]; |
|---|
| 376 | } |
|---|
| 377 | |
|---|
| 378 | |
|---|
| 379 | #pragma mark - |
|---|
| 380 | #pragma mark Account Notification Methods |
|---|
| 381 | |
|---|
| 382 | |
|---|
| 383 | - (void)accountDidSendOrReceiveXMLString:(NSNotification *)notification |
|---|
| 384 | { |
|---|
| 385 | [self appendXMLString:[[notification userInfo] objectForKey:LPXMLString] |
|---|
| 386 | inbound:[[notification name] isEqualToString:LPAccountDidReceiveXMLStringNotification]]; |
|---|
| 387 | } |
|---|
| 388 | |
|---|
| 389 | |
|---|
| 390 | @end |
|---|