Frameworks/Adium Framework/Source/AIListBookmark.m
author Zachary West <zacw@adium.im>
Wed Oct 28 20:48:18 2009 -0400 (2009-10-28)
changeset 2804 2b6a6cd8c92f
parent 2360 7d004b148df5
child 3344 dc4039a09c76
permissions -rw-r--r--
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.
David@0
     1
//
David@0
     2
//  AIListBookmark.m
David@0
     3
//  Adium
David@0
     4
//
David@0
     5
//  Created by Erik Beerepoot on 19/07/07.
David@0
     6
//  Copyright 2007 Adium Team. All rights reserved.
David@0
     7
//
David@0
     8
David@0
     9
#import "AIListBookmark.h"
David@554
    10
#import <Adium/AIAccount.h>
David@554
    11
#import <Adium/AIListGroup.h>
David@0
    12
#import <Adium/AIAccountControllerProtocol.h>
David@0
    13
#import <Adium/AIInterfaceControllerProtocol.h>
David@0
    14
#import <Adium/AIChatControllerProtocol.h>
David@0
    15
#import <Adium/AIContactControllerProtocol.h>
David@0
    16
#import <Adium/AIUserIcons.h>
David@0
    17
#import <Adium/AIService.h>
David@0
    18
#import <Adium/AIChat.h>
David@52
    19
#import <Adium/AIContactList.h>
zacw@1530
    20
#import <AIUtilities/AIAttributedStringAdditions.h>
David@0
    21
zacw@1096
    22
#define	KEY_CONTAINING_OBJECT_UID	@"ContainingObjectUID"
David@0
    23
#define	OBJECT_STATUS_CACHE			@"Object Status Cache"
David@0
    24
David@0
    25
#define KEY_ACCOUNT_INTERNAL_ID		@"AccountInternalObjectID"
David@0
    26
David@84
    27
@interface AIListBookmark ()
zacw@841
    28
- (BOOL)chatIsOurs:(AIChat *)chat;
zacw@1687
    29
- (AIChat *)openChatWithoutActivating;
David@0
    30
- (void)restoreGrouping;
zacw@2804
    31
zacw@2804
    32
- (void)claimChatIfOurs:(AIChat *)chat;
David@0
    33
@end
David@0
    34
David@0
    35
@implementation AIListBookmark
zacw@1097
    36
zacw@1097
    37
@synthesize name, password, chatCreationDictionary;
zacw@1097
    38
zacw@1099
    39
- (id)initWithUID:(NSString *)inUID
zacw@1099
    40
		  account:(AIAccount *)inAccount
zacw@1099
    41
		  service:(AIService *)inService
zacw@1099
    42
	   dictionary:(NSDictionary *)inChatCreationDictionary
zacw@1099
    43
			 name:(NSString *)inName
David@0
    44
{
zacw@1099
    45
	if ((self = [super initWithUID:inUID
zacw@1099
    46
						   account:inAccount
zacw@1099
    47
						   service:inService])) {
zacw@1099
    48
		chatCreationDictionary = [inChatCreationDictionary copy];
zacw@1099
    49
		name = [inName copy];
zacw@1099
    50
		
zacw@1530
    51
		[adium.chatController registerChatObserver:self];
zacw@1530
    52
		
zacw@1099
    53
		[self.account addObserver:self
zacw@1099
    54
					   forKeyPath:@"Online"
zacw@1099
    55
						  options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial)
zacw@1099
    56
						  context:NULL];
zacw@1099
    57
		
David@1109
    58
		[[NSNotificationCenter defaultCenter] addObserver:self
zacw@1099
    59
									 selector:@selector(chatDidOpen:) 
zacw@1099
    60
										 name:Chat_DidOpen
zacw@1099
    61
									   object:nil];
zacw@1956
    62
		
zacw@2804
    63
		// Scan all open chats to claim them, if we loaded after they were available.
zacw@2804
    64
		for (AIChat *chat in adium.interfaceController.openChats) {
zacw@2804
    65
			[self claimChatIfOurs:chat];
zacw@2804
    66
		}
zacw@2804
    67
		
zacw@1956
    68
		AILog(@"Created %@", self);
zacw@1956
    69
		
zacw@1099
    70
	}
zacw@1096
    71
	
zacw@1099
    72
	return self;
David@0
    73
}
David@0
    74
David@0
    75
-(id)initWithChat:(AIChat *)inChat
David@0
    76
{
zacw@1961
    77
	if ((self = [self initWithUID:[NSString stringWithFormat:@"Bookmark:%@", inChat.uniqueChatID]
zacw@1961
    78
						  account:inChat.account
zacw@1961
    79
						  service:inChat.account.service
zacw@1961
    80
					   dictionary:inChat.chatCreationDictionary
zacw@1961
    81
							 name:inChat.name])) {
zacw@1961
    82
		[self setDisplayName:inChat.displayName];
zacw@1961
    83
	}
zacw@1961
    84
	
zacw@1961
    85
	return self;
David@0
    86
}
David@0
    87
David@0
    88
- (id)initWithCoder:(NSCoder *)decoder
David@0
    89
{
David@95
    90
	AIAccount *myAccount = [adium.accountController accountWithInternalObjectID:[decoder decodeObjectForKey:KEY_ACCOUNT_INTERNAL_ID]];
zacw@1099
    91
	
David@0
    92
	if (!myAccount) {
David@0
    93
		[self release];
David@0
    94
		return nil;
David@0
    95
	}
zacw@1099
    96
	
catfish@2166
    97
	if ((self = [self initWithUID:[decoder decodeObjectForKey:@"UID"]
catfish@2166
    98
						  account:myAccount
catfish@2166
    99
						  service:[adium.accountController firstServiceWithServiceID:[decoder decodeObjectForKey:@"ServiceID"]]
catfish@2166
   100
					   dictionary:[decoder decodeObjectForKey:@"chatCreationDictionary"]
catfish@2166
   101
							 name:[decoder decodeObjectForKey:@"name"]])) {
catfish@2166
   102
		[self restoreGrouping];
catfish@2166
   103
	}
catfish@2166
   104
	return self;
zacw@1099
   105
}
David@0
   106
David@0
   107
David@0
   108
- (void)encodeWithCoder:(NSCoder *)encoder
David@0
   109
{
David@427
   110
	[encoder encodeObject:self.UID forKey:@"UID"];
David@838
   111
	[encoder encodeObject:self.account.internalObjectID forKey:KEY_ACCOUNT_INTERNAL_ID];
David@715
   112
	[encoder encodeObject:self.service.serviceID forKey:@"ServiceID"];
David@715
   113
	[encoder encodeObject:self.chatCreationDictionary forKey:@"chatCreationDictionary"];
David@0
   114
	[encoder encodeObject:name forKey:@"name"];
David@0
   115
}
David@0
   116
David@0
   117
- (void)dealloc
David@0
   118
{
zacw@1099
   119
	[name release]; name = nil;
zacw@1099
   120
	[chatCreationDictionary release]; chatCreationDictionary = nil;
zacw@1099
   121
	[password release]; password = nil;
zacw@1099
   122
	
David@1109
   123
	[[NSNotificationCenter defaultCenter] removeObserver:self];
zacw@1530
   124
	[adium.chatController unregisterChatObserver:self];
David@837
   125
	[self.account removeObserver:self forKeyPath:@"Online"];
David@0
   126
David@0
   127
	[super dealloc];
David@0
   128
}
zacw@1097
   129
zacw@1097
   130
/*!
zacw@1097
   131
 * @brief Remove ourself
zacw@1097
   132
 *
zacw@1097
   133
 * We've been asked to be removed. Ask the contact controller to do so.
zacw@1097
   134
 */
zacw@2131
   135
- (void)removeFromGroup:(AIListObject <AIContainingObject> *)group
zacw@1097
   136
{
zacw@1097
   137
	[adium.contactController removeBookmark:self];
zacw@1097
   138
}
zacw@1097
   139
zacw@1097
   140
/*!
zacw@1097
   141
 * @brief Our formatted UID
zacw@1097
   142
 *
zacw@1097
   143
 * If we're in an active chat, returns the name of the chat; otherwise, our UID.
zacw@1097
   144
 */
David@0
   145
- (NSString *)formattedUID
David@0
   146
{
zacw@1097
   147
	AIChat *chat = [adium.chatController existingChatWithName:[self name]
zacw@1097
   148
													onAccount:self.account];
zacw@1097
   149
	
zacw@1097
   150
	if ([self chatIsOurs:chat]) {
zacw@1097
   151
		return chat.name;
zacw@1097
   152
	} else {
zacw@1097
   153
		return self.name;
zacw@1097
   154
	}
David@853
   155
}
David@853
   156
David@893
   157
- (BOOL) existsServerside
David@893
   158
{
David@893
   159
	return NO; //TODO: protocols where this can be yes, like XMPP
David@893
   160
}
David@893
   161
zacw@843
   162
/*!
zacw@1961
   163
 * @brief Internal ID for this object
zacw@1961
   164
 *
zacw@1961
   165
 * An object ID generated by Adium that is shared by all objects which are, to most intents and purposes, identical to
zacw@1961
   166
 * this object.  Ths ID is composed of the service ID and UID, so any object with identical services and object IDs
zacw@1961
   167
 * will have the same value here.
zacw@1961
   168
 */
zacw@1961
   169
- (NSString *)internalObjectID
zacw@1961
   170
{
zacw@1961
   171
	if (!internalObjectID) {
zacw@2137
   172
		NSAssert(self.account != nil, @"Null list bookmark account - make sure you didn't try to touch the internalObjectID before it was loaded.");
zacw@2136
   173
		
zacw@1961
   174
		// We're not like any other bookmarks by the same name.
zacw@1961
   175
		internalObjectID = [[NSString stringWithFormat:@"%@.%@.%@", self.service.serviceID, self.UID, self.account.UID] retain];
zacw@1961
   176
	}
zacw@1961
   177
	
zacw@1961
   178
	return internalObjectID;
zacw@1961
   179
}
zacw@1961
   180
zacw@1961
   181
/*!
zacw@843
   182
 * @brief Set our display name
zacw@843
   183
 *
zacw@843
   184
 * Update the display name of our chat if our display name changes.
zacw@843
   185
 */
zacw@843
   186
- (void)setDisplayName:(NSString *)inDisplayName
zacw@843
   187
{
zacw@843
   188
	[super setDisplayName:inDisplayName];
zacw@843
   189
	
zacw@843
   190
	AIChat *chat = [adium.chatController existingChatWithName:[self name]
zacw@843
   191
					onAccount:self.account];
zacw@843
   192
	
zacw@843
   193
	if ([self chatIsOurs:chat]) {
zacw@843
   194
		chat.displayName = self.displayName;
zacw@843
   195
	}
zacw@843
   196
}
zacw@843
   197
zacw@1097
   198
/*!
catfish@2166
   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
catfish@2166
   200
 */
catfish@2166
   201
- (void)setInitialGroup:(AIListGroup *)inGroup
catfish@2166
   202
{
catfish@2166
   203
	[self setPreference:inGroup.UID
catfish@2166
   204
				 forKey:KEY_CONTAINING_OBJECT_UID
catfish@2166
   205
				  group:OBJECT_STATUS_CACHE];	
catfish@2166
   206
}
catfish@2166
   207
catfish@2166
   208
/*!
zacw@1097
   209
 * @brief Add a containing group
zacw@1097
   210
 *
zacw@1097
   211
 * When adding a containing group, save the group's UID so that we can rejoin the group next time.
zacw@1097
   212
 */
zacw@1062
   213
- (void)addContainingGroup:(AIListGroup *)inGroup
David@0
   214
{
zacw@1096
   215
	[super addContainingGroup:inGroup];
zacw@1062
   216
	
zacw@1096
   217
	NSString *groupUID = inGroup.UID;
zacw@1096
   218
	NSString *savedGroupUID = [self preferenceForKey:KEY_CONTAINING_OBJECT_UID group:OBJECT_STATUS_CACHE];
zacw@1096
   219
	
zacw@1096
   220
	if((!savedGroupUID || ![groupUID isEqualToString:savedGroupUID]) &&
zacw@1096
   221
		(inGroup != adium.contactController.contactList)) {
zacw@1096
   222
		// We either don't have a group, or this is a new, non-root-list group. Set our preference.
zacw@1096
   223
		
zacw@1096
   224
		[self setPreference:groupUID
zacw@1096
   225
					 forKey:KEY_CONTAINING_OBJECT_UID
David@0
   226
					  group:OBJECT_STATUS_CACHE];
David@0
   227
	}
David@0
   228
}
David@0
   229
David@0
   230
/*!
zacw@1097
   231
 * @brief Restore grouping
zacw@1097
   232
 *
zacw@1097
   233
 * When asked to restore grouping, move ourselves to the appropriate AIListGroup:
zacw@1097
   234
 * - The root contact list if contact list groups are disabled, or
zacw@1097
   235
 * - The last saved group. If the last saved group is missing for some reason, we move to "Bookmarks".
David@0
   236
 */
David@0
   237
- (void)restoreGrouping
David@0
   238
{
David@1381
   239
	NSSet *targetGroup = nil;
zacw@1096
   240
	// In reality, it's extremely unlikely the saved group would be lost.
zacw@1096
   241
	NSString *savedGroupUID = [self preferenceForKey:KEY_CONTAINING_OBJECT_UID group:OBJECT_STATUS_CACHE] ?: AILocalizedString(@"Bookmarks", nil);
zacw@1096
   242
David@954
   243
	if (adium.contactController.useContactListGroups) {
David@1381
   244
		targetGroup = [NSSet setWithObject:[adium.contactController groupWithUID:savedGroupUID]];
David@954
   245
	} else {
David@1381
   246
		targetGroup = [NSSet setWithObject:adium.contactController.contactList];
David@0
   247
	}
David@0
   248
zacw@2128
   249
	[adium.contactController moveContact:self fromGroups:self.groups intoGroups:targetGroup];
David@0
   250
}
David@0
   251
zacw@1097
   252
/*!
zacw@1097
   253
 * @brief Open our chat
zacw@1097
   254
 *
zacw@2360
   255
 * @return A chat for the bookmark
zacw@2360
   256
 *
zacw@1097
   257
 * This is called when we are double-clicked in the contact list.
zacw@1097
   258
 * Either find or create a chat appropriately, and activate it.
zacw@1097
   259
 */
zacw@2360
   260
- (AIChat *)openChat
David@0
   261
{
zacw@1687
   262
	AIChat *chat = [self openChatWithoutActivating];
zacw@841
   263
	
zacw@841
   264
	if(!chat.isOpen) {
zacw@795
   265
		[adium.interfaceController openChat:chat];
zacw@841
   266
	}
zacw@841
   267
	
zacw@841
   268
	[adium.interfaceController setActiveChat:chat];
zacw@2360
   269
	
zacw@2360
   270
	return chat;
zacw@841
   271
}
zacw@841
   272
zacw@1097
   273
/*!
zacw@1687
   274
 * @brief Open our chat without activating it
zacw@1687
   275
 *
zacw@1687
   276
 * This is called when joining automatically on connect, and within the
zacw@1687
   277
 * method which opens on double click.
zacw@1687
   278
 */
zacw@1687
   279
- (AIChat *)openChatWithoutActivating
zacw@1687
   280
{
zacw@1687
   281
	AIChat *chat = [adium.chatController existingChatWithName:self.name
zacw@1687
   282
					onAccount:self.account];
zacw@1687
   283
	
zacw@1687
   284
	if (![self chatIsOurs:chat]) {
zacw@1687
   285
		//Open a new group chat (bookmarked chat)
zacw@1687
   286
		chat = [adium.chatController chatWithName:self.name
zacw@1687
   287
				identifier:NULL 
zacw@1687
   288
				onAccount:self.account 
zacw@1687
   289
				chatCreationInfo:self.chatCreationDictionary];
zacw@1687
   290
	}
zacw@1687
   291
	
zacw@1687
   292
	return chat;
zacw@1687
   293
}
zacw@1687
   294
zacw@1687
   295
/*!
zacw@1097
   296
 * @brief A chat opened
zacw@1097
   297
 *
zacw@1097
   298
 * If this chat is our representation, set it up appropriately with our settings.
zacw@1097
   299
 */
zacw@841
   300
- (void)chatDidOpen:(NSNotification *)notification
zacw@841
   301
{
zacw@841
   302
	AIChat *chat = [notification object];
zacw@2804
   303
zacw@2804
   304
	[self claimChatIfOurs:chat];
zacw@2804
   305
}
zacw@2804
   306
zacw@2804
   307
/*!
zacw@2804
   308
 * @brief Claim a chat
zacw@2804
   309
 *
zacw@2804
   310
 * Has no effect if the chat is not ours.
zacw@2804
   311
 *
zacw@2804
   312
 * Establishes any defaults we wish for our chats to have. Called when they are created.
zacw@2804
   313
 */
zacw@2804
   314
- (void)claimChatIfOurs:(AIChat *)chat
zacw@2804
   315
{
zacw@841
   316
	if ([self chatIsOurs:chat]) {
zacw@841
   317
		chat.displayName = self.displayName;
zacw@841
   318
	}
David@0
   319
}
David@0
   320
David@0
   321
/*!
David@0
   322
 * @brief Can this object be part of a metacontact?
David@0
   323
 *
zacw@1097
   324
 * Bookmarks cannot join meta contacts.
David@0
   325
 */
David@0
   326
- (BOOL)canJoinMetaContacts
David@0
   327
{
David@0
   328
	return NO;
David@0
   329
}
David@0
   330
zacw@841
   331
/*!
zacw@841
   332
 * @brief Is this chat ours?
zacw@841
   333
 *
zacw@841
   334
 * If the chat's name, account, and creation dictionary matches ours, it should be considered ours.
zacw@841
   335
 */
zacw@841
   336
- (BOOL)chatIsOurs:(AIChat *)chat
zacw@841
   337
{
zacw@841
   338
	return (chat &&
zacw@1960
   339
			[chat.name isEqualToString:[self.account.service normalizeChatName:self.name]] &&
zacw@841
   340
			chat.account == self.account &&
zacw@841
   341
			((!chat.chatCreationDictionary && !self.chatCreationDictionary) ||
zacw@841
   342
			 ([chat.chatCreationDictionary isEqualToDictionary:self.chatCreationDictionary])));
zacw@841
   343
}
zacw@841
   344
David@0
   345
#pragma mark -
David@0
   346
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
David@0
   347
{
David@434
   348
	if ([keyPath isEqualToString:@"Online"] && object == self.account) {
zacw@959
   349
		// If an account is just initially signing on, a -setOnline:notify:silently will still broadcast an event for the contact.
zacw@959
   350
		// The initial delay an account (usually) sets is done after they're set as online, so these bookmarks would always fire.
zacw@959
   351
		// Thus, we have to use the secondary, silent notification so that the online gets propogated without the events.
zacw@835
   352
		[self setOnline:self.account.online notify:NotifyLater silently:YES];
zacw@835
   353
		[self notifyOfChangedPropertiesSilently:YES];
zacw@1218
   354
		
zacw@1218
   355
		if (self.account.online && [[self preferenceForKey:KEY_AUTO_JOIN group:GROUP_LIST_BOOKMARK] boolValue]) {
zacw@1687
   356
			[self openChatWithoutActivating];
zacw@1218
   357
		}
David@0
   358
	}
David@0
   359
}
David@0
   360
zacw@1530
   361
- (NSSet *)updateChat:(AIChat *)inChat keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
zacw@1530
   362
{
zacw@1530
   363
	if ([self chatIsOurs:inChat] && ([inModifiedKeys containsObject:KEY_UNVIEWED_CONTENT] || [inModifiedKeys containsObject:KEY_UNVIEWED_MENTION])) {
zacw@1530
   364
		NSString *statusMessage = nil;
zacw@1530
   365
		
zacw@1530
   366
		if (inChat.unviewedMentionCount) {
zacw@1530
   367
			// We contain mentions; display both this and the content count.
zacw@1530
   368
			if (inChat.unviewedMentionCount > 1) {
zacw@1532
   369
				statusMessage = [NSString stringWithFormat:AILocalizedString(@"%d mentions, %d messages", "Status message for a bookmark (>1 mention, >1 messages)"),
zacw@1530
   370
								 inChat.unviewedMentionCount, inChat.unviewedContentCount];
zacw@1530
   371
			} else if (inChat.unviewedContentCount > 1) {
zacw@1532
   372
				statusMessage = [NSString stringWithFormat:AILocalizedString(@"1 mention, %d messages", "Status message for a bookmark (1 mention, >1 messages)"),
zacw@1530
   373
								 inChat.unviewedContentCount];
zacw@1530
   374
			} else {
zacw@1532
   375
				statusMessage = AILocalizedString(@"1 mention, 1 message", "Status message for a bookmark (1 mention, 1 message)");
zacw@1530
   376
			}
zacw@1530
   377
		} else if (inChat.unviewedContentCount) {
zacw@1530
   378
			// We don't contain mentions; display the content count.
zacw@1530
   379
			if (inChat.unviewedContentCount > 1) {
zacw@1532
   380
				statusMessage = [NSString stringWithFormat:AILocalizedString(@"%d messages", "Status message for a bookmark (>1 messages)"),
zacw@1530
   381
								 inChat.unviewedContentCount];
zacw@1530
   382
			} else {
zacw@1532
   383
				statusMessage = AILocalizedString(@"1 message", "Status message for a bookmark (1 message)");
zacw@1530
   384
			}
zacw@1530
   385
		}
zacw@1530
   386
zacw@1531
   387
		if (statusMessage) {
zacw@1531
   388
			[self setStatusMessage:[NSAttributedString stringWithString:statusMessage] notify:NotifyNow];
zacw@1531
   389
		} else {
zacw@1531
   390
			[self setStatusMessage:nil notify:NotifyNow];
zacw@1531
   391
		}
zacw@1530
   392
	}
zacw@1530
   393
	
zacw@1530
   394
	return nil;
zacw@1530
   395
}
zacw@1530
   396
David@0
   397
#pragma mark -
David@0
   398
- (NSString *)description
David@0
   399
{
David@837
   400
	return [NSString stringWithFormat:@"<%@:%x %@ - %@ on %@>",NSStringFromClass([self class]), self, self.formattedUID, [self chatCreationDictionary], self.account];
David@0
   401
}
David@0
   402
David@0
   403
@end