adium/adium
Clone
Summary
Browse
Changes
Graph
Adding +[NSString randomString] seems to be popular, it appears to be colliding with some plugin I have loaded. Add a prefix here.
adium-1.5.11
2014-12-16, Thijs Alkemade
c4b291694b4a
Adding +[NSString randomString] seems to be popular, it appears to be colliding with some plugin I have loaded. Add a prefix here.
/*
* Adium is the legal property of its developers, whose names are listed in the copyright file included
* with this source distribution.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program; if not,
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#import <Adium/AIContactControllerProtocol.h>
#import <Adium/AIChatControllerProtocol.h>
#import <Adium/AIContentControllerProtocol.h>
#import <Adium/AIStatusControllerProtocol.h>
#import <Adium/AIContentMessage.h>
#import <Adium/AIListContact.h>
#import <Adium/AIContactList.h>
#import <Adium/AIListGroup.h>
#import <Adium/AIMetaContact.h>
#import <Adium/AIService.h>
#import <Adium/AIUserIcons.h>
#import <Adium/ESFileTransfer.h>
#import <Adium/AIStatus.h>
#import <Adium/AIHTMLDecoder.h>
#import <AIUtilities/AIMutableOwnerArray.h>
#import <AIUtilities/AIMutableStringAdditions.h>
#import <AvailabilityMacros.h>
#import "AIAddressBookController.h"
#define KEY_BASE_WRITING_DIRECTION @"Base Writing Direction"
#define PREF_GROUP_WRITING_DIRECTION @"Writing Direction"
#define CONTACT_SIGN_ON_OR_OFF_PERSISTENCE_DELAY 15
@interface
AIListObject
()
-
(
void
)
setContainingObject
:
(
AIListObject
<
AIContainingObject
>
*
)
inGroup
;
@end
@interface
AIListContact
()
@property
(
readwrite
,
nonatomic
,
assign
)
AIMetaContact
*
metaContact
;
-
(
void
)
remoteGroupingChanged
;
@end
@implementation
AIListContact
//Init with an account
-
(
id
)
initWithUID:
(
NSString
*
)
inUID
account:
(
AIAccount
*
)
inAccount
service:
(
AIService
*
)
inService
{
if
((
self
=
[
self
initWithUID
:
inUID
service
:
inService
]))
{
account
=
[
inAccount
retain
];
}
return
self
;
}
//Standard init
-
(
id
)
initWithUID:
(
NSString
*
)
inUID
service:
(
AIService
*
)
inService
{
if
((
self
=
[
super
initWithUID
:
inUID
service
:
inService
]))
{
account
=
nil
;
m_remoteGroupNames
=
[[
NSMutableSet
alloc
]
initWithCapacity
:
1
];
internalUniqueObjectID
=
nil
;
}
return
self
;
}
-
(
void
)
dealloc
{
[
account
release
];
account
=
nil
;
[
m_remoteGroupNames
release
];
m_remoteGroupNames
=
nil
;
[
internalUniqueObjectID
release
];
internalUniqueObjectID
=
nil
;
[
textColor
release
];
textColor
=
nil
;
[
invertedTextColor
release
];
invertedTextColor
=
nil
;
[
labelColor
release
];
labelColor
=
nil
;
[
imageOpacity
release
];
imageOpacity
=
nil
;
[
ABUniqueID
release
];
ABUniqueID
=
nil
;
[
textProfile
release
];
textProfile
=
nil
;
[
idleSince
release
];
idleSince
=
nil
;
[
idleReadable
release
];
idleReadable
=
nil
;
[
serverDisplayName
release
];
serverDisplayName
=
nil
;
[
formattedUID
release
];
formattedUID
=
nil
;
[
super
dealloc
];
}
//The account that owns this contact
@synthesize
account
;
/*!
* @brief Set the UID of this contact
*
* The UID for an AIListContact generally shouldn't change... if the contact is actually renamed serverside, however,
* it is useful to change the UID without having to change everything else associated with it.
*/
-
(
void
)
setUID:
(
NSString
*
)
inUID
{
if
(
UID
!=
inUID
)
{
[
UID
release
];
UID
=
[
inUID
retain
];
[
internalObjectID
release
];
internalObjectID
=
nil
;
[
internalUniqueObjectID
release
];
internalUniqueObjectID
=
nil
;
}
}
//An object ID generated by Adium that is completely unique to this contact. This ID is generated from the service ID,
//UID, and account UID. Adium will not allow multiple contacts with the same internalUniqueObjectID to be created.
-
(
NSString
*
)
internalUniqueObjectID
{
if
(
!
internalUniqueObjectID
)
{
internalUniqueObjectID
=
[[
AIListContact
internalUniqueObjectIDForService
:
self
.
service
account
:
self
.
account
UID
:
self
.
UID
]
retain
];
}
return
internalUniqueObjectID
;
}
//Generate a unique object ID for the passed object
+
(
NSString
*
)
internalUniqueObjectIDForService:
(
AIService
*
)
inService
account:
(
AIAccount
*
)
inAccount
UID:
(
NSString
*
)
inUID
{
return
[
NSString
stringWithFormat
:
@"%@.%@.%@"
,
inService
.
serviceClass
,
inAccount
.
UID
,
inUID
];
}
//Remote Grouping ------------------------------------------------------------------------------------------------------
#pragma mark Remote Grouping
-
(
NSSet
*
)
remoteGroupNames
{
return
[[
m_remoteGroupNames
copy
]
autorelease
];
}
-
(
void
)
setRemoteGroupNames:
(
NSSet
*
)
inGroupNames
{
NSParameterAssert
(
inGroupNames
!=
nil
);
[
m_remoteGroupNames
setSet
:
inGroupNames
];
[
self
remoteGroupingChanged
];
}
-
(
void
)
addRemoteGroupName:
(
NSString
*
)
inName
{
NSParameterAssert
(
inName
!=
nil
);
if
([
m_remoteGroupNames
containsObject
:
inName
])
return
;
[
m_remoteGroupNames
addObject
:
inName
];
[
self
remoteGroupingChanged
];
}
-
(
void
)
removeRemoteGroupName:
(
NSString
*
)
inName
{
NSParameterAssert
(
inName
!=
nil
);
if
(
!
[
m_remoteGroupNames
containsObject
:
inName
])
return
;
[
m_remoteGroupNames
removeObject
:
inName
];
[
self
remoteGroupingChanged
];
}
-
(
NSUInteger
)
countOfRemoteGroupNames
{
return
m_remoteGroupNames
.
count
;
}
-
(
NSSet
*
)
remoteGroups
{
NSMutableSet
*
groups
=
[
NSMutableSet
set
];
for
(
NSString
*
remoteGroup
in
m_remoteGroupNames
)
{
[
groups
addObject
:
[
adium
.
contactController
groupWithUID
:
remoteGroup
]];
}
return
groups
;
}
-
(
void
)
remoteGroupingChanged
{
NSUInteger
remoteGroupCount
=
m_remoteGroupNames
.
count
;
if
(
remoteGroupCount
==
0
)
[
AIUserIcons
flushCacheForObject
:
self
];
[
self
restoreGrouping
];
if
(
self
.
isStranger
!=
(
remoteGroupCount
==
0
))
{
[
self
setValue
:
[
NSNumber
numberWithBool
:
remoteGroupCount
>
0
]
forProperty
:
@"notAStranger"
notify
:
NotifyLater
];
[
self
notifyOfChangedPropertiesSilently
:
YES
];
}
}
//An AIListContact normally groups based on its remoteGroupNames (if it is not within a metaContact).
//Restore this grouping.
-
(
void
)
restoreGrouping
{
if
(
self
.
metaContact
)
{
[
self
.
metaContact
updateRemoteGroupingOfContact
:
self
];
return
;
}
//Create a group for the contact even if contact list groups aren't on,
//otherwise requests for all the contact list groups will return nothing
NSMutableSet
*
groups
=
[
NSMutableSet
set
];
for
(
NSString
*
remoteGroupName
in
m_remoteGroupNames
)
{
AIListGroup
*
localGroup
=
[
adium
.
contactController
groupWithUID
:
remoteGroupName
];
if
(
!
adium
.
contactController
.
useContactListGroups
)
localGroup
=
adium
.
contactController
.
contactList
;
else
if
(
adium
.
contactController
.
useOfflineGroup
&&
!
self
.
online
&&
!
self
.
alwaysVisible
)
localGroup
=
adium
.
contactController
.
offlineGroup
;
[
groups
addObject
:
localGroup
];
}
[
adium
.
contactController
_moveContactLocally
:
self
fromGroups
:
self
.
groups
toGroups
:
groups
];
}
#pragma mark Names
/*!
* @brief Display name
*
* Display name, drawing first from any externally-provided display name, then falling back to
* the formatted UID.
*
* A listContact attempts to have the same displayName as its containing contact (potentially its metaContact).
* If it is not in a metaContact, its display name is returned by super.displayName
*/
-
(
NSString
*
)
displayName
{
AIMetaContact
*
meta
=
self
.
metaContact
;
NSString
*
displayName
=
meta
?
meta
.
displayName
:
super
.
displayName
;
//If a display name was found, return it; otherwise, return the formattedUID
return
displayName
?
displayName
:
self
.
formattedUID
;
}
/*!
* @brief Own display name
*
* Returns the display name without trying to account for a metaContact. Exists for use by AIMetaContact to avoid
* infinite recursion by its displayName calling our displayName calling its displayName and so on.
*/
-
(
NSString
*
)
ownDisplayName
{
return
super
.
displayName
;
}
/*!
* @brief This contact's serverside display name, which is generally specificed by the contact remotely
*
* @result The serverside display name, or nil if none is set
*/
-
(
NSString
*
)
serversideDisplayName
{
return
[
self
valueForProperty
:
@"serverDisplayName"
];
}
-
(
void
)
setServersideAlias:
(
NSString
*
)
alias
silently
:(
BOOL
)
silent
{
BOOL
changes
=
NO
;
BOOL
displayNameChanges
=
NO
;
//This is the server display name. Set it as such.
if
(
!
[
alias
isEqualToString
:
[
self
valueForProperty
:
@"serverDisplayName"
]])
{
//Set the server display name property as the full display name
[
self
setValue
:
alias
forProperty
:
@"serverDisplayName"
notify
:
NotifyLater
];
changes
=
YES
;
}
NSMutableString
*
cleanedAlias
;
//Remove any newlines, since we won't want them anywhere below
cleanedAlias
=
[
alias
mutableCopy
];
[
cleanedAlias
convertNewlinesToSlashes
];
AIMutableOwnerArray
*
displayNameArray
=
[
self
displayArrayForKey
:
@"Display Name"
];
NSString
*
oldDisplayName
=
[
displayNameArray
objectValue
];
//If the mutableOwnerArray's current value isn't identical to this alias, we should set it
if
(
!
[[
displayNameArray
objectWithOwner
:
self
.
account
]
isEqualToString
:
cleanedAlias
])
{
[
displayNameArray
setObject
:
cleanedAlias
withOwner
:
self
.
account
priorityLevel
:
Low_Priority
];
//If this causes the object value to change, we need to request a manual update of the display name
if
(
oldDisplayName
!=
[
displayNameArray
objectValue
])
{
displayNameChanges
=
YES
;
}
}
if
(
changes
)
{
//Apply any changes
[
self
notifyOfChangedPropertiesSilently
:
silent
];
}
if
(
displayNameChanges
)
{
//Request an alias change
[[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
Contact_ApplyDisplayName
object
:
self
userInfo
:[
NSDictionary
dictionaryWithObject
:
[
NSNumber
numberWithBool
:
YES
]
forKey
:
@"Notify"
]];
}
[
cleanedAlias
release
];
}
/*!
* @brief The way this object's name should be spoken
*
* If not found, the display name is returned.
*/
-
(
NSString
*
)
phoneticName
{
AIMetaContact
*
meta
=
self
.
metaContact
;
NSString
*
phoneticName
;
phoneticName
=
meta
?
meta
.
phoneticName
:
super
.
phoneticName
;;
//If a display name was found, return it; otherwise, return the formattedUID
return
phoneticName
?
phoneticName
:
self
.
displayName
;
}
/*!
* @brief Own phonetic name
*
* Returns the phonetic name without trying to account for a metaContact. Exists for use by AIMetaContact to avoid
* infinite recursion by its phoneticName calling our phoneticName calling its phoneticName and so on.
*/
-
(
NSString
*
)
ownPhoneticName
{
return
super
.
phoneticName
;
}
#pragma mark Properties
/*!
* @brief Set online
*/
-
(
void
)
setOnline:
(
BOOL
)
online
notify:
(
NotifyTiming
)
notify
silently:
(
BOOL
)
silent
{
if
(
online
!=
self
.
online
)
{
[
self
setValue
:
[
NSNumber
numberWithBool
:
online
]
forProperty
:
@"isOnline"
notify
:
notify
];
if
(
!
silent
)
{
[
self
setValue
:
[
NSNumber
numberWithBool
:
YES
]
forProperty
:(
online
?
@"signedOn"
:
@"signedOff"
)
notify
:
notify
];
[
self
setValue
:
nil
forProperty
:(
online
?
@"signedOff"
:
@"signedOn"
)
notify
:
notify
];
[
self
setValue
:
nil
forProperty
:(
online
?
@"signedOn"
:
@"signedOff"
)
afterDelay
:
CONTACT_SIGN_ON_OR_OFF_PERSISTENCE_DELAY
];
}
if
(
online
)
{
if
(
notify
==
NotifyNow
)
{
[
self
notifyOfChangedPropertiesSilently
:
silent
];
}
}
else
{
//Will always notify
[
self
.
account
removePropertyValuesFromContact
:
self
silently
:
silent
];
}
}
}
/*!
* @brief Set the sign on date
*/
-
(
void
)
setSignonDate:
(
NSDate
*
)
signonDate
notify:
(
NotifyTiming
)
notify
{
[
self
setValue
:
signonDate
forProperty
:
@"Signon Date"
notify
:
notify
];
}
/*!
* @brief Date this contact signed on, if available
*/
-
(
NSDate
*
)
signonDate
{
return
[
self
valueForProperty
:
@"Signon Date"
];
}
/*!
* @brief Set the idle state
*
* @param isIdle YES if the contact is idle
* @param idleSinceDate The date this contact went idle. Only relevant if isIdle is YES
* @param notify The NotifyTiming
*/
-
(
void
)
setIdle:
(
BOOL
)
inIsIdle
sinceDate:
(
NSDate
*
)
idleSinceDate
notify:
(
NotifyTiming
)
notify
{
if
(
inIsIdle
)
{
if
(
idleSinceDate
)
{
[
self
setValue
:
idleSinceDate
forProperty
:
@"idleSince"
notify
:
NotifyLater
];
}
else
{
//No idleSinceDate means we are Idle but don't know how long, so set to -1
[
self
setValue
:
[
NSNumber
numberWithInt
:
-1
]
forProperty
:
@"idle"
notify
:
NotifyLater
];
}
}
else
{
[
self
setValue
:
nil
forProperty
:
@"idleSince"
notify
:
NotifyLater
];
[
self
setValue
:
nil
forProperty
:
@"idle"
notify
:
NotifyLater
];
}
/* @"idle", for a contact with an IdleSince date, will be changing every minute. @"isIdle" provides observers a way
* to perform an action when the contact becomes/comes back from idle, regardless of whether an IdleSince is available,
* without having to do that action every minute for other contacts.
*/
[
self
setValue
:
[
NSNumber
numberWithBool
:
inIsIdle
]
forProperty
:
@"isIdle"
notify
:
NotifyLater
];
//Apply any changes
if
(
notify
==
NotifyNow
)
{
[
self
notifyOfChangedPropertiesSilently
:
NO
];
}
}
-
(
void
)
setServersideIconData:
(
NSData
*
)
iconData
notify:
(
NotifyTiming
)
notify
{
[
AIUserIcons
setServersideIconData
:
iconData
forObject
:
self
notify
:
notify
];
}
/*!
* @brief Set the warning level
*
* @param warningLevel The warning level, an integer between 0 and 100
* @param notify The NotifyTiming
*/
-
(
void
)
setWarningLevel:
(
NSInteger
)
warningLevel
notify:
(
NotifyTiming
)
notify
{
if
(
warningLevel
!=
self
.
warningLevel
)
{
[
self
setValue
:
[
NSNumber
numberWithInteger
:
warningLevel
]
forProperty
:
@"Warning"
notify
:
notify
];
}
}
/*!
* @brief Warning level
*
* @result The warning level, an integer between 0 and 100
*/
-
(
NSInteger
)
warningLevel
{
return
[
self
integerValueForProperty
:
@"Warning"
];
}
/*!
* @brief Set the profile array
*/
-
(
void
)
setProfileArray:
(
NSArray
*
)
array
notify:
(
NotifyTiming
)
notify
{
[
self
setValue
:
array
forProperty
:
@"ProfileArray"
notify
:
notify
];
}
/*!
* @brief The profile array
*/
-
(
NSArray
*
)
profileArray
{
return
[
self
valueForProperty
:
@"ProfileArray"
];
}
/*!
* @brief Set the profile
*/
-
(
void
)
setProfile:
(
NSAttributedString
*
)
profile
notify:
(
NotifyTiming
)
notify
{
[
self
setValue
:
profile
forProperty
:
@"textProfile"
notify
:
notify
];
}
/*!
* @brief Profile
*/
-
(
NSAttributedString
*
)
profile
{
return
[
self
valueForProperty
:
@"textProfile"
];
}
/*!
* @brief Is this contact a stranger?
*
* A listContact is a stranger if it has a nil remoteGroupName
*/
-
(
BOOL
)
isStranger
{
return
!
[
self
boolValueForProperty
:
@"notAStranger"
];
}
/*!
* @brief If this contact intentionally on the contact list?
*/
-
(
BOOL
)
isIntentionallyNotAStranger
{
return
!
self
.
isStranger
&&
[
self
.
account
isContactIntentionallyListed
:
self
];
}
/*!
* @brief Is this object connected via a mobile device?
*/
-
(
BOOL
)
isMobile
{
return
[
self
boolValueForProperty
:
@"isMobile"
];
}
/*!
* @brief Set if this contact is mobile
*/
-
(
void
)
setIsMobile:
(
BOOL
)
inIsMobile
notify:
(
NotifyTiming
)
notify
{
[
self
setValue
:
[
NSNumber
numberWithBool
:
inIsMobile
]
forProperty
:
@"isMobile"
notify
:
notify
];
}
/*!
* @brief Is this contact blocked?
*
* @result A boolean indicating if the contact is blocked or not
*/
-
(
BOOL
)
isBlocked
{
return
[
self
boolValueForProperty
:
KEY_IS_BLOCKED
];
}
-
(
void
)
setIsBlocked:
(
BOOL
)
yesOrNo
updateList:
(
BOOL
)
addToPrivacyLists
{
[
self
setIsOnPrivacyList
:
yesOrNo
updateList
:
addToPrivacyLists
privacyType
:
AIPrivacyTypeDeny
];
}
-
(
void
)
setIsAllowed:
(
BOOL
)
yesOrNo
updateList:
(
BOOL
)
addToPrivacyLists
{
[
self
setIsOnPrivacyList
:
yesOrNo
updateList
:
addToPrivacyLists
privacyType
:
AIPrivacyTypePermit
];
}
/*!
* @brief Set if this contact is on the privacy list
*/
-
(
void
)
setIsOnPrivacyList:
(
BOOL
)
shouldBeBlocked
updateList:
(
BOOL
)
addToPrivacyLists
privacyType:
(
AIPrivacyType
)
privType
{
if
(
addToPrivacyLists
)
{
//caller of this method wants to actually block or unblock the contact, rather than just update the property
if
(
!
[
self
.
account
conformsToProtocol
:
@
protocol
(
AIAccount_Privacy
)])
{
NSLog
(
@"Privacy is not supported on contacts for the account: %@"
,
self
.
account
);
return
;
}
id
<
AIAccount_Privacy
>
contactAccount
=
(
id
<
AIAccount_Privacy
>
)
self
.
account
;
BOOL
contactIsBlocked
=
[[
contactAccount
listObjectsOnPrivacyList
:
privType
]
containsObject
:
self
];
if
(
shouldBeBlocked
==
contactIsBlocked
)
return
;
BOOL
result
=
NO
;
if
(
shouldBeBlocked
)
result
=
[
contactAccount
addListObject
:
self
toPrivacyList
:
privType
];
else
result
=
[
contactAccount
removeListObject
:
self
fromPrivacyList
:
privType
];
//Don't update the property if we didn't change anything
if
(
!
result
)
return
;
}
[
self
setValue
:
[
NSNumber
numberWithBool
:
((
privType
==
AIPrivacyTypeDeny
)
==
shouldBeBlocked
)]
forProperty
:
KEY_IS_BLOCKED
notify
:
NotifyNow
];
}
-
(
AIEncryptedChatPreference
)
encryptedChatPreferences
{
AIEncryptedChatPreference
pref
=
EncryptedChat_Default
;
//Get the contact's preference (or metacontact's)
NSNumber
*
prefNumber
=
[
self
.
parentContact
preferenceForKey
:
KEY_ENCRYPTED_CHAT_PREFERENCE
group
:
GROUP_ENCRYPTION
];
//If that turned up nothing, check all the groups it's in
if
(
!
prefNumber
||
[
prefNumber
integerValue
]
==
EncryptedChat_Default
)
{
for
(
AIListGroup
*
group
in
self
.
parentContact
.
groups
)
{
if
((
prefNumber
=
[
group
preferenceForKey
:
KEY_ENCRYPTED_CHAT_PREFERENCE
group
:
GROUP_ENCRYPTION
]))
break
;
}
}
//If that turned up nothing, check global prefs
if
(
!
prefNumber
)
prefNumber
=
[
adium
.
preferenceController
preferenceForKey
:
KEY_ENCRYPTED_CHAT_PREFERENCE
group
:
GROUP_ENCRYPTION
];
//If no contact preference or the contact is set to use the default, use the account preference
if
(
!
prefNumber
||
([
prefNumber
integerValue
]
==
EncryptedChat_Default
))
{
prefNumber
=
[
self
.
account
preferenceForKey
:
KEY_ENCRYPTED_CHAT_PREFERENCE
group
:
GROUP_ENCRYPTION
];
}
if
(
prefNumber
)
pref
=
[
prefNumber
intValue
];
return
pref
;
}
-
(
void
)
setAlwaysVisible:
(
BOOL
)
inVisible
{
[
super
setAlwaysVisible
:
inVisible
];
[
self
restoreGrouping
];
}
-
(
BOOL
)
alwaysVisible
{
if
(
self
.
metaContact
)
{
return
self
.
metaContact
.
alwaysVisible
;
}
return
[
super
alwaysVisible
];
}
#pragma mark Status
/*!
* @brief Determine the status message to be displayed in the contact list
*
* Look at the contact's status message.
* Failing that, look for a statusName, which might be something like "DND" or "Free for Chat"
* and look up the localized description of it.
*/
-
(
NSAttributedString
*
)
contactListStatusMessage
{
NSAttributedString
*
contactListStatusMessage
=
self
.
statusMessage
;
if
(
!
contactListStatusMessage
)
{
NSString
*
statusName
=
self
.
statusName
;
if
(
statusName
)
{
NSString
*
descriptionOfStatus
=
[
adium
.
statusController
localizedDescriptionForStatusName
:
statusName
statusType
:
self
.
statusType
];
if
(
descriptionOfStatus
)
contactListStatusMessage
=
[[[
NSAttributedString
alloc
]
initWithString
:
descriptionOfStatus
]
autorelease
];
}
}
return
contactListStatusMessage
;
}
/*!
* @brief Are sounds for this contact muted?
*/
-
(
BOOL
)
soundsAreMuted
{
return
[
self
.
account
.
statusState
mutesSound
];
}
#pragma mark Parents
/*!
* @brief This object's parent AIListContact
*
* The parent AIListContact is the appropriate place to apply preferences specific to this contact so that such
* preferences are also applied to other AIListContacts in the same meta contact, if necessary.
*
* @result Either this contact or some more-encompassing contact which ultimately contains it.
*/
-
(
AIListContact
*
)
parentContact
{
return
self
.
metaContact
?:
self
;
}
-
(
BOOL
)
containsObject:
(
AIListObject
*
)
object
{
return
NO
;
}
-
(
NSSet
*
)
containingObjects
{
if
(
metaContact
)
return
[
NSSet
setWithObject
:
metaContact
];
return
super
.
containingObjects
;
}
/*!
* @brief Can this object be part of a metacontact?
*/
-
(
BOOL
)
canJoinMetaContacts
{
return
YES
;
}
-
(
AIMetaContact
*
)
metaContact
{
return
metaContact
;
}
-
(
void
)
setMetaContact:
(
AIMetaContact
*
)
meta
{
metaContact
=
meta
;
/* Ugly: Subclass accessing superclass's ivar */
[
m_groups
removeAllObjects
];
}
-
(
BOOL
)
existsServerside
{
return
YES
;
}
-
(
void
)
removeFromGroup:
(
AIListObject
<
AIContainingObject
>
*
)
group
{
if
(
self
.
account
.
online
)
{
if
(
group
==
adium
.
contactController
.
contactList
||
group
==
adium
.
contactController
.
offlineGroup
)
{
[
self
.
account
removeContacts
:
[
NSArray
arrayWithObject
:
self
]
fromGroups
:[
self
.
remoteGroups
allObjects
]];
}
else
{
[
self
.
account
removeContacts
:
[
NSArray
arrayWithObject
:
self
]
fromGroups
:[
NSArray
arrayWithObject
:
group
]];
}
}
}
#pragma mark Equality
/*
- (BOOL)isEqual:(id)anObject
{
return ([anObject isMemberOfClass:[self class]] &&
[[(AIListContact *)anObject internalUniqueObjectID] isEqualToString:[self internalUniqueObjectID]]);
}
*/
//AppleScript ----------------------------------------------------------------------------------------------------------
#pragma mark AppleScript
-
(
id
)
sendScriptCommand:
(
NSScriptCommand
*
)
command
{
NSDictionary
*
evaluatedArguments
=
[
command
evaluatedArguments
];
NSString
*
message
=
[
evaluatedArguments
objectForKey
:
@"message"
];
AIAccount
*
targetAccount
=
[
evaluatedArguments
objectForKey
:
@"account"
];
NSString
*
filePath
=
[
evaluatedArguments
objectForKey
:
@"filePath"
];
AIListContact
*
targetMessagingContact
=
self
;
AIListContact
*
targetFileTransferContact
=
nil
;
if
(
targetAccount
)
{
if
(
self
.
account
!=
account
)
targetMessagingContact
=
[
adium
.
contactController
contactWithService
:
self
.
service
account
:
account
UID
:
self
.
UID
];
targetFileTransferContact
=
targetMessagingContact
;
}
//Send any message we were told to send
if
(
message
&&
[
message
length
])
{
AIChat
*
chat
;
BOOL
autoreply
=
[[
evaluatedArguments
objectForKey
:
@"autoreply"
]
boolValue
];
//Make sure we know where we are sending the message - if we don't have a target yet, find the best contact for
//sending CONTENT_MESSAGE_TYPE.
if
(
!
targetMessagingContact
)
{
//Get the target contact. This could be the same contact, an identical contact on another account,
//or a subcontact (if we're talking about a metaContact, for example)
targetMessagingContact
=
[
adium
.
contactController
preferredContactForContentType
:
CONTENT_MESSAGE_TYPE
forListContact
:
self
];
targetAccount
=
targetMessagingContact
.
account
;
}
if
(
targetMessagingContact
)
{
chat
=
[
adium
.
chatController
openChatWithContact
:
targetMessagingContact
onPreferredAccount
:
NO
];
//Take the string and turn it into an attributed string (in case we were passed HTML)
NSAttributedString
*
attributedMessage
=
[
AIHTMLDecoder
decodeHTML
:
message
];
AIContentMessage
*
messageContent
;
messageContent
=
[
AIContentMessage
messageInChat
:
chat
withSource
:
targetAccount
destination
:
targetMessagingContact
date
:
nil
message
:
attributedMessage
autoreply
:
autoreply
];
[
adium
.
contentController
sendContentObject
:
messageContent
];
}
else
{
AILogWithSignature
(
@"No contact available to receive a message to %@"
,
self
);
}
}
//Send any file we were told to send
if
(
filePath
&&
[
filePath
length
])
{
//Make sure we know where we are sending the file - if we don't have a target yet, find the best contact for
//sending CONTENT_FILE_TRANSFER_TYPE.
if
(
!
targetFileTransferContact
)
{
//Get the target contact. This could be the same contact, an identical contact on another account,
//or a subcontact (if we're talking about a metaContact, for example)
targetFileTransferContact
=
[
adium
.
contactController
preferredContactForContentType
:
CONTENT_FILE_TRANSFER_TYPE
forListContact
:
self
];
}
if
(
targetFileTransferContact
)
{
[
adium
.
fileTransferController
sendFile
:
filePath
toListContact
:
targetFileTransferContact
];
}
else
{
AILogWithSignature
(
@"No contact available to receive files to %@"
,
self
);
NSBeep
();
}
}
return
nil
;
}
//Writing Direction ----------------------------------------------------------------------------------------------------------
#pragma mark Writing Direction
-
(
NSWritingDirection
)
defaultBaseWritingDirection
{
static
NSWritingDirection
defaultBaseWritingDirection
;
static
BOOL
determinedDefaultBaseWritingDirection
=
NO
;
if
(
!
determinedDefaultBaseWritingDirection
)
{
/* Use the default writing direction of the language of the user's locale (and not the language
* of the active localization). By that, we assume most users are mostly talking to their local friends.
*/
NSString
*
lang
=
[[
NSLocale
currentLocale
]
objectForKey
:
NSLocaleLanguageCode
];
defaultBaseWritingDirection
=
[
NSParagraphStyle
defaultWritingDirectionForLanguage
:
lang
];
determinedDefaultBaseWritingDirection
=
YES
;
}
return
defaultBaseWritingDirection
;
}
-
(
NSWritingDirection
)
baseWritingDirection
{
NSNumber
*
dir
=
[
self
preferenceForKey
:
KEY_BASE_WRITING_DIRECTION
group
:
PREF_GROUP_WRITING_DIRECTION
];
return
(
dir
?
[
dir
intValue
]
:
[
self
defaultBaseWritingDirection
]);
}
-
(
void
)
setBaseWritingDirection:
(
NSWritingDirection
)
direction
{
[
self
setPreference
:
[
NSNumber
numberWithInteger
:
direction
]
forKey
:
KEY_BASE_WRITING_DIRECTION
group
:
PREF_GROUP_WRITING_DIRECTION
];
}
#pragma mark Address Book
-
(
ABPerson
*
)
addressBookPerson
{
return
[
AIAddressBookController
personForListObject
:
self
.
parentContact
];
}
-
(
void
)
setAddressBookPerson:
(
ABPerson
*
)
inPerson
{
[
self
.
parentContact
setPreference
:
[
inPerson
uniqueId
]
forKey
:
KEY_AB_UNIQUE_ID
group
:
PREF_GROUP_ADDRESSBOOK
];
}
#pragma mark Applescript
-
(
NSScriptObjectSpecifier
*
)
objectSpecifier
{
NSScriptObjectSpecifier
*
containerRef
=
self
.
account
.
objectSpecifier
;
return
[[[
NSNameSpecifier
allocWithZone
:
[
self
zone
]]
initWithContainerClassDescription
:[
containerRef
keyClassDescription
]
containerSpecifier
:
containerRef
key
:
@"contacts"
name
:
self
.
UID
]
autorelease
];
}
-
(
BOOL
)
scriptingBlocked
{
return
[
self
isBlocked
];
}
-
(
void
)
setScriptingBlocked:
(
BOOL
)
b
{
[
self
setIsBlocked
:
b
updateList
:
YES
];
}
@dynamic
containingObject
;
@end