Examine any open chats when a bookmark is initialized to set its defaults. Fixes #12771.
This was occurring since the "saved chats" were opened/created before the AIListBookmark.
5 // Created by Erik Beerepoot on 19/07/07.
6 // Copyright 2007 Adium Team. All rights reserved.
9 #import "AIListBookmark.h"
10 #import <Adium/AIAccount.h>
11 #import <Adium/AIListGroup.h>
12 #import <Adium/AIAccountControllerProtocol.h>
13 #import <Adium/AIInterfaceControllerProtocol.h>
14 #import <Adium/AIChatControllerProtocol.h>
15 #import <Adium/AIContactControllerProtocol.h>
16 #import <Adium/AIUserIcons.h>
17 #import <Adium/AIService.h>
18 #import <Adium/AIChat.h>
19 #import <Adium/AIContactList.h>
20 #import <AIUtilities/AIAttributedStringAdditions.h>
22 #define KEY_CONTAINING_OBJECT_UID @"ContainingObjectUID"
23 #define OBJECT_STATUS_CACHE @"Object Status Cache"
25 #define KEY_ACCOUNT_INTERNAL_ID @"AccountInternalObjectID"
27 @interface AIListBookmark ()
28 - (BOOL)chatIsOurs:(AIChat *)chat;
29 - (AIChat *)openChatWithoutActivating;
30 - (void)restoreGrouping;
32 - (void)claimChatIfOurs:(AIChat *)chat;
35 @implementation AIListBookmark
37 @synthesize name, password, chatCreationDictionary;
39 - (id)initWithUID:(NSString *)inUID
40 account:(AIAccount *)inAccount
41 service:(AIService *)inService
42 dictionary:(NSDictionary *)inChatCreationDictionary
43 name:(NSString *)inName
45 if ((self = [super initWithUID:inUID
47 service:inService])) {
48 chatCreationDictionary = [inChatCreationDictionary copy];
51 [adium.chatController registerChatObserver:self];
53 [self.account addObserver:self
55 options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial)
58 [[NSNotificationCenter defaultCenter] addObserver:self
59 selector:@selector(chatDidOpen:)
63 // Scan all open chats to claim them, if we loaded after they were available.
64 for (AIChat *chat in adium.interfaceController.openChats) {
65 [self claimChatIfOurs:chat];
68 AILog(@"Created %@", self);
75 -(id)initWithChat:(AIChat *)inChat
77 if ((self = [self initWithUID:[NSString stringWithFormat:@"Bookmark:%@", inChat.uniqueChatID]
78 account:inChat.account
79 service:inChat.account.service
80 dictionary:inChat.chatCreationDictionary
82 [self setDisplayName:inChat.displayName];
88 - (id)initWithCoder:(NSCoder *)decoder
90 AIAccount *myAccount = [adium.accountController accountWithInternalObjectID:[decoder decodeObjectForKey:KEY_ACCOUNT_INTERNAL_ID]];
97 if ((self = [self initWithUID:[decoder decodeObjectForKey:@"UID"]
99 service:[adium.accountController firstServiceWithServiceID:[decoder decodeObjectForKey:@"ServiceID"]]
100 dictionary:[decoder decodeObjectForKey:@"chatCreationDictionary"]
101 name:[decoder decodeObjectForKey:@"name"]])) {
102 [self restoreGrouping];
108 - (void)encodeWithCoder:(NSCoder *)encoder
110 [encoder encodeObject:self.UID forKey:@"UID"];
111 [encoder encodeObject:self.account.internalObjectID forKey:KEY_ACCOUNT_INTERNAL_ID];
112 [encoder encodeObject:self.service.serviceID forKey:@"ServiceID"];
113 [encoder encodeObject:self.chatCreationDictionary forKey:@"chatCreationDictionary"];
114 [encoder encodeObject:name forKey:@"name"];
119 [name release]; name = nil;
120 [chatCreationDictionary release]; chatCreationDictionary = nil;
121 [password release]; password = nil;
123 [[NSNotificationCenter defaultCenter] removeObserver:self];
124 [adium.chatController unregisterChatObserver:self];
125 [self.account removeObserver:self forKeyPath:@"Online"];
131 * @brief Remove ourself
133 * We've been asked to be removed. Ask the contact controller to do so.
135 - (void)removeFromGroup:(AIListObject <AIContainingObject> *)group
137 [adium.contactController removeBookmark:self];
141 * @brief Our formatted UID
143 * If we're in an active chat, returns the name of the chat; otherwise, our UID.
145 - (NSString *)formattedUID
147 AIChat *chat = [adium.chatController existingChatWithName:[self name]
148 onAccount:self.account];
150 if ([self chatIsOurs:chat]) {
157 - (BOOL) existsServerside
159 return NO; //TODO: protocols where this can be yes, like XMPP
163 * @brief Internal ID for this object
165 * An object ID generated by Adium that is shared by all objects which are, to most intents and purposes, identical to
166 * this object. Ths ID is composed of the service ID and UID, so any object with identical services and object IDs
167 * will have the same value here.
169 - (NSString *)internalObjectID
171 if (!internalObjectID) {
172 NSAssert(self.account != nil, @"Null list bookmark account - make sure you didn't try to touch the internalObjectID before it was loaded.");
174 // We're not like any other bookmarks by the same name.
175 internalObjectID = [[NSString stringWithFormat:@"%@.%@.%@", self.service.serviceID, self.UID, self.account.UID] retain];
178 return internalObjectID;
182 * @brief Set our display name
184 * Update the display name of our chat if our display name changes.
186 - (void)setDisplayName:(NSString *)inDisplayName
188 [super setDisplayName:inDisplayName];
190 AIChat *chat = [adium.chatController existingChatWithName:[self name]
191 onAccount:self.account];
193 if ([self chatIsOurs:chat]) {
194 chat.displayName = self.displayName;
199 * @brief For a newly created bookmark, set the group that -restoreGrouping will move us to. This is saved, so has no use on existing bookmarks
201 - (void)setInitialGroup:(AIListGroup *)inGroup
203 [self setPreference:inGroup.UID
204 forKey:KEY_CONTAINING_OBJECT_UID
205 group:OBJECT_STATUS_CACHE];
209 * @brief Add a containing group
211 * When adding a containing group, save the group's UID so that we can rejoin the group next time.
213 - (void)addContainingGroup:(AIListGroup *)inGroup
215 [super addContainingGroup:inGroup];
217 NSString *groupUID = inGroup.UID;
218 NSString *savedGroupUID = [self preferenceForKey:KEY_CONTAINING_OBJECT_UID group:OBJECT_STATUS_CACHE];
220 if((!savedGroupUID || ![groupUID isEqualToString:savedGroupUID]) &&
221 (inGroup != adium.contactController.contactList)) {
222 // We either don't have a group, or this is a new, non-root-list group. Set our preference.
224 [self setPreference:groupUID
225 forKey:KEY_CONTAINING_OBJECT_UID
226 group:OBJECT_STATUS_CACHE];
231 * @brief Restore grouping
233 * When asked to restore grouping, move ourselves to the appropriate AIListGroup:
234 * - The root contact list if contact list groups are disabled, or
235 * - The last saved group. If the last saved group is missing for some reason, we move to "Bookmarks".
237 - (void)restoreGrouping
239 NSSet *targetGroup = nil;
240 // In reality, it's extremely unlikely the saved group would be lost.
241 NSString *savedGroupUID = [self preferenceForKey:KEY_CONTAINING_OBJECT_UID group:OBJECT_STATUS_CACHE] ?: AILocalizedString(@"Bookmarks", nil);
243 if (adium.contactController.useContactListGroups) {
244 targetGroup = [NSSet setWithObject:[adium.contactController groupWithUID:savedGroupUID]];
246 targetGroup = [NSSet setWithObject:adium.contactController.contactList];
249 [adium.contactController moveContact:self fromGroups:self.groups intoGroups:targetGroup];
253 * @brief Open our chat
255 * @return A chat for the bookmark
257 * This is called when we are double-clicked in the contact list.
258 * Either find or create a chat appropriately, and activate it.
262 AIChat *chat = [self openChatWithoutActivating];
265 [adium.interfaceController openChat:chat];
268 [adium.interfaceController setActiveChat:chat];
274 * @brief Open our chat without activating it
276 * This is called when joining automatically on connect, and within the
277 * method which opens on double click.
279 - (AIChat *)openChatWithoutActivating
281 AIChat *chat = [adium.chatController existingChatWithName:self.name
282 onAccount:self.account];
284 if (![self chatIsOurs:chat]) {
285 //Open a new group chat (bookmarked chat)
286 chat = [adium.chatController chatWithName:self.name
288 onAccount:self.account
289 chatCreationInfo:self.chatCreationDictionary];
296 * @brief A chat opened
298 * If this chat is our representation, set it up appropriately with our settings.
300 - (void)chatDidOpen:(NSNotification *)notification
302 AIChat *chat = [notification object];
304 [self claimChatIfOurs:chat];
308 * @brief Claim a chat
310 * Has no effect if the chat is not ours.
312 * Establishes any defaults we wish for our chats to have. Called when they are created.
314 - (void)claimChatIfOurs:(AIChat *)chat
316 if ([self chatIsOurs:chat]) {
317 chat.displayName = self.displayName;
322 * @brief Can this object be part of a metacontact?
324 * Bookmarks cannot join meta contacts.
326 - (BOOL)canJoinMetaContacts
332 * @brief Is this chat ours?
334 * If the chat's name, account, and creation dictionary matches ours, it should be considered ours.
336 - (BOOL)chatIsOurs:(AIChat *)chat
339 [chat.name isEqualToString:[self.account.service normalizeChatName:self.name]] &&
340 chat.account == self.account &&
341 ((!chat.chatCreationDictionary && !self.chatCreationDictionary) ||
342 ([chat.chatCreationDictionary isEqualToDictionary:self.chatCreationDictionary])));
346 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
348 if ([keyPath isEqualToString:@"Online"] && object == self.account) {
349 // If an account is just initially signing on, a -setOnline:notify:silently will still broadcast an event for the contact.
350 // The initial delay an account (usually) sets is done after they're set as online, so these bookmarks would always fire.
351 // Thus, we have to use the secondary, silent notification so that the online gets propogated without the events.
352 [self setOnline:self.account.online notify:NotifyLater silently:YES];
353 [self notifyOfChangedPropertiesSilently:YES];
355 if (self.account.online && [[self preferenceForKey:KEY_AUTO_JOIN group:GROUP_LIST_BOOKMARK] boolValue]) {
356 [self openChatWithoutActivating];
361 - (NSSet *)updateChat:(AIChat *)inChat keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
363 if ([self chatIsOurs:inChat] && ([inModifiedKeys containsObject:KEY_UNVIEWED_CONTENT] || [inModifiedKeys containsObject:KEY_UNVIEWED_MENTION])) {
364 NSString *statusMessage = nil;
366 if (inChat.unviewedMentionCount) {
367 // We contain mentions; display both this and the content count.
368 if (inChat.unviewedMentionCount > 1) {
369 statusMessage = [NSString stringWithFormat:AILocalizedString(@"%d mentions, %d messages", "Status message for a bookmark (>1 mention, >1 messages)"),
370 inChat.unviewedMentionCount, inChat.unviewedContentCount];
371 } else if (inChat.unviewedContentCount > 1) {
372 statusMessage = [NSString stringWithFormat:AILocalizedString(@"1 mention, %d messages", "Status message for a bookmark (1 mention, >1 messages)"),
373 inChat.unviewedContentCount];
375 statusMessage = AILocalizedString(@"1 mention, 1 message", "Status message for a bookmark (1 mention, 1 message)");
377 } else if (inChat.unviewedContentCount) {
378 // We don't contain mentions; display the content count.
379 if (inChat.unviewedContentCount > 1) {
380 statusMessage = [NSString stringWithFormat:AILocalizedString(@"%d messages", "Status message for a bookmark (>1 messages)"),
381 inChat.unviewedContentCount];
383 statusMessage = AILocalizedString(@"1 message", "Status message for a bookmark (1 message)");
388 [self setStatusMessage:[NSAttributedString stringWithString:statusMessage] notify:NotifyNow];
390 [self setStatusMessage:nil notify:NotifyNow];
398 - (NSString *)description
400 return [NSString stringWithFormat:@"<%@:%x %@ - %@ on %@>",NSStringFromClass([self class]), self, self.formattedUID, [self chatCreationDictionary], self.account];