#import #import #import "JVChatRoomBrowser.h" #import "JVChatController.h" #import "JVChatConsole.h" #import "MVConnectionsController.h" #import "MVTableView.h" @interface NSDisclosureButtonCell + (id) alloc; - (id) initWithCell:(NSCell *) cell; @end #pragma mark - @interface JVChatRoomBrowser (JVChatRoomBrowserPrivate) - (void) _needToRefreshResults:(id) sender; - (void) _refreshResults:(id) sender; - (void) _resortResults; - (void) _connectionChange:(NSNotification *) notification; - (void) _startFetch; - (void) _stopFetch; @end #pragma mark - @implementation JVChatRoomBrowser - (id) initWithWindowNibName:(NSString *) windowNibName { if( ( self = [super initWithWindowNibName:@"JVChatRoomBrowser"] ) ) { _connection = nil; _roomResults = nil; _roomOrder = nil; _currentFilter = nil; _sortColumn = @"room"; _ascending = YES; _collapsed = YES; _needsRefresh = YES; _refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:( 1. ) target:self selector:@selector( _refreshResults: ) userInfo:nil repeats:YES] retain]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _connectionChange: ) name:MVChatConnectionDidConnectNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _connectionChange: ) name:MVChatConnectionDidDisconnectNotification object:nil]; } return self; } - (id) initWithConnection:(MVChatConnection *) connection { if( ( self = [self initWithWindowNibName:nil] ) ) { [self setConnection:connection]; } return self; } + (id) chatRoomBrowserForConnection:(MVChatConnection *) connection { return [[self alloc] initWithConnection:connection]; } - (void) release { if( ( [self retainCount] - 1 ) == 1 ) [_refreshTimer invalidate]; [super release]; } - (void) dealloc { [roomField setDelegate:nil]; [roomField setDataSource:nil]; [roomsTable setDelegate:nil]; [roomsTable setDataSource:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [_refreshTimer release]; [_connection release]; [_currentFilter release]; [_roomResults release]; [_roomOrder release]; [_sortColumn release]; _refreshTimer = nil; _connection = nil; _currentFilter = nil; _roomResults = nil; _roomOrder = nil; _sortColumn = nil; [super dealloc]; } - (void) windowDidLoad { NSTableColumn *theColumn = nil; theColumn = [roomsTable tableColumnWithIdentifier:@"members"]; [[theColumn headerCell] setImage:[NSImage imageNamed:@"personHeader"]]; [self tableView:roomsTable didClickTableColumn:[roomsTable tableColumnWithIdentifier:_sortColumn]]; [roomsTable setDoubleAction:@selector( joinRoom: )]; [showBroswer setCell:[[[NSDisclosureButtonCell alloc] initWithCell:[showBroswer cell]] autorelease]]; if( NSAppKitVersionNumber >= 700. ) { [searchField setCell:[[[NSClassFromString( @"NSSearchFieldCell" ) alloc] initTextCell:@""] autorelease]]; [searchField setBezelStyle:NSTextFieldRoundedBezel]; [searchField setBezeled:YES]; [searchField setEditable:YES]; [searchField setEditable:YES]; [[searchField cell] performSelector:@selector( setPlaceholderString: ) withObject:@"Filter Rooms"]; } [searchField setAction:@selector( filterResults: )]; [searchField setTarget:self]; _collapsed = NO; [showBroswer setState:NSOffState]; [self toggleRoomBrowser:showBroswer]; [self _connectionChange:nil]; _needsRefresh = YES; [self _refreshResults:nil]; if( [connectionPopup indexOfSelectedItem] != -1 ) [[self window] makeFirstResponder:roomField]; } #pragma mark - - (void) close { [[self window] orderOut:nil]; [[NSApplication sharedApplication] endSheet:[self window]]; if( _connection )[self _stopFetch]; [super close]; [self performSelector:@selector( release ) withObject:nil afterDelay:0.]; } #pragma mark - - (IBAction) showWindow:(id) sender { [[self window] center]; [[self window] makeKeyAndOrderFront:sender]; } - (IBAction) close:(id) sender { [self close]; } - (IBAction) joinRoom:(id) sender { [self close]; if( ! [_connection isConnected] ) [_connection connect]; [_connection joinChatRoom:[roomField stringValue]]; } - (IBAction) filterResults:(id) sender { [self setFilter:[searchField stringValue]]; _needsRefresh = YES; [self _refreshResults:nil]; } - (IBAction) changeConnection:(id) sender { [self setConnection:[[sender selectedItem] representedObject]]; [[self window] makeFirstResponder:roomField]; if( ! _collapsed && ! [_connection isConnected] ) { if( NSRunInformationalAlertPanel( NSLocalizedString( @"Connection is Disconencted", "connection is disconnected dialog title" ), NSLocalizedString( @"Would you like to connect and retrieve the server's chat room listing?", "would you like to connect to get room listing dialog message" ), NSLocalizedString( @"Yes", "yes button" ), NSLocalizedString( @"No", "no button" ), nil ) == NSOKButton ) { [_connection connect]; } else { [showBroswer setState:NSOffState]; [self toggleRoomBrowser:showBroswer]; } } } - (IBAction) hideRoomBrowser:(id) sender { if( _collapsed ) return; [showBroswer setState:NSOffState]; [self toggleRoomBrowser:showBroswer]; } - (IBAction) showRoomBrowser:(id) sender { if( ! _collapsed ) return; [showBroswer setState:NSOnState]; [self toggleRoomBrowser:showBroswer]; } - (IBAction) toggleRoomBrowser:(id) sender { NSRect windowFrame = [[self window] frame]; if( ! [_connection isConnected] && [sender state] ) { if( NSRunInformationalAlertPanel( NSLocalizedString( @"Connection is Disconencted", "connection is disconnected dialog title" ), NSLocalizedString( @"Would you like to connect and retrieve the server's chat room listing?", "would you like to connect to get room listing dialog message" ), NSLocalizedString( @"Yes", "yes button" ), NSLocalizedString( @"No", "no button" ), nil ) == NSOKButton ) { [_connection connect]; } else { [sender setState:NSOffState]; return; } } static float origWidth = 0.; if( ! origWidth ) origWidth = NSWidth( windowFrame ); static float origOffset = 0.; if( ! origOffset ) origOffset = NSHeight( [borwserArea frame] ); float offset = NSHeight( [borwserArea frame] ); if( ! offset ) offset = origOffset; float width = NSWidth( windowFrame ); [searchArea selectTabViewItemAtIndex:2]; if( ! [sender state] ) { [borwserArea selectTabViewItemAtIndex:0]; [[self window] setShowsResizeIndicator:NO]; width = origWidth; _collapsed = YES; } else width = 500.; NSRect newWindowFrame = NSMakeRect( ( NSMinX( windowFrame ) + ( ( NSWidth( windowFrame ) - width ) / 2. ) ), NSMinY( windowFrame ) + ( [sender state] ? offset * -1 : offset ), width, ( [sender state] ? NSHeight( windowFrame ) + offset : NSHeight( windowFrame ) - offset ) ); [[self window] setFrame:newWindowFrame display:YES animate:YES]; if( [sender state] ) { [roomsTable sizeLastColumnToFit]; [borwserArea selectTabViewItemAtIndex:1]; [searchArea selectTabViewItemAtIndex:1]; [[self window] setShowsResizeIndicator:YES]; _collapsed = NO; [self _startFetch]; } else [searchArea selectTabViewItemAtIndex:0]; _needsRefresh = YES; [self _refreshResults:nil]; } #pragma mark - - (void) setFilter:(NSString *) filter { [_currentFilter autorelease]; _currentFilter = [filter copy]; [searchField setStringValue:_currentFilter]; } - (NSString *) filter { return [[_currentFilter retain] autorelease]; } #pragma mark - - (void) setConnection:(MVChatConnection *) connection { if( _connection ) { [self _stopFetch]; [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:_connection]; } [_connection autorelease]; _connection = [connection retain]; if( _connection && ! _collapsed ) [self _startFetch]; if( _connection ) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _needToRefreshResults: ) name:MVChatConnectionGotRoomInfoNotification object:_connection]; [_roomResults autorelease]; _roomResults = [[_connection roomListResults] retain]; [_roomOrder autorelease]; _roomOrder = [[NSMutableArray array] retain]; _needsRefresh = YES; [self _refreshResults:nil]; [showBroswer setEnabled:( _connection ? YES : NO )]; [roomField setEnabled:( _connection ? YES : NO )]; } - (MVChatConnection *) connection { return [[_connection retain] autorelease]; } #pragma mark - - (NSSize) windowWillResize:(NSWindow *) window toSize:(NSSize) proposedFrameSize { return ( _collapsed ? [window frame].size : proposedFrameSize ); } - (BOOL) windowShouldZoom:(NSWindow *) window toFrame:(NSRect) newFrame { return ( _collapsed ? NO : YES ); } #pragma mark - - (int) numberOfItemsInComboBox:(NSComboBox *) comboBox { return [_roomOrder count]; } - (id) comboBox:(NSComboBox *) comboBox objectValueForItemAtIndex:(int) index { return [_roomOrder objectAtIndex:index]; } - (unsigned int) comboBox:(NSComboBox *) comboBox indexOfItemWithStringValue:(NSString *) string { return [_roomOrder indexOfObject:string]; } - (NSString *) comboBox:(NSComboBox *) comboBox completedString:(NSString *) substring { NSEnumerator *enumerator = [_roomOrder objectEnumerator]; NSString *room = nil; while( ( room = [enumerator nextObject] ) ) if( [room hasPrefix:substring] ) return room; return nil; } - (void) comboBoxSelectionDidChange:(NSNotification *) notification { [acceptButton setEnabled:( [roomField indexOfSelectedItem] != -1 || [[roomField stringValue] length] )]; if( ! _collapsed && roomsTable != [[roomsTable window] firstResponder] ) { int index = [roomField indexOfSelectedItem]; if( index != -1 ) { [roomsTable selectRow:index byExtendingSelection:NO]; [roomsTable scrollRowToVisible:index]; } else [roomsTable deselectAll:nil]; } } - (void) controlTextDidChange:(NSNotification *) notification { [acceptButton setEnabled:( [[roomField stringValue] length] )]; if( ! _collapsed && roomsTable != [[roomsTable window] firstResponder] ) { int index = [roomField indexOfSelectedItem]; if( index != -1 ) { [roomsTable selectRow:index byExtendingSelection:NO]; [roomsTable scrollRowToVisible:index]; } else [roomsTable deselectAll:nil]; } } - (void) controlTextDidEndEditing:(NSNotification *) notification { if( ! _collapsed && roomsTable != [[roomsTable window] firstResponder] ) { int index = [roomField indexOfSelectedItem]; if( index != -1 ) { [roomsTable selectRow:index byExtendingSelection:NO]; [roomsTable scrollRowToVisible:index]; } else [roomsTable deselectAll:nil]; } } #pragma mark - - (int) numberOfRowsInTableView:(NSTableView *) view { return [_roomOrder count]; } - (id) tableView:(NSTableView *) view objectValueForTableColumn:(NSTableColumn *) column row:(int) row { if( [[column identifier] isEqualToString:@"room"] ) { return [_roomOrder objectAtIndex:row]; } else if( [[column identifier] isEqualToString:@"topic"] ) { NSMutableDictionary *info = [_roomResults objectForKey:[_roomOrder objectAtIndex:row]]; NSAttributedString *t = [info objectForKey:@"topicAttributed"]; if( ! t ) { NSData *topic = [info objectForKey:@"topic"]; 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]; if( ! ( t = [NSAttributedString attributedStringWithIRCFormat:topic options:options] ) ) { [options setObject:[NSNumber numberWithUnsignedInt:[NSString defaultCStringEncoding]] forKey:@"StringEncoding"]; t = [NSAttributedString attributedStringWithIRCFormat:topic options:options]; } if( t ) [info setObject:t forKey:@"topicAttributed"]; } return t; } else if( [[column identifier] isEqualToString:@"members"] ) { NSString *room = [_roomOrder objectAtIndex:row]; return [[_roomResults objectForKey:room] objectForKey:@"users"]; } return nil; } - (void) tableView:(NSTableView *) view didClickTableColumn:(NSTableColumn *) column { if( [[column identifier] isEqualToString:@"topic"] ) return; [[view window] makeFirstResponder:view]; BOOL ascending = [[view indicatorImageInTableColumn:column] isEqual:[MVTableView ascendingSortIndicator]]; [view setIndicatorImage:nil inTableColumn:[view highlightedTableColumn]]; if( ascending ) [view setIndicatorImage:[MVTableView descendingSortIndicator] inTableColumn:column]; else [view setIndicatorImage:[MVTableView ascendingSortIndicator] inTableColumn:column]; [view setHighlightedTableColumn:column]; _ascending = ! ascending; [_sortColumn autorelease]; _sortColumn = [[column identifier] copy]; unsigned int index = [roomsTable selectedRow]; NSString *selectedRoom = ( index != -1 && [_roomOrder count] ? [[[_roomOrder objectAtIndex:index] copy] autorelease] : nil ); [roomsTable deselectAll:nil]; [self _resortResults]; if( selectedRoom ) index = [_roomOrder indexOfObject:selectedRoom]; else index = NSNotFound; if( index != NSNotFound ) { [roomsTable selectRow:index byExtendingSelection:NO]; [roomsTable scrollRowToVisible:index]; } } - (void) tableViewSelectionDidChange:(NSNotification *) notification { if( [notification object] != [[[notification object] window] firstResponder] ) return; if( [roomsTable selectedRow] == -1 ) { [roomField setObjectValue:@""]; [acceptButton setEnabled:( [[roomField stringValue] length] )]; } else { [roomField setObjectValue:[_roomOrder objectAtIndex:[roomsTable selectedRow]]]; [acceptButton setEnabled:( [[roomField stringValue] length] )]; } } @end #pragma mark - NSComparisonResult sortByRoomNameAscending( NSString *room1, NSString *room2, void *context ) { return [room1 caseInsensitiveCompare:room2]; } NSComparisonResult sortByRoomNameDescending( NSString *room1, NSString *room2, void *context ) { return [room2 caseInsensitiveCompare:room1]; } NSComparisonResult sortByNumberOfMembersAscending( NSString *room1, NSString *room2, void *context ) { NSDictionary *info = context; NSComparisonResult res = [(NSNumber *)[[info objectForKey:room1] objectForKey:@"users"] compare:[[info objectForKey:room2] objectForKey:@"users"]]; if( res != NSOrderedSame ) return res; return [room1 caseInsensitiveCompare:room2]; } NSComparisonResult sortByNumberOfMembersDescending( NSString *room1, NSString *room2, void *context ) { NSDictionary *info = context; NSComparisonResult res = [(NSNumber *)[[info objectForKey:room2] objectForKey:@"users"] compare:[[info objectForKey:room1] objectForKey:@"users"]]; if( res != NSOrderedSame ) return res; return [room1 caseInsensitiveCompare:room2]; } #pragma mark - @implementation JVChatRoomBrowser (JVChatRoomBrowserPrivate) - (void) _connectionChange:(NSNotification *) notification { NSEnumerator *enumerator = [[[MVConnectionsController defaultManager] connections] objectEnumerator]; MVChatConnection *connection = nil; NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; NSMenuItem *item = nil; while( ( connection = [enumerator nextObject] ) ) { item = [[[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"%@ (%@)", [connection server], [connection nickname]] action:NULL keyEquivalent:@""] autorelease]; NSImage *icon = nil; if( [connection isConnected] ) icon = [NSImage imageNamed:@"connected"]; else icon = [[[NSImage alloc] initWithSize:NSMakeSize( 9., 16. )] autorelease]; [item setImage:icon]; [item setRepresentedObject:connection]; if( connection == _connection ) [item setState:NSOnState]; [menu addItem:item]; } [connectionPopup setMenu:menu]; if( [[notification name] isEqualToString:MVChatConnectionDidConnectNotification] && [notification object] == _connection ) { if( ! _collapsed ) [self _startFetch]; } else if( [[notification name] isEqualToString:MVChatConnectionDidDisconnectNotification] && [notification object] == _connection ) { [self setConnection:nil]; } if( ! _connection && [menu numberOfItems] ) [connectionPopup selectItemAtIndex:-1]; [connectionPopup setEnabled:( ! ( ! _connection && ! [menu numberOfItems] ) )]; [showBroswer setEnabled:( _connection ? YES : NO )]; [roomField setEnabled:( _connection ? YES : NO )]; if( [connectionPopup indexOfSelectedItem] == -1 || ! [menu numberOfItems] ) { [roomField setObjectValue:@""]; [acceptButton setEnabled:NO]; if( ! _collapsed ) { [showBroswer setState:NSOffState]; [self toggleRoomBrowser:showBroswer]; } } } - (void) _needToRefreshResults:(id) sender { _needsRefresh = YES; } - (void) _refreshResults:(id) sender { if( ! _needsRefresh ) return; _needsRefresh = NO; unsigned int index = [roomsTable selectedRow]; NSString *selectedRoom = ( index != -1 && [_roomOrder count] ? [[[_roomOrder objectAtIndex:index] copy] autorelease] : nil ); if( _collapsed || ! [_currentFilter length] ) { [_roomOrder setArray:[_roomResults allKeys]]; goto refresh; } NSEnumerator *enumerator = [_roomResults keyEnumerator]; NSEnumerator *venumerator = [_roomResults objectEnumerator]; NSString *room = nil; NSDictionary *info = nil; while( ( room = [enumerator nextObject] ) && ( info = [venumerator nextObject] ) ) { if( [room rangeOfString:_currentFilter options:NSCaseInsensitiveSearch].location != NSNotFound || [[[info objectForKey:@"topicAttributed"] string] rangeOfString:_currentFilter options:NSCaseInsensitiveSearch].location != NSNotFound ) { if( ! [_roomOrder containsObject:room] ) [_roomOrder addObject:room]; } else [_roomOrder removeObject:room]; } refresh: if( _connection && [_connection isConnected] && [_roomResults count] ) { [indexResults setObjectValue:[NSString stringWithFormat:NSLocalizedString( @"%d rooms indexed.", "number of rooms listed on the server" ), [_roomResults count]]]; if( ! [_currentFilter length] ) { [indexAndFindResults setObjectValue:[NSString stringWithFormat:NSLocalizedString( @"%d rooms indexed.", "number of rooms listed on the server" ), [_roomResults count]]]; } else { [indexAndFindResults setObjectValue:[NSString stringWithFormat:NSLocalizedString( @"%d of %d rooms found.", "number of rooms found with a filter from the server listing" ), [_roomOrder count], [_roomResults count]]]; } } else { [indexResults setObjectValue:@""]; [indexAndFindResults setObjectValue:@""]; } [roomsTable deselectAll:nil]; [self _resortResults]; if( selectedRoom ) index = [_roomOrder indexOfObject:selectedRoom]; else index = NSNotFound; if( index != NSNotFound ) { [roomsTable selectRow:index byExtendingSelection:NO]; [roomsTable scrollRowToVisible:index]; } } - (void) _resortResults { if( _collapsed || ( [_sortColumn isEqualToString:@"room"] && _ascending ) ) [_roomOrder sortUsingFunction:sortByRoomNameAscending context:NULL]; else if( [_sortColumn isEqualToString:@"room"] && ! _ascending ) [_roomOrder sortUsingFunction:sortByRoomNameDescending context:NULL]; else if( [_sortColumn isEqualToString:@"members"] && _ascending ) [_roomOrder sortUsingFunction:sortByNumberOfMembersAscending context:_roomResults]; else if( [_sortColumn isEqualToString:@"members"] && ! _ascending ) [_roomOrder sortUsingFunction:sortByNumberOfMembersDescending context:_roomResults]; if( ! _collapsed ) [roomsTable noteNumberOfRowsChanged]; [roomField noteNumberOfItemsChanged]; } - (void) _startFetch { JVChatConsole *console = [[JVChatController defaultManager] chatConsoleForConnection:_connection ifExists:YES]; [console pause]; [_connection fetchRoomList]; } - (void) _stopFetch { [_connection stopFetchingRoomList]; JVChatConsole *console = [[JVChatController defaultManager] chatConsoleForConnection:_connection ifExists:YES]; [console resume]; } @end