mirror of
https://github.com/Codeux-Software/Textual.git
synced 2026-06-16 13:24:32 +00:00
772 lines
18 KiB
Objective-C
772 lines
18 KiB
Objective-C
/* *********************************************************************
|
|
* _____ _ _
|
|
* |_ _|____ _| |_ _ _ __ _| |
|
|
* | |/ _ \ \/ / __| | | |/ _` | |
|
|
* | | __/> <| |_| |_| | (_| | |
|
|
* |_|\___/_/\_\\__|\__,_|\__,_|_|
|
|
*
|
|
* Copyright (c) 2008 - 2010 Satoshi Nakagawa <psychs AT limechat DOT net>
|
|
* Copyright (c) 2010 - 2020 Codeux Software, LLC & respective contributors.
|
|
* Please see Acknowledgements.pdf for additional information.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Textual, "Codeux Software, LLC", nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
*********************************************************************** */
|
|
|
|
#include <objc/message.h>
|
|
|
|
#import "NSObjectHelperPrivate.h"
|
|
#import "TXMasterController.h"
|
|
#import "TXWindowControllerPrivate.h"
|
|
#import "TVCMainWindowPrivate.h"
|
|
#import "TVCMemberListAppearance.h"
|
|
#import "TVCMemberListPrivate.h"
|
|
#import "TVCMemberListCellPrivate.h"
|
|
#import "TVCLogControllerPrivate.h"
|
|
#import "TDCSharedProtocolDefinitionsPrivate.h"
|
|
#import "TDCSheetBase.h"
|
|
#import "TPCPreferencesLocal.h"
|
|
#import "TLOEncryptionManagerPrivate.h"
|
|
#import "TLOFileLoggerPrivate.h"
|
|
#import "TLOInputHistoryPrivate.h"
|
|
#import "TLOLocalization.h"
|
|
#import "IRCClientPrivate.h"
|
|
#import "IRCChannelConfigPrivate.h"
|
|
#import "IRCChannelModePrivate.h"
|
|
#import "IRCChannelMemberListPrivate.h"
|
|
#import "IRCChannelUserPrivate.h"
|
|
#import "IRCISupportInfo.h"
|
|
#import "IRCTreeItemPrivate.h"
|
|
#import "IRCUser.h"
|
|
#import "IRCUserRelationsPrivate.h"
|
|
#import "IRCWorldPrivate.h"
|
|
#import "IRCChannelPrivate.h"
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
NSString * const IRCChannelConfigurationWasUpdatedNotification = @"IRCChannelConfigurationWasUpdatedNotification";
|
|
|
|
@interface IRCChannel ()
|
|
@property (readonly) BOOL isSelectedChannel;
|
|
@property (nonatomic, assign) BOOL statusChangedByAction;
|
|
@property (nonatomic, copy, readwrite) IRCChannelConfig *config;
|
|
@property (nonatomic, assign, readwrite) NSTimeInterval channelJoinTime;
|
|
@property (nonatomic, strong, readwrite, nullable) IRCChannelMode *modeInfo;
|
|
@property (nonatomic, strong, readwrite, nullable) IRCChannelMemberList *memberInfo;
|
|
@property (nonatomic, strong, nullable) TLOFileLogger *logFile;
|
|
@property (nonatomic, assign, readwrite) NSUInteger logFileSessionCount;
|
|
@end
|
|
|
|
@implementation IRCChannel
|
|
|
|
@synthesize associatedClient = _associatedClient;
|
|
|
|
- (instancetype)init
|
|
{
|
|
[self doesNotRecognizeSelector:_cmd];
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (instancetype)initWithConfigDictionary:(NSDictionary<NSString *, id> *)dic
|
|
{
|
|
NSParameterAssert(dic != nil);
|
|
|
|
IRCChannelConfig *config = [[IRCChannelConfig alloc] initWithDictionary:dic];
|
|
|
|
return [self initWithConfig:config];
|
|
}
|
|
|
|
- (instancetype)initWithConfig:(IRCChannelConfig *)config
|
|
{
|
|
if ((self = [super init])) {
|
|
self.config = config;
|
|
|
|
[self.config writeSecretKeyToKeychain];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)updateConfig:(IRCChannelConfig *)config
|
|
{
|
|
[self updateConfig:config fireChangedNotification:YES updateStoredChannelList:YES];
|
|
}
|
|
|
|
- (void)updateConfig:(IRCChannelConfig *)config fireChangedNotification:(BOOL)fireChangedNotification
|
|
{
|
|
[self updateConfig:config fireChangedNotification:fireChangedNotification updateStoredChannelList:YES];
|
|
}
|
|
|
|
- (void)updateConfig:(IRCChannelConfig *)config fireChangedNotification:(BOOL)fireChangedNotification updateStoredChannelList:(BOOL)updateStoredChannelList
|
|
{
|
|
NSParameterAssert(config != nil);
|
|
|
|
IRCChannelConfig *currentConfig = self.config;
|
|
|
|
if ([config isEqual:currentConfig]) {
|
|
return;
|
|
}
|
|
|
|
if (currentConfig.type != config.type ||
|
|
[currentConfig.channelName isEqualToString:config.channelName] == NO ||
|
|
[currentConfig.uniqueIdentifier isEqualToString:config.uniqueIdentifier] == NO)
|
|
{
|
|
LogToConsoleError("Tried to load configuration for incorrect channel");
|
|
|
|
return;
|
|
}
|
|
|
|
self.config = config;
|
|
|
|
[self.config writeSecretKeyToKeychain];
|
|
|
|
if (updateStoredChannelList) {
|
|
[self.associatedClient updateStoredChannelList];
|
|
}
|
|
|
|
if (fireChangedNotification) {
|
|
[RZNotificationCenter() postNotificationName:IRCChannelConfigurationWasUpdatedNotification object:self];
|
|
}
|
|
}
|
|
|
|
- (NSDictionary<NSString *, id> *)configurationDictionary
|
|
{
|
|
return [self.config dictionaryValue];
|
|
}
|
|
|
|
- (id)copyWithZone:(nullable NSZone *)zone
|
|
{
|
|
/* Implement this method to allow channel to be
|
|
used as a dictionary key. */
|
|
|
|
return self;
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
return [NSString stringWithFormat:@"<IRCChannel [%@]: %@>", self.associatedClient.description, self.name];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Property Getter
|
|
|
|
- (NSString *)uniqueIdentifier
|
|
{
|
|
return self.config.uniqueIdentifier;
|
|
}
|
|
|
|
- (NSString *)name
|
|
{
|
|
return self.config.channelName;
|
|
}
|
|
|
|
- (nullable NSString *)secretKey
|
|
{
|
|
return self.config.secretKey;
|
|
}
|
|
|
|
- (BOOL)autoJoin
|
|
{
|
|
return self.config.autoJoin;
|
|
}
|
|
|
|
- (BOOL)isChannel
|
|
{
|
|
return (self.config.type == IRCChannelTypeChannel);
|
|
}
|
|
|
|
- (BOOL)isPrivateMessage
|
|
{
|
|
return (self.config.type == IRCChannelTypePrivateMessage);
|
|
}
|
|
|
|
- (BOOL)isUtility
|
|
{
|
|
return (self.config.type == IRCChannelTypeUtility);
|
|
}
|
|
|
|
- (BOOL)isPrivateMessageForZNCUser
|
|
{
|
|
if (self.isPrivateMessage == NO) {
|
|
return NO;
|
|
}
|
|
|
|
IRCClient *client = self.associatedClient;
|
|
|
|
return [client nicknameIsZNCUser:self.name];
|
|
}
|
|
|
|
- (IRCChannelType)type
|
|
{
|
|
return self.config.type;
|
|
}
|
|
|
|
- (NSString *)channelTypeString
|
|
{
|
|
switch (self.config.type) {
|
|
case IRCChannelTypeChannel:
|
|
{
|
|
return @"channel";
|
|
}
|
|
case IRCChannelTypePrivateMessage:
|
|
{
|
|
return @"query";
|
|
}
|
|
case IRCChannelTypeUtility:
|
|
{
|
|
return @"utility";
|
|
}
|
|
}
|
|
}
|
|
|
|
- (nullable NSURL *)logFilePath
|
|
{
|
|
NSString *writePath = [TLOFileLogger writePathForItem:self];
|
|
|
|
if (writePath == nil) {
|
|
return nil;
|
|
}
|
|
|
|
return [NSURL fileURLWithPath:writePath];
|
|
}
|
|
|
|
- (nullable TVCLogLine *)lastLine
|
|
{
|
|
return self.viewController.lastLine;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Property Setter
|
|
|
|
- (void)setAutoJoin:(BOOL)autoJoin
|
|
{
|
|
if (self.isChannel == NO) {
|
|
return;
|
|
}
|
|
|
|
if (self.autoJoin == autoJoin) {
|
|
return;
|
|
}
|
|
|
|
IRCChannelConfigMutable *mutableConfig = [self.config mutableCopy];
|
|
|
|
mutableConfig.autoJoin = autoJoin;
|
|
|
|
self.config = mutableConfig;
|
|
}
|
|
|
|
- (void)setName:(NSString *)name
|
|
{
|
|
NSParameterAssert(name != nil);
|
|
|
|
if (self.isChannel) {
|
|
return;
|
|
}
|
|
|
|
if ([self.name isEqualToString:name]) {
|
|
return;
|
|
}
|
|
|
|
IRCChannelConfigMutable *mutableConfig = [self.config mutableCopy];
|
|
|
|
mutableConfig.channelName = name;
|
|
|
|
self.config = mutableConfig;
|
|
}
|
|
|
|
- (void)setTopic:(nullable NSString *)topic
|
|
{
|
|
if (self->_topic != topic) {
|
|
self->_topic = [topic copy];
|
|
|
|
[self.viewController setTopic:self->_topic];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Utilities
|
|
|
|
- (void)preferencesChanged
|
|
{
|
|
if ([TPCPreferences displayPublicMessageCountOnDockBadge] == NO) {
|
|
if (self.isChannel) {
|
|
self.dockUnreadCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if TEXTUAL_BUILT_WITH_ADVANCED_ENCRYPTION == 1
|
|
- (OTRKitMessageState)encryptionState
|
|
{
|
|
if ([TPCPreferences textEncryptionIsEnabled] == NO) {
|
|
return OTRKitMessageStatePlaintext;
|
|
}
|
|
|
|
if (self.isPrivateMessage == NO) {
|
|
return OTRKitMessageStatePlaintext;
|
|
}
|
|
|
|
IRCClient *client = self.associatedClient;
|
|
|
|
return [sharedEncryptionManager() messageStateFor:[client encryptionAccountNameForUser:self.name]
|
|
from:[client encryptionAccountNameForLocalUser]];
|
|
}
|
|
|
|
- (BOOL)encryptionStateIsEncrypted
|
|
{
|
|
return ([self encryptionState] == OTRKitMessageStateEncrypted);
|
|
}
|
|
|
|
- (void)noteEncryptionStateDidChange
|
|
{
|
|
self.viewController.encrypted = self.encryptionStateIsEncrypted;
|
|
|
|
[mainWindow() updateTitleFor:self];
|
|
}
|
|
|
|
- (void)closeOpenEncryptionSessions
|
|
{
|
|
if (self.encryptionStateIsEncrypted == NO) {
|
|
return;
|
|
}
|
|
|
|
IRCClient *client = self.associatedClient;
|
|
|
|
[sharedEncryptionManager() endConversationWith:[client encryptionAccountNameForUser:self.name]
|
|
from:[client encryptionAccountNameForLocalUser]];
|
|
}
|
|
#endif
|
|
|
|
#pragma mark -
|
|
#pragma mark Channel Status
|
|
|
|
- (void)setStatus:(IRCChannelStatus)status
|
|
{
|
|
if (self->_status != status) {
|
|
self->_status = status;
|
|
|
|
[self performActionOnStatusChange];
|
|
}
|
|
}
|
|
|
|
- (void)performActionOnStatusChange
|
|
{
|
|
if (self.statusChangedByAction) {
|
|
self.statusChangedByAction = NO;
|
|
|
|
return;
|
|
}
|
|
|
|
if (self.status == IRCChannelStatusJoined) {
|
|
[self activate];
|
|
} else if (self.status == IRCChannelStatusParted) {
|
|
[self deactivate];
|
|
}
|
|
}
|
|
|
|
- (void)resetStatus:(IRCChannelStatus)toStatus
|
|
{
|
|
if (toStatus == IRCChannelStatusJoining) {
|
|
return;
|
|
}
|
|
|
|
self.channelModesReceived = NO;
|
|
self.channelNamesReceived = NO;
|
|
self.errorOnLastJoinAttempt = NO;
|
|
self.sentInitialWhoRequest = NO;
|
|
|
|
self.channelJoinTime = 0;
|
|
|
|
self.modeInfo = nil;
|
|
|
|
self.status = toStatus;
|
|
|
|
self.topic = nil;
|
|
|
|
/* Clearing members, instead of just declaring memberInfo nil,
|
|
is important so that all users can be properly disassociated
|
|
with this channel. There are many relations. */
|
|
[self clearMembers];
|
|
|
|
self.memberInfo = nil;
|
|
}
|
|
|
|
- (void)activate
|
|
{
|
|
self.statusChangedByAction = YES;
|
|
|
|
[self resetStatus:IRCChannelStatusJoined];
|
|
|
|
IRCClient *client = self.associatedClient;
|
|
|
|
if (self.isUtility == NO) {
|
|
self.memberInfo = [[IRCChannelMemberList alloc] initWithChannel:self];
|
|
|
|
/* A channel is only assigned by TVCMainWindow when it's switched to.
|
|
Because of that we have to assign it here as the user already has
|
|
the channel selected, but it was never assigned because the member
|
|
list did not exist up to this point. */
|
|
if (self.isSelectedChannel) {
|
|
[mainWindowMemberList() assignToChannel:self];
|
|
}
|
|
}
|
|
|
|
if (self.isChannel) {
|
|
[client postEventToViewController:@"channelJoined" forChannel:self];
|
|
|
|
self.modeInfo = [[IRCChannelMode alloc] initWithChannel:self];
|
|
}
|
|
|
|
if (self.isPrivateMessage) {
|
|
IRCUser *user1 = [self.associatedClient findUserOrCreate:self.name];
|
|
|
|
[self addUser:user1];
|
|
|
|
IRCUser *user2 = [self.associatedClient findUserOrCreate:client.userNickname];
|
|
|
|
[self addUser:user2];
|
|
}
|
|
|
|
self.channelJoinTime = [NSDate timeIntervalSince1970];
|
|
}
|
|
|
|
- (void)deactivate
|
|
{
|
|
#if TEXTUAL_BUILT_WITH_ADVANCED_ENCRYPTION == 1
|
|
if (self.isPrivateMessage) {
|
|
[self closeOpenEncryptionSessions];
|
|
}
|
|
#endif
|
|
|
|
self.statusChangedByAction = YES;
|
|
|
|
[self resetStatus:IRCChannelStatusParted];
|
|
|
|
if (self.isChannel) {
|
|
[self.associatedClient postEventToViewController:@"channelParted" forChannel:self];
|
|
}
|
|
}
|
|
|
|
- (void)prepareForPermanentDestruction
|
|
{
|
|
self.statusChangedByAction = YES;
|
|
|
|
[self resetStatus:IRCChannelStatusTerminated];
|
|
|
|
[self closeLogFile];
|
|
|
|
[self.config destroySecretKeyKeychainItem];
|
|
|
|
NSArray *openWindows = [windowController()
|
|
windowsFromWindowList:@[@"TDCChannelPropertiesSheet",
|
|
@"TDCChannelModifyTopicSheet",
|
|
@"TDCChannelModifyModesSheet",
|
|
@"TDCChannelBanListSheet"]];
|
|
|
|
for (TDCSheetBase <TDCChannelPrototype> *windowObject in openWindows) {
|
|
if ([windowObject.channelId isEqualToString:self.uniqueIdentifier]) {
|
|
[windowObject close];
|
|
}
|
|
}
|
|
|
|
[[mainWindow() inputHistoryManager] destroy:self];
|
|
|
|
[self.viewController prepareForPermanentDestruction];
|
|
}
|
|
|
|
- (void)prepareForApplicationTermination
|
|
{
|
|
LogToConsoleTerminationProgress("Preparing channel: <%{public}@>", self.uniqueIdentifier);
|
|
|
|
self.statusChangedByAction = YES;
|
|
|
|
LogToConsoleTerminationProgress("[%{public}@] Resetting status to terminated", self.uniqueIdentifier);
|
|
|
|
[self resetStatus:IRCChannelStatusTerminated];
|
|
|
|
LogToConsoleTerminationProgress("[%{public}@] Closing log file", self.uniqueIdentifier);
|
|
|
|
[self closeLogFile];
|
|
|
|
if (self.isPrivateMessage) {
|
|
LogToConsoleTerminationProgress("[%{public}@] Destroying keychain items for private message", self.uniqueIdentifier);
|
|
|
|
[self.config destroySecretKeyKeychainItem];
|
|
}
|
|
|
|
LogToConsoleTerminationProgress("[%{public}@] Preparing view controller: <%{public}@>", self.uniqueIdentifier, self.viewController.uniqueIdentifier);
|
|
|
|
[self.viewController prepareForApplicationTermination];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Log File
|
|
|
|
- (void)reopenLogFileIfNeeded
|
|
{
|
|
if ([TPCPreferences logToDiskIsEnabled] && self.isUtility == NO) {
|
|
if ( self.logFile) {
|
|
[self.logFile reopenIfNeeded];
|
|
}
|
|
} else {
|
|
[self closeLogFile];
|
|
}
|
|
}
|
|
|
|
- (void)closeLogFile
|
|
{
|
|
if (self.logFile == nil) {
|
|
return;
|
|
}
|
|
|
|
[self.logFile close];
|
|
}
|
|
|
|
- (void)logFileWriteSessionBegin
|
|
{
|
|
[self.associatedClient logFileRecordSessionChanged:YES inChannel:self];
|
|
}
|
|
|
|
- (void)logFileWriteSessionEnd
|
|
{
|
|
[self.associatedClient logFileRecordSessionChanged:NO inChannel:self];
|
|
|
|
self.logFileSessionCount = 0;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Printing
|
|
|
|
- (void)writeToLogLineToLogFile:(TVCLogLine *)logLine
|
|
{
|
|
NSParameterAssert(logLine != nil);
|
|
|
|
if (self.isUtility || [TPCPreferences logToDiskIsEnabled] == NO) {
|
|
return;
|
|
}
|
|
|
|
// Perform addition before if statement to avoid infinite loop
|
|
self.logFileSessionCount += 1;
|
|
|
|
if (self.logFileSessionCount == 1) {
|
|
[self logFileWriteSessionBegin];
|
|
}
|
|
|
|
if (self.logFile == nil) {
|
|
self.logFile = [[TLOFileLogger alloc] initWithChannel:self];
|
|
}
|
|
|
|
[self.logFile writeLogLine:logLine];
|
|
}
|
|
|
|
- (void)print:(TVCLogLine *)logLine
|
|
{
|
|
[self print:logLine completionBlock:nil];
|
|
}
|
|
|
|
- (void)print:(TVCLogLine *)logLine completionBlock:(nullable TVCLogControllerPrintOperationCompletionBlock)completionBlock
|
|
{
|
|
NSParameterAssert(logLine != nil);
|
|
|
|
[self.viewController print:logLine completionBlock:completionBlock];
|
|
|
|
[self writeToLogLineToLogFile:logLine];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Member List
|
|
|
|
- (void)addUser:(IRCUser *)user
|
|
{
|
|
[self.memberInfo addUser:user];
|
|
}
|
|
|
|
- (void)addMember:(IRCChannelUser *)member
|
|
{
|
|
[self.memberInfo addMember:member];
|
|
}
|
|
|
|
- (void)addMember:(IRCChannelUser *)member checkForDuplicates:(BOOL)checkForDuplicates
|
|
{
|
|
[self.memberInfo addMember:member checkForDuplicates:checkForDuplicates];
|
|
}
|
|
|
|
- (void)removeMemberWithNickname:(NSString *)nickname
|
|
{
|
|
[self.memberInfo removeMemberWithNickname:nickname];
|
|
}
|
|
|
|
- (void)removeMember:(IRCChannelUser *)member
|
|
{
|
|
[self.memberInfo removeMember:member];
|
|
}
|
|
|
|
- (void)resortMember:(IRCChannelUser *)member
|
|
{
|
|
[self.memberInfo resortMember:member];
|
|
}
|
|
|
|
- (void)replaceMember:(IRCChannelUser *)member1 withMember:(IRCChannelUser *)member2
|
|
{
|
|
[self.memberInfo replaceMember:member1 withMember:member2];
|
|
}
|
|
|
|
- (void)replaceMember:(IRCChannelUser *)member1 withMember:(IRCChannelUser *)member2 resort:(BOOL)resort
|
|
{
|
|
[self.memberInfo replaceMember:member1 withMember:member2 resort:resort];
|
|
}
|
|
|
|
- (void)replaceMember:(IRCChannelUser *)member1 withMember:(IRCChannelUser *)member2 resort:(BOOL)resort replaceInAllChannels:(BOOL)replaceInAllChannels
|
|
{
|
|
[self.memberInfo replaceMember:member1 withMember:member2 resort:resort replaceInAllChannels:replaceInAllChannels];
|
|
}
|
|
|
|
- (void)changeMember:(NSString *)nickname mode:(NSString *)mode value:(BOOL)value
|
|
{
|
|
[self.memberInfo changeMember:nickname mode:mode value:value];
|
|
}
|
|
|
|
- (void)clearMembers
|
|
{
|
|
[self.memberInfo clearMembers];
|
|
}
|
|
|
|
- (NSUInteger)numberOfMembers
|
|
{
|
|
return self.memberInfo.numberOfMembers;
|
|
}
|
|
|
|
- (nullable NSArray<IRCChannelUser *> *)memberList
|
|
{
|
|
return self.memberInfo.memberList;
|
|
}
|
|
|
|
- (NSData *)pasteboardDataForMembers:(NSArray<IRCChannelUser *> *)members
|
|
{
|
|
return [self.memberInfo pasteboardDataForMembers:members];
|
|
}
|
|
|
|
+ (BOOL)readNicknamesFromPasteboardData:(NSData *)pasteboardData withBlock:(void (NS_NOESCAPE ^)(IRCChannel *channel, NSArray<NSString *> *nicknames))callbackBlock
|
|
{
|
|
return [IRCChannelMemberList readNicknamesFromPasteboardData:pasteboardData withBlock:callbackBlock];
|
|
}
|
|
|
|
+ (BOOL)readMembersFromPasteboardData:(NSData *)pasteboardData withBlock:(void (NS_NOESCAPE ^)(IRCChannel *channel, NSArray<IRCChannelUser *> *members))callbackBlock
|
|
{
|
|
return [IRCChannelMemberList readMembersFromPasteboardData:pasteboardData withBlock:callbackBlock];
|
|
}
|
|
|
|
- (BOOL)memberExists:(NSString *)nickname
|
|
{
|
|
return [self.memberInfo memberExists:nickname];
|
|
}
|
|
|
|
- (nullable IRCChannelUser *)findMember:(NSString *)nickname
|
|
{
|
|
return [self.memberInfo findMember:nickname];
|
|
}
|
|
|
|
- (void)sortMembers
|
|
{
|
|
[self.memberInfo sortMembers];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Table View Delegate
|
|
|
|
- (nullable NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
|
|
{
|
|
NSView *newView = [tableView makeViewWithIdentifier:@"GroupView" owner:self];
|
|
|
|
return newView;
|
|
}
|
|
|
|
- (nullable NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
|
|
{
|
|
TVCMemberListRowCell *rowView = [[TVCMemberListRowCell alloc] initWithMemberList:(id)tableView];
|
|
|
|
return rowView;
|
|
}
|
|
|
|
- (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
|
|
{
|
|
[mainWindowMemberList() refreshDrawingForRow:row];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark IRCTreeItem Properties
|
|
|
|
- (BOOL)isSelectedChannel
|
|
{
|
|
return (self == mainWindow().selectedItem);
|
|
}
|
|
|
|
- (BOOL)isActive
|
|
{
|
|
return (self.status == IRCChannelStatusJoined);
|
|
}
|
|
|
|
- (BOOL)isClient
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (NSUInteger)numberOfChildren
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
- (nullable id)childAtIndex:(NSUInteger)index
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
- (NSString *)label
|
|
{
|
|
NSString *customLabel = self.config.label;
|
|
|
|
if (customLabel.length > 0) {
|
|
return customLabel;
|
|
}
|
|
|
|
return self.name;
|
|
}
|
|
|
|
- (nullable IRCClient *)associatedClient
|
|
{
|
|
return self->_associatedClient;
|
|
}
|
|
|
|
- (nullable IRCChannel *)associatedChannel
|
|
{
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|