//
//  AfficheurTimeline.m
//  Afficheur
//
//  Created by kichi on 08/09/06.
//  Copyright 2008 Katsuhiko Ichinose. All rights reserved.
//

#import "AfficheurTimeline.h"
#import "AfficheurController.h"
#import "AfficheurPreferences.h"
#import "AfficheurPanel.h"
#import "AfficheurGrowl.h"
#import "Service.h"
#import "ServiceJaiku.h"
#import "HTTP.h"
#import "NSStringOgreKit.h"
#import <OgreKit/OgreKit.h>
#import "TextFieldCell.h"
#import <math.h>
#import "i18n.h"


@implementation AfficheurTimeline
#if defined(_D_E_B_U_G_)
APPKIT_EXTERN NSThread* TheMainThread;
#endif //defined(_D_E_B_U_G_)

#define WaitNoteNumberOfRowsChanged	0.010
#define WaitNoteNumberOfRowsChangedAndScrollToPoint	0.010

// NIB file
static NSString *NibFile	= @"Timeline";

// Title
static NSString *TIMELINE	= @"Timeline";

// Past controll
static NSString *Past		= @"past";

// Predicate
static NSString *Predicate	= @"predicate";
static NSString *Title		= @"title";
static NSString *Status		= @"status";

// Key
static NSString *KeyObject		= @"object";
static NSString *KeyDoReplace	= @"doReplace";
static NSString *KeyRow			= @"row";
static NSString *KeyDiff		= @"diff";

// Logo
NSString *LogoTwitter		= @"logo-Twitter";
NSString *LogoJaiku			= @"logo-Jaiku";
NSString *LogoTumblr		= @"logo-tumblr";
NSString *LogoWassr			= @"logo-Wassr";
//NSString *LogoNowa			= @"logo-nowa";
NSString *LogoIdentica		= @"logo-identica";
//NSString *LogoJisko			= @"logo-Jisko";
//NSString *LogoChuitter		= @"logo-chuitter";
NSString *LogoFriendFeed	= @"logo-friendfeed";
NSString *LogoFaceBook		= @"logo-facebook";
NSString *Mail				= @"mail";

// Icon
NSString *IconProtect		= @"icon_lock";
NSString *IconRetweet		= @"retweet";
NSString *IconVerified		= @"icon_verified";

// Toolbar
static NSString *ToolbarSegment	= @"Segment";
static NSString *ToolbarSearch	= @"Search";
static NSString *ShowToolbar	= @"Show Toolbar";
static NSString *HideToolbar	= @"Hide Toolbar";

- (void)loadNib
{
	//LOG(@"[%@ loadNib]", [self className]);
	[NSBundle loadNibNamed:NibFile owner:self];
}

- (id)initWithController:(AfficheurController *)controller
			 preferences:(AfficheurPreferences *)preferences;
{
	//LOG(@"[%@ init]", [self className]);
	self = [super init];
	if (self)
	{
		_controller = controller;
		_preferences = preferences;
		_removeDic = [[NSMutableDictionary alloc] init];

		_columnIcon = nil;
		_columnText = nil;
		_columnDate = nil;

		_predicateService = nil;
		_predicateUser = nil;
		_predicateSearch = nil;
		_titleService = nil;
		_titleUser = nil;
		_textStatusBar = nil;
		_selectedSegment = 0;
		_searchTag = 1;
		_searchString = nil;

		_lockTimeline = [[NSLock alloc] init];
		_inhibitTimeline = NO;
		
		_lockProfile = [[NSLock alloc] init];
		_lockedProfile = NO;
		_queueProfile = [[NSMutableArray alloc] init];
		_lockQueueProfile = [[NSLock alloc] init];

		_queueError = [[NSMutableDictionary alloc] init];
		_lockQueueError = [[NSLock alloc] init];
		
		_queueErrorCount = [[NSMutableDictionary alloc] init];
		_lockQueueErrorCount = [[NSLock alloc] init];
		
		_lockNotify = [[NSLock alloc] init];
		_lockedNotify = NO;
		_queueNotify = [[NSMutableArray alloc] init];
		_lockQueueNotify = [[NSLock alloc] init];

		_lockPast = [[NSLock alloc] init];
		_lockedPast = NO;
		_queuePast = [[NSMutableArray alloc] init];
		_lockQueuePast = [[NSLock alloc] init];

		_lockCellSize = [[NSLock alloc] init];
		_lockedCellSize = NO;
		_queueCellSize = [[NSMutableArray alloc] init];
		_lockQueueCellSize = [[NSLock alloc] init];

		_queuePhotoURL = [[NSMutableArray alloc] init];
		_lockQueuePhotoURL = [[NSLock alloc] init];

		_queueAdd = [[NSMutableArray alloc] init];

		_mainThread = nil;
		_keyView = NO;
		_selectedRow = NO;
		
		_row_min = 42;
		[self loadNib];
	}
	return self;
}

- (void)dealloc
{
	if (_predicateService)
	{
		[_predicateService release];
	}
	if (_predicateUser)
	{
		[_predicateUser release];
	}
	if (_predicateSearch)
	{
		[_predicateSearch release];
	}
	if (_searchString)
	{
		[_searchString release];
	}
	if (_titleService)
	{
		[_titleService release];
	}
	if (_titleUser)
	{
		[_titleUser release];
	}
	if (_textStatusBar)
	{
		[_textStatusBar release];
	}
	[_removeDic release];
	[_lockTimeline release];
	[_lockProfile release];
	[_queueProfile release];
	[_lockQueueProfile release];
	[_queueError release];
	[_lockQueueError release];
	[_queueErrorCount release];
	[_lockQueueErrorCount release];
	[_lockPast release];
	[_queuePast release];
	[_lockQueuePast release];
	[_queuePhotoURL release];
	[_lockQueuePhotoURL release];
	[_lockCellSize release];
	[_queueCellSize release];
	[_lockQueueCellSize release];
	[_queueAdd release];
	[super dealloc];
}

- (BOOL)isDisplay
{
	return [_panel isVisible];
}

- (void)setDisplay:(BOOL)state
{
	if (state)
	{
		[_panel orderFront:self];
	}
	else
	{
		[_panel orderOut:self];
	}
}

- (void)makeKeyAndOrderFront
{
	[_panel makeKeyAndOrderFront:self];
}

- (BOOL)canHide
{
	return [_panel canHide];
}

- (void)setCanHide:(BOOL)canHide
{
	[_panel setCanHide:canHide];
}

- (BOOL)isKeyView
{
	return _keyView;
}

- (BOOL)isEnabledReply
{
	return _selectedRow;
}

- (BOOL)isEnabledFavorite
{
	return _selectedRow;
}

- (BOOL)isEnabledOpenPermaLink
{
	return _selectedRow;
}

- (BOOL)isEnabledOpenURL
{
	return _selectedRow;
}

- (BOOL)isEnabledRetweetViaAPI
{
	if (_selectedRow)
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			if ([[obj valueForKey:KeyService] isEqualToString:Twitter] &&
				[[obj valueForKey:KeyProtected] intValue] == 0)
			{
				return YES;
			}
			else if ([[obj valueForKey:KeyService] isEqualToString:Tumblr] &&
				[[obj valueForKey:KeyProtected] intValue] == 0)
			{
				return YES;
			}
			else if ([[obj valueForKey:KeyService] isEqualToString:Identica] &&
					 [[obj valueForKey:KeyProtected] intValue] == 0)
			{
				return YES;
			}
		}
	}
	return NO;
}

- (BOOL)isEnabledDirectMessage
{
	if (_selectedRow)
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			NSString *service = [obj valueForKey:KeyService];
			if ([service isEqualToString:Twitter])
			{
				return YES;
			}
			if ([service isEqualToString:Identica])
			{
				return YES;
			}
//			if ([service isEqualToString:Jisko])
//			{
//				return YES;
//			}
		}
	}
	return NO;
}

- (BOOL)isEnabledCopy
{
	if (_keyView && _selectedRow)
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			if (obj)
			{
				return YES;
			}
		}
	}
	return NO;
}

- (BOOL)isEnabledCopyPermalink
{
	if (_keyView && _selectedRow)
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			if (obj)
			{
				return YES;
			}
		}
	}
	return NO;
}

- (NSDictionary *)fetch:(NSString *)item_id
			withService:(NSString *)service
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@) AND (%K == %@)",
							   KeyFrom, FromAPI,
							   KeyService, service,
							   KeyId, item_id];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return [fetch objectAtIndex:0];
}

- (NSArray *)arrayFetchALL:(NSString *)item_id
			   withService:(NSString *)service
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyId, item_id];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return fetch;
}

- (NSDictionary *)fetchALL:(NSString *)item_id
			   withService:(NSString *)service
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyId, item_id];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return [fetch objectAtIndex:0];
}

- (NSDictionary *)fetchRid:(NSString *)rid
			   withService:(NSString *)service
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyRId, rid];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return [fetch objectAtIndex:0];
}

- (NSArray *)fetchFromAPIWithService:(NSString *)service
								user:(NSString *)user
								text:(NSString *)text
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@) AND (%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyFrom, FromAPI,
							   KeyUser, user,
							   KeyText, text];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return fetch;
}

- (NSArray *)fetchFromAPIWithService:(NSString *)service
								user:(NSString *)user
							text_raw:(NSString *)text_raw
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@) AND (%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyFrom, FromAPI,
							   KeyUser, user,
							   KeyTextRaw, text_raw];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return fetch;
}

- (NSArray *)fetchFromXMPPWithService:(NSString *)service
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyFrom, FromXMPP];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return fetch;
}

- (NSArray *)fetchFromXMPPWithService:(NSString *)service
								 user:(NSString *)user
								 text:(NSString *)text
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@) AND (%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyFrom, FromXMPP,
							   KeyUser, user,
							   KeyText, text];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return fetch;
}

- (NSArray *)fetchFromXMPPWithService:(NSString *)service
								 user:(NSString *)user
							 text_raw:(NSString *)text_raw
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@) AND (%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyFrom, FromXMPP,
							   KeyUser, user,
							   KeyTextRaw, text_raw];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return fetch;
}

- (NSArray *)fetchErrorWithService:(NSString *)service
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyId, @"0"];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return fetch;
}

- (NSDictionary *)fetchImageURL:(NSString *)image_url
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:@"(%K == %@)",
							   KeyImageURL, image_url];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return [fetch objectAtIndex:0];
}

- (NSString *)removed:(NSString *)item_id
		  withService:(NSString *)service
{
	return [_removeDic valueForKey:[NSString stringWithFormat:@"%@:%@", service, item_id]];
}

- (NSDictionary *)begins:(NSString *)item_id
			 withService:(NSString *)service
					user:(NSString *)user
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:
							   @"(%K == %@) AND (%K == %@) AND (%K beginswith %@)",
							   KeyService, service,
							   KeyUser, user,
							   KeyId, item_id];
	NSArray *fetch = nil;
	@synchronized(_timeline)
	{
		fetch = [[_timeline content] filteredArrayUsingPredicate:predicates];
	}
	if ([fetch count] <= 0)
	{
		return nil;
	}
	return [fetch objectAtIndex:0];
}

- (void)performLockProfile:(id)object
{
	@synchronized(_lockProfile)
	{
		//LOG(@"[%@ lockProfile]", [self className]);
		_lockedProfile = YES;
		[_lockProfile lock];
	}
}

- (void)lockProfile
{
	//LOG(@"[%@ lockProfile]", [self className]);
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performLockProfile:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performLockProfile:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performUnlockProfile:(id)object
{
	@synchronized(_lockProfile)
	{
		if (_lockedProfile)
		{
			//LOG(@"[%@ unlockProfile]", [self className]);
			_lockedProfile = NO;
			[_lockProfile unlock];
		}
	}
}

- (void)unlockProfile
{
	//LOG(@"[%@ unlockProfile]", [self className]);
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performUnlockProfile:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performUnlockProfile:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performLockNotify:(id)object
{
	@synchronized(_lockNotify)
	{
		if (!_lockedNotify)
		{
			_lockedNotify = YES;
			[_lockNotify lock];
		}
	}
}

- (void)lockNotify
{
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performLockNotify:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performLockNotify:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performUnlockNotify:(id)object
{
	@synchronized(_lockNotify)
	{
		if (_lockedNotify)
		{
			_lockedNotify = NO;
			[_lockNotify unlock];
		}
	}
}

- (void)unlockNotify
{
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performUnlockNotify:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performUnlockNotify:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performLockPast:(id)object
{
	@synchronized(_lockPast)
	{
		if (!_lockedPast)
		{
			_lockedPast = YES;
			[_lockPast lock];
		}
	}
}

- (void)lockPast
{
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performLockPast:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performLockPast:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performUnlockPast:(id)object
{
	@synchronized(_lockPast)
	{
		if (_lockedPast)
		{
			_lockedPast = NO;
			[_lockPast unlock];
		}
	}
}

- (void)unlockPast
{
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performUnlockPast:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performUnlockPast:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performLockCellSize:(id)object
{
	@synchronized(_lockCellSize)
	{
		if (!_lockedCellSize)
		{
			_lockedCellSize = YES;
			[_lockCellSize lock];
		}
	}
}

- (void)lockCellSize
{
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performLockCellSize:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performLockCellSize:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performUnlockCellSize:(id)object
{
	@synchronized(_lockCellSize)
	{
		if (_lockedCellSize)
		{
			_lockedCellSize = NO;
			[_lockCellSize unlock];
		}
	}
}

- (void)unlockCellSize
{
	if ([[NSThread currentThread] isEqual:_mainThread])
	{
		[self performUnlockCellSize:nil];
	}
	else
	{
		[self performSelectorOnMainThread:@selector(performUnlockCellSize:)
							   withObject:nil
							waitUntilDone:YES];
	}
}

- (void)performGetMainThread:(id)object
{
	_mainThread = [NSThread currentThread];
}

- (void)startThread
{
	//LOG(@"[%@ startThread]\n%@", [self className], [NSThread currentThread]);
	[self performSelectorOnMainThread:@selector(performGetMainThread:)
						   withObject:nil
						waitUntilDone:YES];
	[self lockProfile];
	[NSThread detachNewThreadSelector:@selector(threadProfileImage:)
							 toTarget:self
						   withObject:nil];
	[self unlockProfile];
	[self lockNotify];
	[NSThread detachNewThreadSelector:@selector(threadNotify:)
							 toTarget:self
						   withObject:nil];
	[self unlockNotify];
	[self lockPast];
	[NSThread detachNewThreadSelector:@selector(threadPast:)
							 toTarget:self
						   withObject:nil];
	[self unlockPast];
	[self lockCellSize];
	[NSThread detachNewThreadSelector:@selector(threadCellSize:)
							 toTarget:self
						   withObject:nil];
	[self unlockCellSize];
}

- (NSImage *)profileImage:(NSString *)userProfile
{
	NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@",
							  KeyUserProfile,
							  userProfile];
	NSArray *fetch = nil;
	@synchronized(_profile)
	{
		fetch = [[_profile content] filteredArrayUsingPredicate:predicate];
	}
	if (!fetch || [fetch count] <= 0)
	{
		return nil;
	}
	return [[fetch objectAtIndex:0] valueForKey:KeyProfileImage];
}

- (NSDictionary *)fetchPast:(NSString *)item_id
				withService:(NSString *)service
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:
							   @"(%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyId, item_id];
	@synchronized(_past)
	{
		NSArray *fetch = [[_past content] filteredArrayUsingPredicate:predicates];
		if (fetch && [fetch count] > 0)
		{
			return [fetch objectAtIndex:0];
		}
	}
	return nil;
}

- (NSDictionary *)fetchPast:(NSString *)item_id
				withService:(NSString *)service
					   kind:(NSString *)kind
{
	NSPredicate *predicates = [NSPredicate predicateWithFormat:
							   @"(%K == %@) AND (%K == %@) AND (%K == %@)",
							   KeyService, service,
							   KeyId, item_id,
							   KeyKind, kind];
	@synchronized(_past)
	{
		NSArray *fetch = [[_past content] filteredArrayUsingPredicate:predicates];
		if (fetch && [fetch count] > 0)
		{
			return [fetch objectAtIndex:0];
		}
	}
	return nil;
}

- (void)performNotifyIcon:(NSArray *)object
{
	@try
	{
		NSImage *icon = [object objectAtIndex:0];
		NSImage *profileImage = [object objectAtIndex:1];
		NSImage *serviceIcon = [object objectAtIndex:2];
#if defined(_D_E_B_U_G_)
		if ([[NSThread currentThread] isEqual:TheMainThread])
		{
			LOG(@"[%@ setImage] MAIN THREAD !!!", [self className]);
		}
#endif //defined(_D_E_B_U_G_)
		@synchronized([NSApplication sharedApplication])
		{
			[icon lockFocus];
			[[NSGraphicsContext currentContext] setShouldAntialias:YES];
			if (![profileImage isKindOfClass:[NSNull class]])
			{
				[profileImage compositeToPoint:NSMakePoint(4, 4)
									 operation:NSCompositeSourceOver];
			}
			if (![serviceIcon isKindOfClass:[NSNull class]])
			{
				[serviceIcon compositeToPoint:NSMakePoint(0, 0)
									operation:NSCompositeSourceOver];
			}
			[icon unlockFocus];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performNotifyIcon] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)notify:(id)object
{
	@try
	{
		NSImage *icon = [[[NSImage alloc] initWithSize:NSMakeSize(48+4, 48+4)] autorelease];
		if (![[object valueForKey:KeyUser] isEqualToString:@""])
		{
			NSImage *profileImage = [object valueForKey:KeyProfileImage];
			NSImage *serviceIcon = [object valueForKey:KeyServiceIcon];
			if (!profileImage)
			{
				profileImage = (NSImage *)[NSNull null];
			}
			if (!serviceIcon)
			{
				serviceIcon = (NSImage *)[NSNull null];
			}
			[self performNotifyIcon:[NSArray arrayWithObjects:
									 icon, profileImage, serviceIcon, nil]];
		}
		else
		{
			icon = [object valueForKey:KeyProfileImage];
		}
		NSData *data = nil;
		if (icon)
		{
			data = [NSData dataWithData:[icon TIFFRepresentation]];
		}
		[_controller notifyWithTitle:[object valueForKey:KeyNotifyTitle]
						 description:[object valueForKey:KeyTextNotify]
					notificationName:[object valueForKey:KeyNotify]
							iconData:data
						clickContext:[NSString stringWithFormat:@"%@ %@",
									  [object valueForKey:KeyService],
									  [object valueForKey:KeyId]]];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ notify] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (BOOL)notify1:(id)object
{
	BOOL notified = NO;
	@try
	{
		id item = [self fetchALL:[object valueForKey:KeyId]
					 withService:[object valueForKey:KeyService]];
		if (item &&
			![[item valueForKey:KeyFirst] boolValue] &&
			![self fetchPast:[item valueForKey:KeyId] withService:[item valueForKey:KeyService]])
		{
			NSString *userProfile = [item valueForKey:KeyUserProfile];
			id profileImage = nil;
			if ([userProfile isEqualToString:@""])
			{
				profileImage = @"";
			}
			else
			{
				profileImage = [self profileImage:userProfile];
				if (!profileImage)
				{
					[_lockQueueError lock];
					id error = [_queueError valueForKey:userProfile];
					[_lockQueueError unlock];
					if (error && [error intValue] == 0)
					{
						LOG(@"[%@ notify1] error\n%@: %@\n%@", [self className], [item valueForKey:KeyUserProfile], [item valueForKey:KeyText], userProfile);
						profileImage = @"";
					}
				}
			}
			if (profileImage)
			{
				//LOG(@"[%@ notify1]\n%@: %@", [self className], [item valueForKey:KeyUserProfile], [item valueForKey:KeyText]);
				[self notify:item];
				notified = YES;
			}
		}
		else
		{
			notified = YES;
			if ((item == nil) || ![[item valueForKey:KeyFirst] boolValue])
			{
				LOG(@"[%@ notify1] not", [self className]);
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ notify1] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return notified;
}

- (void)performSetSelectedObjects:(NSArray *)objects
{
	[_timeline setSelectedObjects:objects];
}

- (void)performScrollRowToVisible:(NSNumber *)number
{
	[_view scrollRowToVisible:[number intValue]];
}

- (int) rowOfItem:(NSString *)item_id
	  withService:(NSString *)service
{
	//LOG(@"[%@ rowOfItem]\n%@: %@", [self className], service, item_id);
	@try
	{
		NSEnumerator *enumerator = [[_timeline arrangedObjects] objectEnumerator];
		id obj;
		int i = 0;
		while ((obj = [enumerator nextObject]))
		{
			if ([service isEqualToString:[obj valueForKey:KeyService]] &&
				[item_id isEqualToString:[obj valueForKey:KeyId]])
			{
				return i;
			}
			i++;
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ rowOfItem] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return -1;
}

- (void)performSetSelectedItem:(NSString *)item_id
				   withService:(NSString *)service
{
	//LOG(@"[%@ performSetSelectedItem]\n%@: %@", [self className], service, item_id);
	@try
	{
		int i = [self rowOfItem:item_id withService:service];
		if (i >= 0)
		{
			id obj = [[_timeline arrangedObjects] objectAtIndex:i];
			//LOG(@"[%@ performSetSelectedItem] setSelectedObjects", [self className]);
			if ([[NSThread currentThread] isEqual:_mainThread])
			{
				[_timeline setSelectedObjects:[NSArray arrayWithObjects:obj, nil]];
			}
			else
			{
				[self performSelectorOnMainThread:@selector(performSetSelectedObjects:)
									   withObject:[NSArray arrayWithObjects:obj, nil]
									waitUntilDone:YES];
			}
			//LOG(@"[%@ performSetSelectedItem] scrollRowToVisible", [self className]);
			if ([[NSThread currentThread] isEqual:_mainThread])
			{
				[_view scrollRowToVisible:i];
			}
			else
			{
				[self performSelectorOnMainThread:@selector(performScrollRowToVisible:)
									   withObject:[NSNumber numberWithInt:i]
									waitUntilDone:YES];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performSetSelectedItem] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)setSelectedObject:(id)object
{
	//LOG(@"[%@ setSelectedObject] %@", [self className], [object className]);
	@try
	{
		[self performSetSelectedItem:[object valueForKey:KeyId]
						 withService:[object valueForKey:KeyService]];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ setSelectedObject] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (NSRect) rectOfItem:(NSString *)item_id
		  withService:(NSString *)service
{
	//LOG(@"[%@ rectOfItem]\n%@: %@", [self className], service, item_id);
	@try
	{
		int i = [self rowOfItem:item_id withService:service];
		if (i >= 0)
		{
			return [_view rectOfRow:i];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ rectOfItem] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return NSMakeRect(0, 0, 0, 0);
}

- (NSDictionary *)dictionaryOfItem
{
	NSClipView *superview = (NSClipView *)[_view superview];
	NSRect rect = [superview documentVisibleRect];
	int row = [_view rowAtPoint:NSMakePoint(rect.origin.x, rect.origin.y)];
	if (row >= 0)
	{
		NSRect rowRect = [_view rectOfRow:row];
		id rowItem = [[_timeline arrangedObjects] objectAtIndex:row];
		NSString *rowId = [rowItem valueForKey:KeyId];
		NSString *rowService = [rowItem valueForKey:KeyService];
		float diff = rect.origin.y - rowRect.origin.y;
		LOG(@"[%@ dictionaryOfItem]\n"\
			 @"rowId       %@\n"\
			 @"rowService  %@\n"\
			 @"rowAtPoint  %d\n",
//			 @"diff        %f\n"\
//			 @"rectOfItem  %f,%f - %f,%f",
			 [self className],
			 rowId,
			 rowService,
			 row);
//			 diff,
//			 rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
		return [NSDictionary dictionaryWithObjectsAndKeys:
				[NSNumber numberWithInt:row], KeyRow,
				rowId, KeyId,
				rowService, KeyService,
				[NSNumber numberWithFloat:diff], KeyDiff,
				nil];
	}
	return nil;
}

- (void)performRearrangeObjects:(NSNumber *)keepSelectedItem
{
	LOG(@"[%@ performRearrangeObjects]", [self className]);
	//@synchronized(_timeline)
	{
		@try
		{
			NSClipView *superview = (NSClipView *)[_view superview];
			NSRect rect = [superview documentVisibleRect];
			int row = [_view rowAtPoint:NSMakePoint(rect.origin.x, rect.origin.y)];
			NSRect rowRect;
			id rowItem;
			NSString *rowId;
			NSString *rowService;
			float diff;
			if (row >= 0)
			{
				rowRect = [_view rectOfRow:row];
				rowItem = [[_timeline arrangedObjects] objectAtIndex:row];
				rowId = [rowItem valueForKey:KeyId];
				rowService = [rowItem valueForKey:KeyService];
				diff = rect.origin.y - rowRect.origin.y;
			}
			NSArray *selected = [_timeline selectedObjects];
			[_timeline rearrangeObjects];
			if ([keepSelectedItem boolValue] && ([selected count] > 0))
			{
				NSDictionary *item = [selected objectAtIndex:0];
				NSString *service = [item valueForKey:KeyService];
				NSString *removedID = [self removed:[item valueForKey:KeyId]
										withService:service];
				if (removedID)
				{
					id fetch = [self fetch:removedID
							   withService:service];
					if (fetch)
					{
						item = fetch;
					}
				}
				[self setSelectedObject:item];
			}
			int selectedRow = [_view selectedRow];
			if ([_preferences generalKeepCursorPosition] && (selectedRow >= 0))
			{
				NSRect rect = [_view rectOfRow:selectedRow];
				[superview scrollToPoint:NSMakePoint(rect.origin.x, rect.origin.y)];
			}
			else if (row == 0)
			{
				int i = [self rowOfItem:rowId withService:rowService];
				float y = 0;
				if (i == row)
				{
					y = diff;
				}
				[superview scrollToPoint:NSMakePoint(0, y)];
			}
			else if (row > 0)
			{
				NSRect itemRect = [self rectOfItem:rowId withService:rowService];
				LOG(@"[%@ performRearrangeObjects]\n"\
					 @"rowId       %@\n"\
					 @"rowService  %@\n"\
					 @"rowAtPoint  %d\n",
//					 @"diff        %f\n"\
//					 @"rectOfItem  %f,%f - %f,%f",
					 [self className],
					 rowId,
					 rowService,
					 row);
//					 diff,
//					 itemRect.origin.x, itemRect.origin.y, itemRect.size.width, itemRect.size.height);
				[superview scrollToPoint:NSMakePoint(itemRect.origin.x, itemRect.origin.y + diff)];
			}
			else
			{
				[superview scrollToPoint:NSMakePoint(0, 0)];
			}
			[_scroll reflectScrolledClipView:superview];
			if (([_view gridStyleMask] & NSTableViewSolidHorizontalGridLineMask) == 0)
			{
				[_view setGridStyleMask:NSTableViewSolidHorizontalGridLineMask];
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ performRearrangeObjects] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
	}
}

- (void)performNoteNumberOfRowsChanged:(id)object
{
	LOG(@"[%@ performNoteNumberOfRowsChanged]", [self className]);
	//@synchronized(_timeline)
	{
		@try
		{
			[_view noteNumberOfRowsChanged];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ performNoteNumberOfRowsChanged] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
	}
	LOG(@"[%@ performNoteNumberOfRowsChanged] done", [self className]);
}

- (BOOL)isInBounds:(NSPoint)point
{
	NSRect bounds = [_scroll bounds];
	float bx = bounds.origin.x;
	float bw = bx + bounds.size.width;
	float by = bounds.origin.y;
	float bh = by + bounds.size.height;
	NSPoint pt = [_scroll convertPoint:point fromView:nil];
	float x = pt.x;
	float y = pt.y;
	LOG(@"[%@ isInBounds]\n%f, %f\n%f, %f, %f, %f", [self className], x, y, bx, by, bw, bh);
	return ((x >= bx) && (x < bw) && (y >= by) && (y < bh));
}

- (void)performScrollToPoint:(id)obj
{
	LOG(@"[%@ performScrollToPoint]", [self className]);
	//@synchronized(_timeline)
	{
		@try
		{
			NSClipView *superview = (NSClipView *)[_view superview];
			if (obj)
			{
				BOOL inBounds = [self isInBounds:[[_view window] convertScreenToBase:[NSEvent mouseLocation]]] && _keyView;
				int row = [[obj valueForKey:KeyRow] intValue];
				NSString *rowId = [obj valueForKey:KeyId];
				NSString *rowService = [obj valueForKey:KeyService];
				float diff = [[obj valueForKey:KeyDiff] floatValue];
				int selectedRow = [_view selectedRow];
				if ([_preferences generalKeepCursorPosition] && (selectedRow >= 0))
				{
					NSRect rect = [_view rectOfRow:selectedRow];
					[superview scrollToPoint:NSMakePoint(rect.origin.x, rect.origin.y)];
					LOG(@"[%@ performScrollToPoint] selectedRow >= 0", [self className]);
				}
				else if ((row == 0) &&
						 (!inBounds || ![_preferences generalNotKeepTimeline]))
				{
					int i = [self rowOfItem:rowId withService:rowService];
					float y = 0;
					if (i == row)
					{
						y = diff;
					}
					[superview scrollToPoint:NSMakePoint(0, y)];
					LOG(@"[%@ performScrollToPoint] row == 0", [self className]);
				}
				else if ((row > 0) ||
						 ((row == 0) && inBounds && [_preferences generalNotKeepTimeline]))
				{
					NSRect itemRect = [self rectOfItem:rowId withService:rowService];
					LOG(@"[%@ performScrollToPoint]\n"\
						 @"rowId       %@\n"\
						 @"rowService  %@\n"\
						 @"rowAtPoint  %d\n"\
						 @"diff        %f\n"\
						 @"rectOfItem  %f,%f - %f,%f",
						 [self className],
						 rowId,
						 rowService,
						 row,
						 diff,
						 itemRect.origin.x, itemRect.origin.y, itemRect.size.width, itemRect.size.height);
					[superview scrollToPoint:NSMakePoint(itemRect.origin.x, itemRect.origin.y + diff)];
					LOG(@"[%@ performScrollToPoint] row > 0", [self className]);
				}
				else
				{
					[superview scrollToPoint:NSMakePoint(0, 0)];
				}
			}
			else
			{
				[superview scrollToPoint:NSMakePoint(0, 0)];
			}
			//	[superview scrollToPoint:rect.origin];
			[_scroll reflectScrolledClipView:superview];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ performScrollToPoint] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
	}
	LOG(@"[%@ performScrollToPoint] done", [self className]);
}

- (void)performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint:(id)obj
{
	LOG(@"[%@ performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint]", [self className]);
	@try
	{
		[self performRearrangeObjects:[NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]];
		[self performNoteNumberOfRowsChanged:nil];
		[self performScrollToPoint:obj];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	LOG(@"[%@ performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint] done", [self className]);
}

- (void)addRemoveID:(NSString *)removeID
		withService:(NSString *)service
			  newID:(NSString *)newID
{
	LOG(@"[%@ addRemoveID] %@:%@ = %@", [self className], service, removeID, newID);
	if (![removeID isEqualToString:newID])
	{
		@try
		{
			@synchronized(_removeDic)
			{
				[_removeDic setObject:newID
							   forKey:[NSString stringWithFormat:
									   @"%@:%@",
									   service,
									   removeID]];
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ removeID] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
	}
}

- (void)performRemoveObject:(NSDictionary *)object
{
	//LOG(@"[%@ performRemoveObject] %@", [self className], object);
	@try
	{
		@synchronized(_timeline)
		{
			int i = [[object valueForKey:KeyIndex] intValue];
			[[_timeline content] removeObjectAtIndex:i];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performRemoveObject] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[object release];
	}
}

- (void)cellSize:(NSMutableDictionary *)item
{
	@try
	{
		@synchronized(_timeline)
		{
			float width = [_columnText width];
			NSCell *cell = [_columnText dataCell];
			[cell setAttributedStringValue:[item valueForKey:KeyTextDisplay]];
			float height = [cell cellSizeForBounds:NSMakeRect(0, 0, width, 2000)].height;
			[item setValue:[NSNumber numberWithFloat:width] forKey:KeyWidth];
			[item setValue:[NSNumber numberWithFloat:height] forKey:KeyHeight];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ cellSize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)performInsertObject:(NSDictionary *)object
{
	//LOG(@"[%@ performInsertObject] %@", [self className], object);
	@try
	{
		@synchronized(_timeline)
		{
			int i = [[object valueForKey:KeyIndex] intValue];
			NSMutableDictionary *obj = [NSMutableDictionary dictionaryWithDictionary:
										[object valueForKey:KeyObject]];
			[self cellSize:obj];
			[[_timeline content] insertObject:obj atIndex:i];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performInsertObject] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[object release];
	}
}

- (void)performReplaceObject:(NSDictionary *)object
{
	//LOG(@"[%@ performReplaceObject] %@", [self className], object);
	@try
	{
		@synchronized(_timeline)
		{
			int i = [[object valueForKey:KeyIndex] intValue];
			NSMutableDictionary *obj = [NSMutableDictionary dictionaryWithDictionary:
										[object valueForKey:KeyObject]];
			[self cellSize:obj];
			[[_timeline content] replaceObjectAtIndex:i withObject:obj];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performReplaceObject] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[object release];
	}
}

- (id)addTimelineObject:(id)object
			  withFirst:(BOOL)first
{
	id reply = nil;
	@try
	{
		int remove = 0;
		//@synchronized(_timeline)
		{
			if ([[object valueForKey:KeyId] isEqualToString:IdZero])
			{
				@try
				{
					NSString *service = [object valueForKey:KeyService];
					NSArray *error = [self fetchErrorWithService:service];
					if (error)
					{
						int count = [error count];
						int i = 0;
						while (i < [[_timeline content] count])
						{
							id obj = [[_timeline content] objectAtIndex:i];
							if ([[obj valueForKey:KeyId] isEqualToString:@"0"] &&
								[[obj valueForKey:KeyService] isEqualToString:service])
							{
								[self performSelectorOnMainThread:@selector(performRemoveObject:)
													   withObject:[[NSDictionary dictionaryWithObjectsAndKeys:
																	[NSNumber numberWithInt:i], KeyIndex,
																	nil] retain]
													waitUntilDone:YES];
								count--;
								if (count <= 0)
								{
									break;
								}
							}
							else
							{
								i++;
							}
						}
					}
				}
				@catch (NSException *exception)
				{
					EXPLOG(@"[%@ addObject] IDZero EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
				}
			}
			BOOL replace = NO;
			NSString *serviceName = [object valueForKey:KeyService];
			if ([[object valueForKey:KeyFrom] isEqualToString:FromAPI])
			{
				NSArray *xmpp = [self fetchFromXMPPWithService:serviceName];
				if (xmpp)
				{
					@try
					{
						Service *service = [_controller service:serviceName];
						NSString *xmppId = [object valueForKey:KeyId];
						NSString *user = [object valueForKey:KeyUser];
						NSString *text_raw = [object valueForKey:KeyTextRaw];
						int count = [xmpp count];
						int i = 0;
						while (i < [[_timeline content] count])
						{
							id obj = [[_timeline content] objectAtIndex:i];
							if ([[obj valueForKey:KeyFrom] isEqualToString:FromXMPP] &&
								[[obj valueForKey:KeyService] isEqualToString:serviceName])
							{
								//LOG(@"[%@ addObject]"\
								//	@"\n\%@: '%@\'",
								//	[self className], [obj valueForKey:KeyUser], [obj valueForKey:KeyTextRaw]);
								if([[obj valueForKey:KeyId] isEqualToString:xmppId])
								{
									LOG(@"[%@ addObject]"\
										@"\nremove id: %@"\
										@"\n%@"\
										@"\n%@"\
										@"\n%@"\
										@"\n%@",
										[self className], xmppId, text_raw, [obj valueForKey:KeyTextRaw], [object valueForKey:KeyTextEx], [obj valueForKey:KeyTextEx]);
									@synchronized(_past)
									{
										[object setValue:[obj valueForKey:KeyProfileImage] forKey:KeyProfileImage];
										[object setValue:[obj valueForKey:KeyPhotoURL] forKey:KeyPhotoURL];
										[object setValue:[obj valueForKey:KeyImageURL] forKey:KeyImageURL];
									//	[object setValue:[obj valueForKey:KeyImage] forKey:KeyImage];
									//	if ([obj valueForKey:KeyImage])
									//	{
									//		[self formatText:object
									//			 withService:service
									//			 appearances:[_preferences appearances]];
									//	}
										[_past removeObject:[NSDictionary dictionaryWithObjectsAndKeys:
															 [obj valueForKey:KeyId], KeyId,
															 [obj valueForKey:KeyKind], KeyKind,
															 [obj valueForKey:KeyService], KeyService,
															 nil]];
									}
									[self addRemoveID:[obj valueForKey:KeyId]
										  withService:serviceName
												newID:[object valueForKey:KeyId]];
									remove = i;
									[self performSelectorOnMainThread:@selector(performRemoveObject:)
														   withObject:[[NSDictionary dictionaryWithObjectsAndKeys:
																		[NSNumber numberWithInt:i], KeyIndex,
																		nil] retain]
														waitUntilDone:YES];
								}
								else if([[obj valueForKey:KeyUser] isEqualToString:user] &&
										[[obj valueForKey:KeyTextRaw] isEqualToString:text_raw])
								{
									LOG(@"[%@ addObject]"\
										@"\nremove: %@"\
										@"\n%@"\
										@"\n%@",
										[self className], [obj valueForKey:KeyId], text_raw, [obj valueForKey:KeyTextRaw]);
									[object setValue:[obj valueForKey:KeyProfileImage] forKey:KeyProfileImage];
									[object setValue:[obj valueForKey:KeyPhotoURL] forKey:KeyPhotoURL];
									[object setValue:[obj valueForKey:KeyImageURL] forKey:KeyImageURL];
								//	[object setValue:[obj valueForKey:KeyImage] forKey:KeyImage];
								//	if ([obj valueForKey:KeyImage])
								//	{
								//		[self formatText:object
								//			 withService:service
								//			 appearances:[_preferences appearances]];
								//	}
									@synchronized(_past)
									{
										[_past removeObject:[NSDictionary dictionaryWithObjectsAndKeys:
															 [obj valueForKey:KeyId], KeyId,
															 [obj valueForKey:KeyKind], KeyKind,
															 [obj valueForKey:KeyService], KeyService,
															 nil]];
									}
									[self addRemoveID:[obj valueForKey:KeyId]
										  withService:serviceName
												newID:[object valueForKey:KeyId]];
									if ([service isDelayedReply:[obj valueForKey:KeyId]])
									{
										reply = [obj copy];
									}
									remove = i;
									[self performSelectorOnMainThread:@selector(performRemoveObject:)
														   withObject:[[NSDictionary dictionaryWithObjectsAndKeys:
																		[NSNumber numberWithInt:i], KeyIndex,
																		nil] retain]
														waitUntilDone:YES];
								}
								else
								{
									i++;
								}
								count--;
								if (count <= 0)
								{
									break;
								}
							}
							else
							{
								i++;
							}
						}
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@ addObject] XMPP EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
					}
				}
			}
			else if (([[object valueForKey:KeyService] isEqualToString:Identica]) &&
					 ([[object valueForKey:KeyFrom] isEqualToString:FromXMPP]))
			{
				NSDictionary *fetch = [self fetch:[object valueForKey:KeyId]
									  withService:[object valueForKey:KeyService]];
				if (fetch)
				{
					@try
					{
						NSString *item_id = [object valueForKey:KeyId];
						NSString *text_raw = [object valueForKey:KeyTextRaw];
						int count = [[_timeline content] count];
						int i = 0;
						while (i < count)
						{
							id obj = [[_timeline content] objectAtIndex:i];
							if (([[obj valueForKey:KeyService] isEqualToString:serviceName]) &&
								([[obj valueForKey:KeyId] isEqualToString:item_id]) &&
								(![[obj valueForKey:KeyTextRaw] isEqualToString:text_raw]))
							{
								LOG(@"[%@ addObject]"\
									@"\nreplace: %@"\
									@"\n%@"\
									@"\n%@",
									[self className], [obj valueForKey:KeyId], text_raw, [obj valueForKey:KeyTextRaw]);
								[obj setValue:[object valueForKey:KeyText] forKey:KeyText];
								[obj setValue:[object valueForKey:KeyText] forKey:KeyTextEx];
								[obj setValue:[object valueForKey:KeyTextRaw] forKey:KeyTextRaw];
								//[obj setValue:[object valueForKey:KeyTextDisplay] forKey:KeyTextDisplay];
								//[obj setValue:[object valueForKey:KeyTextDisplaySelected] forKey:KeyTextDisplaySelected];
								[obj setValue:[object valueForKey:KeyTextNotify] forKey:KeyTextNotify];
								[obj setValue:[object valueForKey:KeyNotifyTitle] forKey:KeyNotifyTitle];
								//[obj setValue:[object valueForKey:KeySort] forKey:KeySort];
								[self formatText:obj
									 withService:[_controller service:[obj valueForKey:KeyService]]
									 appearances:[_preferences appearances]];
								[self performSelectorOnMainThread:@selector(performReplaceObject:)
													   withObject:[[NSDictionary dictionaryWithObjectsAndKeys:
																	[NSNumber numberWithInt:i], KeyIndex,
																	obj, KeyObject,
																	nil] retain]
													waitUntilDone:YES];
								replace = YES;
								break;
							}
							i++;
						}
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@ addObject] REPLACE EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
					}
				}
			}
			if (!replace)
			{
				BOOL exist = NO;
				NSString *item_id = [object valueForKey:KeyId];
				NSDate *item_date = [object valueForKey:KeyCreatedAt];
				int count = [[_timeline content] count];
				int i = 0;
				while (i < count)
				{
					id obj = [[_timeline content] objectAtIndex:i];
					if ([[obj valueForKey:KeyService] isEqualToString:serviceName] &&
						[[obj valueForKey:KeyId] isEqualToString:item_id])
					{
						LOG(@"[%@ addObject] EXIST!", [self className]);
						exist = YES;
						break;
					}
					else if ([item_date compare:[obj valueForKey:KeyCreatedAt]] != NSOrderedAscending)
					{
						break;
					}
					i++;
				}
				if (exist)
				{
					id obj = [[_timeline content] objectAtIndex:i];
					if (![[obj valueForKey:KeyCategory] isEqualToString:[object valueForKey:KeyCategory]])
					{
						[obj setValue:[object valueForKey:KeyCategory] forKey:KeyCategory];
						//[obj setValue:[object valueForKey:KeySort] forKey:KeySort];
						[self formatText:obj
							 withService:[_controller service:[obj valueForKey:KeyService]]
							 appearances:[_preferences appearances]];
						[self performSelectorOnMainThread:@selector(performReplaceObject:)
											   withObject:[[NSDictionary dictionaryWithObjectsAndKeys:
															[NSNumber numberWithInt:i], KeyIndex,
															obj, KeyObject,
															nil] retain]
											waitUntilDone:YES];
					}
				}
				else
				{
					if (!first && [_preferences generalStackTimeline])
					{
						i = remove;
					}
					if ([[_timeline content] count] > i)
					{
						id obj = [[_timeline content] objectAtIndex:i];
						double sort = [[obj valueForKey:KeySort] doubleValue];
						if (i > 0)
						{
							id obj2 = [[_timeline content] objectAtIndex:i - 1];
							double sort2 = [[obj2 valueForKey:KeySort] doubleValue];
							sort += (sort2 - sort) / 2;
							LOG(@"[%@ addObject]  %d / %0.12lf", [self className], i, sort);
						}
						else
						{
							sort += 1;
							LOG(@"[%@ addObject] +%d / %0.12lf", [self className], i, sort);
						}
						[object setValue:[NSNumber numberWithDouble:sort] forKey:KeySort];
					}
					else if ([[_timeline content] count] > 0)
					{
						id obj = [[_timeline content] objectAtIndex:i - 1];
						double sort = [[obj valueForKey:KeySort] doubleValue];
						sort -= 1;
						[object setValue:[NSNumber numberWithDouble:sort] forKey:KeySort];
						LOG(@"[%@ addObject] -%d / %0.12lf", [self className], i, sort);
					}
					else
					{
						[object setValue:[NSNumber numberWithDouble:0.0] forKey:KeySort];
						LOG(@"[%@ addObject]  %d", [self className], i);
					}
					[self performSelectorOnMainThread:@selector(performInsertObject:)
										   withObject:[[NSDictionary dictionaryWithObjectsAndKeys:
														[NSNumber numberWithInt:i], KeyIndex,
														object, KeyObject,
														nil] retain]
										waitUntilDone:YES];
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ addObject] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return reply;
}

- (void)removeProfileImage:(NSString *)userProfile
{
	@synchronized(_profile)
	{
		int i = 0;
		while (i < [[_profile arrangedObjects] count]) {
			id obj = [[_profile arrangedObjects] objectAtIndex:i];
			if ([[obj valueForKey:KeyUserProfile] isEqualToString:userProfile])
			{
#if 0 //defined(_D_E_B_U_G_)
				NSImage *profileImage = [obj valueForKey:KeyProfileImage];
				int cnt = [obj retainCount];
				int cnti = [profileImage retainCount];
#endif //defined(_D_E_B_U_G_)
				[_profile removeObjectAtArrangedObjectIndex:i];
#if 0 //defined(_D_E_B_U_G_)
				LOG2(@"[%@ updateProfileImage] %d / %d", [self className], cnt, [obj retainCount]);
				LOG2(@"[%@ updateProfileImage] %d / %d", [self className], cnti, [profileImage retainCount]);
#endif //defined(_D_E_B_U_G_)
#if 0
				while ([obj retainCount] > 1)
				{
					[obj release];
				}
				while ([profileImage retainCount] > 2)
				{
					[profileImage release];
				}
#endif
			}
			else
			{
				i++;
			}
		}
	}
}

- (void)updateProfileImage:(NSString *)userProfile
				 withImage:(NSImage *)profileImage
{
	//LOG(@"[%@ updateProfileImage]", [self className]);
	[_lockTimeline lock];
	BOOL inhibitTimeline = _inhibitTimeline;
	@try
	{
		_inhibitTimeline = YES;
		BOOL isPhoto = NO;
		NSString *key = KeyUserProfile;
		NSString *image = KeyProfileImage;
		if ([userProfile lengthOfRegularExpression:[NSString stringWithFormat:@"^%@", ProfilePhoto]] > 0)
		{
			//LOG(@"[%@ updateProfileImage] photo\n%@\n%@", [self className], userProfile, profileImage);
			key = KeyImageURL;
			image = KeyImage;
		}
		else if ([userProfile lengthOfRegularExpression:[NSString stringWithFormat:@"^%@", ProfileIcon]] > 0)
		{
			//LOG(@"[%@ updateProfileImage] photo\n%@\n%@", [self className], userProfile, profileImage);
			key = KeyIcon;
			image = KeyIconImage;
		}
		else if ([userProfile lengthOfRegularExpression:[NSString stringWithFormat:@"^%@", ProfileSource]] > 0)
		{
			//LOG(@"[%@ updateProfileImage] photo\n%@\n%@", [self className], userProfile, profileImage);
			key = KeySourceIcon;
			image = KeySourceIconImage;
			profileImage = [Service resizeImage:profileImage withSize:NSMakeSize(16, 16)];
		}
		NSDictionary *appearances = [_preferences appearances];
		int count = [[_timeline content] count];
		int i = 0;
		while (i < count)
		{
			id obj = [[_timeline content] objectAtIndex:i];
			if (([[obj valueForKey:key] isEqualToString:userProfile]) &&
				![obj valueForKey:image])
			{
				obj = [NSMutableDictionary dictionaryWithDictionary:obj];
				[obj setValue:profileImage forKey:image];
				if ([key isEqualToString:KeyImageURL] ||
					[key isEqualToString:KeyIcon] ||
					[key isEqualToString:KeySourceIcon])
				{
					//LOG(@"[%@ updateProfileImage] photo\n%@", [self className], userProfile);
					isPhoto = YES;
					[self formatText:obj
						 withService:[_controller service:[obj valueForKey:KeyService]]
						 appearances:appearances];
					[obj setValue:[NSNumber numberWithInt:-1] forKey:KeyWidth];
					[obj setValue:[NSNumber numberWithInt:-1] forKey:KeyHeight];
					if ([key isEqualToString:KeyImageURL])
					{
						[obj setValue:[NSNull null] forKey:image];
					}
					else
					{
						LOG(@"[%@ updateProfileImage] icon: %f, %f", [self className], [profileImage size].width, [profileImage size].height);
					}
				}
				[self performSelectorOnMainThread:@selector(performReplaceObject:)
									   withObject:[[NSDictionary dictionaryWithObjectsAndKeys:
												   [NSNumber numberWithInt:i], KeyIndex,
												   obj, KeyObject,
												   nil] retain]
									waitUntilDone:YES];
			}
			i++;
		}
		if ([key isEqualToString:KeyImageURL])
		{
#if defined(ALLOC_MAIN_THREAD)
			[self performSelectorOnMainThread:@selector(removeProfileImage:)
								   withObject:userProfile
								waitUntilDone:YES];
#else //defined(ALLOC_MAIN_THREAD)
			[self removeProfileImage:userProfile];
#endif //defined(ALLOC_MAIN_THREAD)
		}
#if 1
		NSDictionary *rowItem = [self dictionaryOfItem];
		[self performSelectorOnMainThread:@selector(performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint:)
							   withObject:rowItem
							waitUntilDone:YES];
		[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChangedAndScrollToPoint]];
#else
		NSDictionary *rowItem = [self dictionaryOfItem];
		[self performSelectorOnMainThread:@selector(performRearrangeObjects:)
							   withObject:[NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]
							waitUntilDone:YES];
		if (isPhoto)
		{
			[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChanged]];
			[self performSelectorOnMainThread:@selector(performNoteNumberOfRowsChanged:)
								   withObject:nil
								waitUntilDone:YES];
		}
		[self performSelectorOnMainThread:@selector(performScrollToPoint:)
							   withObject:rowItem
							waitUntilDone:YES];
#endif
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ updateProfiles] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		if (!inhibitTimeline)
		{
			_inhibitTimeline = NO;
		}
		[_lockTimeline unlock];
	}
}

#if defined(ALLOC_MAIN_THREAD)
- (void)createImageWithData:(NSData *)data
{
	@try
	{
		_image = nil;
		_image = [[NSImage alloc] initWithData:data];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ createImageWithData] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		if (_image)
		{
			[_image release];
		}
		_image = nil;
	}
}

- (void)createImageWithSize:(NSNumber *)size
{
	@try
	{
		_image = nil;
		_image = [[NSImage alloc] initWithSize:NSMakeSize([size intValue], [size intValue])];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ createImageWithSize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		if (_image)
		{
			[_image release];
		}
		_image = nil;
	}
}
#endif //defined(ALLOC_MAIN_THREAD)

- (NSImage *)getProfileImage:(NSString *)url
{
	LOG(@"[%@ getProfileImage] %@", [self className], url);
	NSImage *profileImage = nil;
	if (![url isEqualToString:@""])
	{
		HTTP *http = [[HTTP alloc] init];
		if (http)
		{
			@try
			{
				id result = nil;
				NSString *real_url = [NSString stringWithFormat:@"%@", url];
				NSString *host = nil;
				if ([url lengthOfRegularExpression:@"^http"] == 0)
				{
					NSImage *image = [NSImage imageNamed:url];
					if (image)
					{
						result = [Service resizeImage:image withSize:NSMakeSize(16, 16)];
					}
				}
				else if ([url lengthOfRegularExpression:@"wassr\\.jp/.*photo$"] > 0)
				{
					result = [[_controller service:Wassr] getWithAuth:url];
				}
				else
				{
					BOOL redirect = YES;
					[http setTimeoutInterval:3];
					[http setRedirect:NO];
					while (redirect)
					{
#if 0 //defined(_D_E_B_U_G_)
						if ([url lengthOfRegularExpression:@"tumblr\\.com/photo/"] > 0)
						{
							LOG2(@"[%@ getProfileImage]\n%@", [self className], real_url);
						}
#endif //defined(_D_E_B_U_G_)
						host = [real_url replaceWithExpression:@"^.*?(:?http|https)://(.*?)/.*$"
													   replace:@"\\2"];
						NSDictionary *head = [http head:[HTTP urlEncode:real_url
															  unescaped:@"?&=%"
																escaped:@"+"]
												 header:[NSDictionary dictionaryWithObjectsAndKeys:
														 host, @"Host",
														 nil]
												   body:@""];
						if ([head isKindOfClass:[NSDictionary class]] && [http statusCode] >= 300)
						{
							if ([http statusCode] >= 400)
							{
								redirect = NO;
							}
							else if ([head valueForKey:@"Location"])
							{
								NSString *location = [head valueForKey:@"Location"];
								if ([location lengthOfRegularExpression:@"^(:?http|https)://"])
								{
									real_url = location;
								}
								else
								{
									real_url = [NSString stringWithFormat:@"%@%@",
										   [real_url replaceWithExpression:@"^((:?http|https)://.*?)/.*$" replace:@"\\1"], location];
								}
							}
							else
							{
								redirect = NO;
							}
						}
						else
						{
							redirect = NO;
						}
					}
					[http setRedirect:YES];
					result = [http get:[HTTP urlEncode:real_url
											 unescaped:@"?&=%"
											   escaped:@"+"]
								header:[NSDictionary dictionaryWithObjectsAndKeys:
										host, @"Host",
										nil]
								  body:@""];
				}
				if (!result || [result isKindOfClass:[NSError class]])
				{
					EXPLOG(@"[%@ getProfileImage] get ERROR\n%@\n%@", [self className], result, real_url);
				}
				else if ([result isKindOfClass:[NSImage class]])
				{
					profileImage = result;
				}
				else
				{
					//profileImage = [[[NSImage alloc] initWithData:result] autorelease];
#if defined(ALLOC_MAIN_THREAD)
					[self performSelectorOnMainThread:@selector(createImageWithData:)
										   withObject:result
										waitUntilDone:YES];
					profileImage = _image;
					//LOG2(@"[%@ getProfileImage] %d", [self className], [profileImage retainCount]);
#else //defined(ALLOC_MAIN_THREAD)
					profileImage = [[NSImage alloc] initWithData:result];
#endif //defined(ALLOC_MAIN_THREAD)
					if (!profileImage)
					{
#if 0 //defined(_D_E_B_U_G_)
						EXPLOG(@"[%@ getProfileImage] NSImage ERROR\n%@\n%@\n%@",
							   [self className],
							   [result className],
							   [HTTP urlEncode:url unescaped:@"?&=%" escaped:@"+"],
							   [[[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding] autorelease]);
#endif //defined(_D_E_B_U_G_)
					}
					else if (![profileImage isKindOfClass:[NSImage class]])
					{
#if 0 //defined(_D_E_B_U_G_)
						EXPLOG(@"[%@ getProfileImage] NO NSImage ERROR\n%@\n%@",
							[self className],
							[profileImage className],
							[HTTP urlEncode:url unescaped:@"?&=%" escaped:@"+"]);
#endif //defined(_D_E_B_U_G_)
						[profileImage release];
						profileImage = nil;
					}
#if 0 //defined(_D_E_B_U_G_)
					else if ([url lengthOfRegularExpression:@"tumblr\\.com/photo/"] > 0)
					{
						NSSize sz = [profileImage size];
						LOG2(@"[%@ getProfileImage]\n%@\n%f %f", [self className], real_url, sz.width, sz.height);
					}
#endif //defined(_D_E_B_U_G_)
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@ getProfileImage] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			@finally
			{
				[http release];
			}
		}
	}
	LOG(@"[%@ getProfileImage] %@ done", [self className], url);
	return profileImage;
}

- (void)performCreateProfileImage:(NSArray *)images
{
	@try
	{
		if ([images count] == 2)
		{
			NSImage *profileImage = [images objectAtIndex:0];
			NSImage *profileImage1 = [images objectAtIndex:1];
#if defined(_D_E_B_U_G_)
			if ([[NSThread currentThread] isEqual:TheMainThread])
			{
				LOG(@"[%@ setImage] MAIN THREAD !!!", [self className]);
			}
#endif //defined(_D_E_B_U_G_)
			@synchronized([NSApplication sharedApplication])
			{
				[profileImage lockFocus];
				[[NSGraphicsContext currentContext] setShouldAntialias:YES];
				[profileImage1 drawInRect:NSMakeRect(0, 0, [profileImage size].width, [profileImage size].height)
								 fromRect:NSMakeRect(0, 0, [profileImage1 size].width, [profileImage1 size].height)
								operation:NSCompositeSourceOver
								 fraction:1.0];
				[profileImage unlockFocus];
			}
		}
		else if ([images count] > 3)
		{
			NSImage *profileImage = [images objectAtIndex:0];
			NSImage *profileImage1 = [images objectAtIndex:1];
			NSImage *profileImage2 = [images objectAtIndex:2];
			BOOL isMail = [[images objectAtIndex:3] boolValue];
			NSImage *profileIcon = [[[NSImage alloc] initWithSize:NSMakeSize(18, 18)] autorelease];
#if defined(_D_E_B_U_G_)
			if ([[NSThread currentThread] isEqual:TheMainThread])
			{
				LOG(@"[%@ setImage] MAIN THREAD !!!", [self className]);
			}
#endif //defined(_D_E_B_U_G_)
			@synchronized([NSApplication sharedApplication])
			{
				[profileIcon lockFocus];
				[[NSGraphicsContext currentContext] setShouldAntialias:YES];
				[[NSColor whiteColor] set];
				if (isMail)
				{
					[NSBezierPath fillRect:NSMakeRect(3, 0, 15, 12)];
				}
				else
				{
					[NSBezierPath fillRect:NSMakeRect(0, 0, 18, 18)];
				}
				[profileImage2 drawInRect:NSMakeRect(0, 0, 18, 18)
								 fromRect:NSMakeRect(0, 0, [profileImage2 size].width, [profileImage2 size].height)
								operation:NSCompositeSourceOver
								 fraction:1.0];
				[profileIcon unlockFocus];
			}
			@synchronized([NSApplication sharedApplication])
			{
				[profileImage lockFocus];
				[[NSGraphicsContext currentContext] setShouldAntialias:YES];
				[profileImage1 drawInRect:NSMakeRect(0, 0, [profileImage size].width, [profileImage size].height)
								 fromRect:NSMakeRect(0, 0, [profileImage1 size].width, [profileImage1 size].height)
								operation:NSCompositeCopy
								 fraction:1.0];
				[profileIcon drawInRect:NSMakeRect(30, 0, 18, 18)
							   fromRect:NSMakeRect(0, 0, [profileIcon size].width, [profileIcon size].height)
							  operation:NSCompositeSourceOver
							   fraction:1.0];
				[profileImage unlockFocus];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performCreateProfileImage] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (NSImage *)createProfileImage:(NSImage *)image
					   withIcon:(NSImage *)icon
						   mail:(BOOL)isMail
{
	//NSImage *profileImage = [[[NSImage alloc] initWithSize:NSMakeSize(48, 48)] autorelease];
#if defined(ALLOC_MAIN_THREAD)
	[self performSelectorOnMainThread:@selector(createImageWithSize:)
						   withObject:[NSNumber numberWithInt:48]
						waitUntilDone:YES];
	NSImage *profileImage = _image;
#else //defined(ALLOC_MAIN_THREAD)
	NSImage *profileImage = [[NSImage alloc] initWithSize:NSMakeSize(48, 48)];
#endif //defined(ALLOC_MAIN_THREAD)
	if (profileImage)
	{
		@try
		{
			[self performCreateProfileImage:[NSArray arrayWithObjects:
											 profileImage, image, icon,
											 [NSNumber numberWithBool:isMail], nil]];
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ createProfileImage] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
	}
	return profileImage;
}

#if defined(ALLOC_MAIN_THREAD)
- (void)addProfileImage:(NSString *)userProfile
{
	@try
	{
		NSDictionary *imageItem = [NSDictionary dictionaryWithObjectsAndKeys:
								   userProfile, KeyUserProfile,
								   [_image autorelease], KeyProfileImage,
								   nil];
		@synchronized(_profile)
		{
			[_profile addObject:imageItem];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ addProfileImage] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}
#endif //defined(ALLOC_MAIN_THREAD)

- (void)threadProfileImage:(id)object
{
	//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		for (;;)
		{
			NSAutoreleasePool *internalPool = [[NSAutoreleasePool alloc] init];
			BOOL unlockNotify = NO;
			[_lockProfile lock];
			LOG(@"[%@ threadProfileImage] lock", [self className]);
			[_controller lockService];
			LOG(@"[%@ threadProfileImage] locked", [self className]);
			LOG(@"[%@ threadProfileImage]", [self className]);
			@try
			{
				[_lockQueueProfile lock];
				while ([_queueProfile count] > 0)
				{
					id item = [_queueProfile objectAtIndex:0];
					[_lockQueueProfile unlock];
					if ([item isKindOfClass:[NSDictionary class]])
					{
						NSString *userProfile = [item valueForKey:KeyUserProfile];
						if ([userProfile isEqualToString:Past])
						{
							unlockNotify = YES;
						}
						else if (![userProfile isEqualToString:@""])
						{
							NSImage *profileImage = nil;
							if ([userProfile lengthOfRegularExpression:@" "] <= 0)
							{
								NSImage *profileImage1 = [self profileImage:userProfile];
								if (!profileImage1)
								{
									if (([userProfile lengthOfRegularExpression:[NSString stringWithFormat:@"^%@", [ProfilePhoto regularizeString]]] > 0) ||
										([userProfile lengthOfRegularExpression:[NSString stringWithFormat:@"^%@", [ProfileIcon regularizeString]]] > 0) ||
										([userProfile lengthOfRegularExpression:[NSString stringWithFormat:@"^%@", [ProfileSource regularizeString]]] > 0))
									{
										NSString *url = [userProfile replaceWithExpression:@"^.*?:(.*)$"
																				   replace:@"\\1"];
										if (url)
										{
											//LOG(@"[%@ threadProfileImage] photo\n%@", [self className], url);
											profileImage1 = [self getProfileImage:url];
											//LOG(@"[%@ threadProfileImage] photo\n%@", [self className], profileImage1);
											if (profileImage1)
											{
												profileImage = profileImage1;
											}
#if defined(_D_E_B_U_G_)
											if (!profileImage1)
											{
												LOG(@"[%@ threadProfileImage] error0\n%@", [self className], url);
											}
#endif //defined(_D_E_B_U_G_)
										}
									}
									else
									{
										profileImage1 = [self getProfileImage:userProfile];
										if (profileImage1)
										{
											profileImage = [self createProfileImage:profileImage1
																		   withIcon:nil
																			   mail:NO];
											[profileImage1 release];
										}
#if defined(_D_E_B_U_G_)
										if (!profileImage1)
										{
											LOG(@"[%@ threadProfileImage] error0\n%@", [self className], userProfile);
										}
#endif //defined(_D_E_B_U_G_)
									}
								}
							}
							else
							{
								NSString *userProfile1 = [userProfile replaceWithExpression:@"^(.*) (.*)$" replace:@"\\1"];
								NSString *userProfile2 = [userProfile replaceWithExpression:@"^(.*) (.*)$" replace:@"\\2"];
								NSImage *profileImage1 = [self profileImage:userProfile1];
								NSImage *profileImage2 = [self profileImage:userProfile2];
								NSImage *getProfileImage1 = profileImage1;
								NSImage *getProfileImage2 = profileImage2;
								if (!profileImage1)
								{
									profileImage1 = [self getProfileImage:userProfile1];
#if defined(_D_E_B_U_G_)
									if (!profileImage1)
									{
										LOG(@"[%@ threadProfileImage] error1\n%@", [self className], userProfile1);
									}
#endif //defined(_D_E_B_U_G_)
								}
								if (!profileImage2)
								{
									profileImage2 = [self getProfileImage:userProfile2];
#if defined(_D_E_B_U_G_)
									if (!profileImage2)
									{
										LOG(@"[%@ threadProfileImage] error2\n%@", [self className], userProfile2);
									}
#endif //defined(_D_E_B_U_G_)
								}
								if (profileImage1 && profileImage2)
								{
									profileImage = [self createProfileImage:profileImage1
																   withIcon:profileImage2
																	   mail:[userProfile2 isEqualToString:Mail]];
								}
								if (profileImage1 && !getProfileImage1)
								{
									[profileImage1 release];
								}
								if (profileImage2 && !getProfileImage2)
								{
									[profileImage2 release];
								}
							}
							if (profileImage)
							{
#if defined(ALLOC_MAIN_THREAD)
								_image = profileImage;
								[self performSelectorOnMainThread:@selector(addProfileImage:)
													   withObject:userProfile
													waitUntilDone:YES];
#else //defined(ALLOC_MAIN_THREAD)
								NSDictionary *imageItem = [NSDictionary dictionaryWithObjectsAndKeys:
														   userProfile, KeyUserProfile,
														   [profileImage autorelease], KeyProfileImage,
														   nil];
								@synchronized(_profile)
								{
									[_profile addObject:imageItem];
								}
#endif //defined(ALLOC_MAIN_THREAD)
								[self updateProfileImage:userProfile withImage:profileImage];
								unlockNotify = YES;
							}
							else
							{
								int errorCount = 0;
								[_lockQueueError lock];
								id error = [_queueError valueForKey:userProfile];
								[_lockQueueError unlock];
								LOG(@"[%@ threadProfileImage] error: %@", [self className], error);
								if (!error)
								{
									errorCount = 1;
								}
								else
								{
									errorCount = [error intValue];
									if (errorCount < 3)
									{
										errorCount++;
									}
									else
									{
										LOG(@"[%@ threadProfileImage] ERROR\n%@", [self className], userProfile);
										errorCount = 0;
										unlockNotify = YES;
									}
								}
								//LOG(@"[%@ threadProfileImage]\nerror: %d", [self className], errorCount);
								[_lockQueueError lock];
								[_queueError setValue:[NSNumber numberWithInt:errorCount]
											   forKey:userProfile];
								[_lockQueueError unlock];
								if (errorCount > 0)
								{
									[_lockQueueProfile lock];
									[_queueProfile insertObject:item atIndex:1];
									[_lockQueueProfile unlock];
								}
								//else
								//{
								//	[_queueError removeObjectForKey:userProfile];
								//}
							}
						}
					}
					else
					{
						unlockNotify = YES;
					}
					[_lockQueueProfile lock];
					@try
					{
						[_queueProfile removeObjectAtIndex:0];
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@ threadProfileImage] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
					}
					@finally
					{
						[_lockQueueProfile unlock];
					}
					[_lockQueueProfile lock];
				}
				[_lockQueueProfile unlock];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@ threadProfileImage] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			@finally
			{
				//LOG(@"[%@ threadProfileImage] done\nnotify = %d, past = %d, photo = %d", [self className], [_queueNotify count], [_queuePast count], [_queuePhotoURL count]);
				[_lockProfile unlock];
				[self lockProfile];
				LOG(@"[%@ threadProfileImage] unlock", [self className]);
				[_controller unlockService];
				LOG(@"[%@ threadProfileImage] unlocked", [self className]);
				[_lockQueueNotify lock];
				if (unlockNotify || ([_queueNotify count] > 0))
				{
					LOG(@"[%@ threadProfileImage] unlockNotify", [self className]);
					[self unlockNotify];
				}
				else
				{
					LOG(@"[%@ threadProfileImage] done", [self className]);
				}
				[_lockQueueNotify unlock];
			}
#if defined(_D_E_B_U_G_)
#endif //defined(_D_E_B_U_G_)
			[internalPool release];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadProfileImage] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	//[pool release];
	[NSThread exit];
}

- (void)queueingUserProfile:(id)userProfile
{
	//LOG(@"[%@ queueingUserProfile]\n%@", [self className], userProfile);
	@try
	{
		id object = nil;
		if ([userProfile isKindOfClass:[NSString class]])
		{
			if ([userProfile isEqualToString:@""])
			{
				object = @"";
			}
			else if ([userProfile isEqualToString:Past])
			{
				object = Past;
			}
			else
			{
				NSPredicate *predicates = [NSPredicate predicateWithFormat:@"%K == %@",
										   KeyUserProfile,
										   userProfile];
				[_lockQueueProfile lock];
				id list = [_queueProfile filteredArrayUsingPredicate:predicates];
				[_lockQueueProfile unlock];
				if (!list || [list count] <= 0)
				{
					object = userProfile;
				}
			}
		}
		if (object)
		{
			[_lockQueueProfile lock];
			[_queueProfile addObject:
			 [NSDictionary dictionaryWithObjectsAndKeys:object, KeyUserProfile, nil]];
			[_lockQueueProfile unlock];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ queueingUserProfile] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)queueingUserProfile:(id)userProfile
				 withUnlock:(BOOL)unlock
{
	//LOG(@"[%@ queueingUserProfile:withUnlock:]\n%@", [self className], userProfile);
	BOOL lockedPast = _lockedPast;
	@try
	{
		[self queueingUserProfile:userProfile];
		if (unlock)
		{
			if (lockedPast)
			{
				[self unlockProfile];
			}
#if defined(_D_E_B_U_G_)
			else
			{
				LOG(@"[%@ queueingUserProfile:withUnlock:] !unlockProfile", [self className]);
			}
#endif //defined(_D_E_B_U_G_)
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ queueingUserProfile:withUnlock:] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)threadNotify:(id)object
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		for (;;)
		{
			NSAutoreleasePool *internalPool = [[NSAutoreleasePool alloc] init];
			BOOL unlockProfile = NO;
			int notifyN = 0;
			int countNotify = 0;
			[_lockNotify lock];
			//LOG(@"[%@ threadNotify] lock", [self className]);
			//LOG(@"[%@ threadNotify] locked", [self className]);
			LOG(@"[%@ threadNotify] %d", [self className], [_queueNotify count]);
			@try
			{
				[_lockQueueNotify lock];
				countNotify = [_queueNotify count];
				while (!unlockProfile && [_queueNotify count] > 0)
				{
					BOOL remove = YES;
					id item = [_queueNotify objectAtIndex:0];
					[_lockQueueNotify unlock];
					if ([item isKindOfClass:[NSDictionary class]])
					{
						notifyN++;
						if (![self notify1:item])
						{
							NSString *userProfile = [item valueForKey:KeyUserProfile];
							//LOG(@"[%@ threadNotify] queueing\n%@: %@\n%@", [self className], [item valueForKey:KeyUser], [item valueForKey:KeyText], userProfile);
							[self queueingUserProfile:userProfile];
							unlockProfile = YES;
							[_lockQueueError lock];
							id error = [_queueError valueForKey:userProfile];
							[_lockQueueError unlock];
							if (!error || ([error intValue] > 0))
							{
								remove = NO;
							}
							else
							{
								[_lockQueueErrorCount lock];
								error = [_queueErrorCount valueForKey:userProfile];
								[_lockQueueErrorCount unlock];
								int errorCount = 0;
								if (error)
								{
									errorCount = [error intValue];
								}
								if (errorCount <= 3)
								{
									errorCount++;
									[_lockQueueErrorCount lock];
									[_queueErrorCount setValue:[NSNumber numberWithInt:errorCount]
														forKey:userProfile];
									[_lockQueueErrorCount unlock];
								}
								if (!error || (errorCount <= 3))
								{
									[_lockQueueNotify lock];
									[_queueNotify addObject:item];
									[_lockQueueNotify unlock];
								}
							}
						}
					}
					else
					{
						LOG(@"[%@ threadNotify] %@", [self className], item);
					}
					[_lockQueueNotify lock];
					@try
					{
						if (remove)
						{
							[_queueNotify removeObjectAtIndex:0];
						}
						else
						{
							LOG(@"[%@ threadNotify] %d", [self className], [_queueNotify count]);
						}
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@ threadNotify] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
					}
				}
				countNotify = [_queueNotify count];
				[_lockQueueNotify unlock];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@ threadNotify] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			@finally
			{
				LOG(@"[%@ threadNotify] %d, %d, %d", [self className], notifyN, countNotify, [_queueProfile count]);
				[_lockNotify unlock];
				[self lockNotify];
			}
			if (unlockProfile || (countNotify > 0))
			{
				LOG(@"[%@ threadNotify] unlockProfile", [self className]);
				[self unlockProfile];
			}
			else
			{
				LOG(@"[%@ threadNotify] unlockPast", [self className]);
				[self unlockPast];
			}
			[internalPool release];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadNotify] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[pool release];
	[NSThread exit];
}

- (void)queueingNotify:(id)notifies
{
	//LOG(@"[%@ queueingNotify]", [self className]);
	@try
	{
		if ([notifies isKindOfClass:[NSArray class]])
		{
			[_lockQueueNotify lock];
			@try
			{
				NSEnumerator *enumerator = [notifies objectEnumerator];
				id obj;
				while ((obj = [enumerator nextObject]))
				{
					[_queueNotify addObject:obj];
				}
				//[_queueNotify addObject:@""];
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@ queueingUserProfile] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			@finally
			{
				[_lockQueueNotify unlock];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ queueingUserProfile] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)queueingParsePhotoURL:(NSDictionary *)item
{
	[_lockQueuePhotoURL lock];
	@try
	{
		[_queuePhotoURL addObject:item];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ addParsePhotoURL] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[_lockQueuePhotoURL unlock];
}

- (void)performReplace:(NSMutableDictionary *)object
{
	LOG(@"[%@ performReplace]", [self className]);
	@try
	{
		BOOL doReplace = NO;
		@synchronized(_timeline)
		{
			NSString *item_id = [object valueForKey:KeyId];
			NSString *service = [object valueForKey:KeyService];
			int count = [[_timeline content] count];
			int i = 0;
			while (i < count)
			{
				id obj = [[_timeline content] objectAtIndex:i];
				if ([[obj valueForKey:KeyId] isEqualToString:item_id] &&
					[[obj valueForKey:KeyService] isEqualToString:service])
				{
					//LOG(@"[%@ performReplace]\n%@[%@] %@: %@\n%@\n%@",
					//	[self className],
					//	service, item_id, [obj valueForKey:KeyUser], [object valueForKey:KeyText],
					//	[object valueForKey:KeyImageURL],
					//	[object valueForKey:KeyImage]);
					obj = [NSMutableDictionary dictionaryWithDictionary:object];
					[self cellSize:obj];
					[[_timeline content] replaceObjectAtIndex:i withObject:obj];
					doReplace = YES;
					break;
				}
				i++;
			}
		}
		if (doReplace)
		{
			[object setValue:[NSNumber numberWithBool:YES] forKey:KeyDoReplace];
			//[self performRearrangeObjects:[NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]];
			//[self performNoteNumberOfRowsChanged:nil];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performReplace] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)updatePhotoURL:(NSString *)item_id
		   withService:(NSString *)service
{
	LOG(@"[%@ updatePhotoURL] lock", [self className]);
	[_controller lockService];
	LOG(@"[%@ updatePhotoURL] locked", [self className]);
	@try
	{
		LOG(@"[%@ updatePhotoURL] timeline lock", [self className]);
		[_lockTimeline lock];
		LOG(@"[%@ updatePhotoURL] timeline locked", [self className]);
		@try
		{
			NSDictionary *item = [self fetchALL:item_id
									withService:service];
			if (item)
			{
				item = [item retain];
				@try
				{
					LOG(@"[%@ updatePhotoURL] parsePhotoURL\n%@", [self className], service);
					NSDictionary *parse = [[_controller service:service] parsePhotoURL:item];
					if (parse && ![parse isEqualToDictionary:item])
					{
						BOOL inhibitTimeline = _inhibitTimeline;
						@try
						{
							_inhibitTimeline = YES;
							LOG(@"[%@ updatePhotoURL] formatText", [self className]);
							NSMutableDictionary *replace = [NSMutableDictionary dictionaryWithDictionary:parse];
							[self formatText:replace
								 withService:[_controller service:service]
								 appearances:[_preferences appearances]];
							LOG(@"[%@ updatePhotoURL] replace", [self className]);
							[self performSelectorOnMainThread:@selector(performReplace:)
												   withObject:replace
												waitUntilDone:YES];
							if ([replace valueForKey:KeyDoReplace])
							{
#if 1
								LOG(@"[%@ updatePhotoURL] performRearrangeObjects", [self className]);
								NSDictionary *rowItem = [self dictionaryOfItem];
								[self performSelectorOnMainThread:@selector(performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint:)
													   withObject:rowItem
													waitUntilDone:YES];
								[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChangedAndScrollToPoint]];
#else
								NSDictionary *rowItem = [self dictionaryOfItem];
								[self performSelectorOnMainThread:@selector(performRearrangeObjects:)
													   withObject:[NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]
													waitUntilDone:YES];
								[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChanged]];
								[self performSelectorOnMainThread:@selector(performNoteNumberOfRowsChanged:)
													   withObject:nil
													waitUntilDone:YES];
								[self performSelectorOnMainThread:@selector(performScrollToPoint:)
													   withObject:rowItem
													waitUntilDone:YES];
#endif
							}
						}
						@catch (NSException *exception)
						{
							EXPLOG(@"[%@ updatePhotoURL] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
						}
						@finally
						{
							if (!inhibitTimeline)
							{
								_inhibitTimeline = NO;
							}
						}
					}
				}
				@catch (NSException *exception)
				{
					EXPLOG(@"[%@ updatePhotoURL] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
				}
				@finally
				{
					[item release];
				}
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ updatePhotoURL] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
		@finally
		{
			LOG(@"[%@ updatePhotoURL] timeline unlock", [self className]);
			[_lockTimeline unlock];
			LOG(@"[%@ updatePhotoURL] timeline unlocked", [self className]);
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ updatePhotoURL] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		LOG(@"[%@ updatePhotoURL] unlock", [self className]);
		[_controller unlockService];
		LOG(@"[%@ updatePhotoURL] unlocked", [self className]);
	}
}

- (void)updateTimelineLimit:(UInt32)limit
				   withItem:(NSString *)service
					   kind:(NSString *)kind
{
	if (limit)
	{
		LOG(@"[%@ updateTimelineLimit] lock", [self className]);
		[_controller lockService];
		LOG(@"[%@ updateTimelineLimit] locked", [self className]);
		@try
		{
			LOG(@"[%@ updateTimelineLimit] timeline lock", [self className]);
			[_lockTimeline lock];
			LOG(@"[%@ updateTimelineLimit] timeline locked", [self className]);
			@try
			{
				NSMutableArray *content = [_timeline content];
				int i = 0;
				int count = 0;
				while (i < [content count])
				{
					NSDictionary *object = [content objectAtIndex:i];
					if ([[object valueForKey:KeyService] isEqualToString:service] &&
						[[object valueForKey:KeyKind] isEqualToString:kind])
					{
						count++;
					}
					i++;
				}
				LOG(@"[%@ updateTimelineLimit] %d\n%@:%@", [self className], count, service, kind);
				if (count > limit)
				{
					//NSMutableArray *objects = [NSMutableArray array];
					BOOL remove = NO;
					i = [content count] - 1;
					while ((i >= 0) && (count > limit))
					{
						NSDictionary *object = [content objectAtIndex:i];
						if ([[object valueForKey:KeyService] isEqualToString:service] &&
							[[object valueForKey:KeyKind] isEqualToString:kind])
						{
							LOG(@"[%@ updateTimelineLimit] remove\n%@\n%@\n%@",
								[self className],
								[object valueForKey:KeyUser], 
								[object valueForKey:KeyText],
								[object valueForKey:KeyDate]);
							//[objects addObject:[content objectAtIndex:i]];
							[content removeObjectAtIndex:i];
							remove = YES;
							count--;
						}
						i--;
					}
#if 0
					while ([objects count] > 0)
					{
						id item = [objects objectAtIndex:0];
						NSString *image_url = [item valueForKey:KeyImageURL];
						id obj = [self fetchImageURL:image_url];
						if (!obj)
						{
							@synchronized(_profile)
							{
								i = 0;
								while (i < [[_profile content] count])
								{
									id profile = [[_profile content] objectAtIndex:i];
									if ([image_url isEqualToString:[profile valueForKey:KeyUserProfile]])
									{
#if defined(_D_E_B_U_G_)
//										int cnt = [profile retainCount];
										LOG(@"[%@ updateTimelineLimit] remove\n%@", [self className], image_url);
#endif //defined(_D_E_B_U_G_)
										[[_profile content] removeObjectAtIndex:i];
#if defined(_D_E_B_U_G_)
//										LOG2(@"[%@ updateTimelineLimit] %d / %d", [self className], cnt, [profile retainCount]);
#endif //defined(_D_E_B_U_G_)
										break;
									}
									i++;
								}
							}
						}
						[objects removeObjectAtIndex:0];
					}
#endif
					if (remove)
					{
						LOG(@"[%@ updateTimelineLimit] %d", [self className], [content count]);
						BOOL inhibitTimeline = _inhibitTimeline;
						_inhibitTimeline = YES;
#if 1
						NSDictionary *rowItem = [self dictionaryOfItem];
						[self performSelectorOnMainThread:@selector(performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint:)
											   withObject:rowItem
											waitUntilDone:YES];
						[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChangedAndScrollToPoint]];
#else
						NSDictionary *rowItem = [self dictionaryOfItem];
						[self performSelectorOnMainThread:@selector(performRearrangeObjects:)
											   withObject:[NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]
											waitUntilDone:YES];
						[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChanged]];
						[self performSelectorOnMainThread:@selector(performNoteNumberOfRowsChanged:)
											   withObject:nil
											waitUntilDone:YES];
						[self performSelectorOnMainThread:@selector(performScrollToPoint:)
											   withObject:rowItem
											waitUntilDone:YES];
#endif
						_inhibitTimeline = inhibitTimeline;
					}
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@ updateTimelineLimit] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			@finally
			{
				LOG(@"[%@ updateTimelineLimit] timeline unlock", [self className]);
				[_lockTimeline unlock];
				LOG(@"[%@ updateTimelineLimit] timeline unlocked", [self className]);
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ updateTimelineLimit] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
		@finally
		{
			LOG(@"[%@ updateTimelineLimit] unlock", [self className]);
			[_controller unlockService];
			LOG(@"[%@ updateTimelineLimit] unlocked", [self className]);
		}
	}
}

- (void)updatePast:(NSArray *)objects
{
	@try
	{
		if ([objects count] > 0)
		{
			//LOG(@"[%@ updatePast]", [self className]);
			NSDictionary *item = [objects objectAtIndex:0];
			NSString* kind = [[item valueForKey:KeyKind] retain];
			NSString* service = [[item valueForKey:KeyService] retain];
			@try
			{
				if (![kind isEqualToString:KindError])
				{
					@synchronized(_past)
					{
						UInt32 n = 0;
						UInt32 i = 0;
						while (i < [[_past content] count])
						{
							NSDictionary *past = [[_past content] objectAtIndex:i];
							if ([[past valueForKey:KeyService] isEqualToString:service] &&
								[[past valueForKey:KeyKind] isEqualToString:kind])
							{
								//LOG(@"[%@ updatePast]\n+%@ %@ %@", [self className], [past valueForKey:KeyService], [past valueForKey:KeyKind], [past valueForKey:KeyId]);
								n++;
							}
							i++;
						}
						i = 0;
						while ((i < [[_past content] count]) && (n > 50))
						{
							NSDictionary *past = [[_past content] objectAtIndex:i];
							if ([[past valueForKey:KeyService] isEqualToString:service] &&
								[[past valueForKey:KeyKind] isEqualToString:kind])
							{
								//LOG(@"[%@ updatePast]\n-%@ %@ %@", [self className], [past valueForKey:KeyService], [past valueForKey:KeyKind], [past valueForKey:KeyId]);
								[_past removeObjectAtArrangedObjectIndex:i];
								n--;
							}
							else
							{
								i++;
							}
						}
						i = 0;
						while (i < [objects count])
						{
							NSDictionary *object = [objects objectAtIndex:i];
							NSString *item_id = [object valueForKey:KeyId];
							NSString *item_service = [object valueForKey:KeyService];
							NSDictionary *fetch = [self fetchPast:item_id
													  withService:item_service
															 kind:[object valueForKey:KeyKind]];
							if (!fetch)
							{
								[_past addObject:[NSDictionary dictionaryWithObjectsAndKeys:
												  item_id, KeyId,
												  item_service, KeyService,
												  [object valueForKey:KeyKind], KeyKind,
												  nil]];
							}
							i++;
						}
					}
					[self updateTimelineLimit:[_preferences generalLimitOfTimeline]
									 withItem:service
										 kind:kind];
				}
			}
			@catch (NSException * exception)
			{
				EXPLOG(@"[%@ updatePast] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			@finally
			{
				[service release];
				[kind release];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ updatePast] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)threadPast:(id)object
{
	//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		for (;;)
		{
			NSAutoreleasePool *internalPool = [[NSAutoreleasePool alloc] init];
			[_lockPast lock];
			LOG(@"[%@ threadPast]", [self className]);
			@try
			{
				// Past
				[_lockQueuePast lock];
				@try
				{
					while ([_queuePast count] > 0)
					{
						NSArray *past = [_queuePast objectAtIndex:0];
						[_lockQueuePast unlock];
						@try
						{
							[self updatePast:past];
						}
						@catch (NSException *exception)
						{
							EXPLOG(@"[%@ threadPast] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
						}
						[_lockQueuePast lock];
						[_queuePast removeObjectAtIndex:0];
						[_lockQueueProfile lock];
						int countProfile = [_queueProfile count];
						[_lockQueueProfile unlock];
						[_lockQueueNotify lock];
						int countNotify = [_queueNotify count];
						[_lockQueueNotify unlock];
						if ((countProfile > 0) || (countNotify > 0))
						{
							break;
						}
					}
				}
				@catch (NSException *exception)
				{
					EXPLOG(@"[%@ threadNotify] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
				}
				@finally
				{
					[_lockQueuePast unlock];
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@ threadPast] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			//LOG(@"[%@ threadPast] done\nnotify = %d, past = %d, photo = %d", [self className], [_queueNotify count], [_queuePast count], [_queuePhotoURL count]);
			//[_lockPast unlock];
			//[self lockPast];
			LOG(@"[%@ threadPast] PhotoURL", [self className]);

			// PhotoURL
			[_lockQueuePhotoURL lock];
			@try
			{
				while ([_queuePhotoURL count] > 0)
				{
					// Check queueProfile
					[_lockQueueProfile lock];
					int count = [_queueProfile count];
					[_lockQueueProfile unlock];
					if (count > 0)
					{
						LOG(@"[%@ threadPast] queueProfile", [self className]);
						break;
					}
					// Check queuePast
					[_lockQueuePast lock];
					count = [_queuePast count];
					[_lockQueuePast unlock];
					if (count > 0)
					{
						LOG(@"[%@ threadPast] queuePast", [self className]);
						break;
					}
					// PhotoURL
					id item = [_queuePhotoURL objectAtIndex:0];
					[_lockQueuePhotoURL unlock];
					@try
					{
						[self updatePhotoURL:[item valueForKey:KeyId]
								 withService:[item valueForKey:KeyService]];
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@ threadNotify] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
					}
					@finally
					{
						[_lockQueuePhotoURL lock];
						[_queuePhotoURL removeObjectAtIndex:0];
					}
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@ threadPast] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			@finally
			{
				//LOG(@"[%@ threadPast] done photo\nnotify = %d, past = %d, photo = %d", [self className], [_queueNotify count], [_queuePast count], [_queuePhotoURL count]);
				[_lockQueuePhotoURL unlock];
			}
			[_lockPast unlock];
			[self lockPast];
			LOG(@"[%@ threadPast] done %d", [self className], [_queuePast count]);
			[self unlockProfile];
			[internalPool release];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadPast] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	//[pool release];
	[NSThread exit];
}

- (void)queueingPast:(NSArray *)object
		 withService:(Service *)service
{
	//LOG(@"[%@ queueingPast]", [self className]);
	[_lockQueuePast lock];
	@try
	{
		[_queuePast addObject:[NSArray arrayWithArray:object]];
		if ([object count] > 0)
		{
			if ([[[object objectAtIndex:0] valueForKey:KeyKind] isEqualToString:KindTimeline])
			{
				NSMutableArray *replies = [NSMutableArray array];
				NSEnumerator *enumerator = [object objectEnumerator];
				id obj;
				while ((obj = [enumerator nextObject]))
				{
					if ([service isReply:obj])
					{
						NSMutableDictionary *item = [NSMutableDictionary dictionaryWithDictionary:obj];
						[item setObject:KindReply forKey:KeyKind];
						[replies addObject:item];
					}
				}
				if ([replies count] > 0)
				{
#if defined(_D_E_B_U_G_)
					if ([[service service] isEqualToString:Twitter])
					{
						enumerator = [replies objectEnumerator];
						while ((obj = [enumerator nextObject]))
						{
							LOG(@"[%@ queueingPast] Reply\n%@ [%@] %@: %@ - %@",
								[self className],
								[obj valueForKey:KeyId],
								[obj valueForKey:KeyKind],
								[obj valueForKey:KeyUser],
								[obj valueForKey:KeyText],
								[[obj valueForKey:KeyDate] replaceWithExpression:@"\n"
																		 replace:@" "
																		 options:OgreMultilineOption]);
						}
					}
#endif //defined(_D_E_B_U_G_)
					[_queuePast addObject:[NSArray arrayWithArray:replies]];
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ queueingPast] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[_lockQueuePast unlock];
}

- (void)threadCellSize:(id)object
{
	//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		for (;;)
		{
			NSAutoreleasePool *internalPool = [[NSAutoreleasePool alloc] init];
			[_lockCellSize lock];
			LOG(@"[%@ threadCellSize]", [self className]);
			@try
			{
				[_lockTimeline lock];
				@try
				{
					[_lockQueueCellSize lock];
					@try
					{
						while ([_queueCellSize count] > 0)
						{
							[_queueCellSize removeObjectAtIndex:0];
							[_lockQueueCellSize unlock];
							@try
							{
								int i = 0;
								while (i < [[_timeline content] count])
								{
									[_lockQueueCellSize lock];
									if ([_queueCellSize count] > 0)
									{
										[_lockQueueCellSize unlock];
										EXPLOG(@"[%@ threadCellSize] break", [self className]);
										break;
									}
									[_lockQueueCellSize unlock];
									NSMutableDictionary *obj = [NSMutableDictionary dictionaryWithDictionary:
																[[_timeline content] objectAtIndex:i]];
									[self performSelectorOnMainThread:@selector(cellSize:)
														   withObject:obj
														waitUntilDone:YES];
									[[_timeline content] replaceObjectAtIndex:i withObject:obj];
									i++;
								}
								if (i >= [[_timeline content] count])
								{
									[_lockQueueCellSize lock];
									if ([_queueCellSize count] > 0)
									{
										[_lockQueueCellSize unlock];
										EXPLOG(@"[%@ threadCellSize] not rearrange", [self className]);
									}
									else
									{
										[_lockQueueCellSize unlock];
#if 1
										NSDictionary *rowItem = [self dictionaryOfItem];
										[self performSelectorOnMainThread:@selector(performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint:)
															   withObject:rowItem
															waitUntilDone:YES];
										[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChangedAndScrollToPoint]];
#else
										[self performSelectorOnMainThread:@selector(performRearrangeObjects:)
															   withObject:[NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]
															waitUntilDone:YES];
									}
									[_lockQueueCellSize lock];
									if ([_queueCellSize count] > 0)
									{
										[_lockQueueCellSize unlock];
										EXPLOG(@"[%@ threadCellSize] not note", [self className]);
									}
									else
									{
										[_lockQueueCellSize unlock];
										[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChanged]];
										[self performSelectorOnMainThread:@selector(performNoteNumberOfRowsChanged:)
															   withObject:nil
															waitUntilDone:YES];
									}
									[_lockQueueCellSize lock];
									if ([_queueCellSize count] > 0)
									{
										[_lockQueueCellSize unlock];
										EXPLOG(@"[%@ threadCellSize] not scroll", [self className]);
									}
									else
									{
										[_lockQueueCellSize unlock];
										NSDictionary *rowItem = [self dictionaryOfItem];
										[self performSelectorOnMainThread:@selector(performScrollToPoint:)
															   withObject:rowItem
															waitUntilDone:YES];
#endif
									}
								}
							}
							@catch (NSException *exception)
							{
								EXPLOG(@"[%@ threadCellSize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
							}
							[_lockQueueCellSize lock];
						}
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@ threadCellSize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
					}
					@finally
					{
						[_lockQueueCellSize unlock];
					}
				}
				@catch (NSException *exception)
				{
					EXPLOG(@"[%@ threadCellSize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
				}
				@finally
				{
					[_lockTimeline unlock];
				}
			}
			@catch (NSException *exception)
			{
				EXPLOG(@"[%@ threadCellSize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
			}
			[_lockCellSize unlock];
			[self lockCellSize];
			LOG(@"[%@ threadCellSize] done", [self className]);
			[internalPool release];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadCellSize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	//[pool release];
	[NSThread exit];
}

- (void)queueingCellSize:(BOOL)flag
{
	//LOG(@"[%@ queueingCellSize]", [self className]);
	[_lockQueueCellSize lock];
	@try
	{
		[_queueCellSize addObject:[NSNumber numberWithBool:flag]];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ queueingCellSize] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[_lockQueueCellSize unlock];
}

- (NSDictionary *)appearanceColor:(NSDictionary *)object
			 withColors:(NSDictionary *)colors
{
	Service *service = [_controller service:[object valueForKey:KeyService]];
	NSDictionary *color = [colors valueForKey:AppearanceNormal];
	if ([[object valueForKey:KeyKind] isEqualToString:KindDM])
	{
		color = [colors valueForKey:AppearanceMessage];
	}
	else if ([[object valueForKey:KeyKind] isEqualToString:KindReply])
	{
		color = [colors valueForKey:AppearanceReply];
	}
	else if ([service isReply:object])
	{
		color = [colors valueForKey:AppearanceReply];
	}
	else if ([[object valueForKey:KeyKind] isEqualToString:KindChannel])
	{
		color = [colors valueForKey:AppearanceChannel];
	}
	else if ([service isChannel:object])
	{
		color = [colors valueForKey:AppearanceChannel];
	}
	else if ([[object valueForKey:KeyNotify] isEqualToString:GrowlAfficheurKeywordsNotify])
	{
		color = [colors valueForKey:AppearanceKeyword];
	}
	return color;
}

- (NSAttributedString *)image:(NSImage *)image
				withBaseLine:(float)baseLine
						attr:(NSDictionary *)attr
{
	id str = nil;
	NSTextAttachment *textAtt = [[[NSTextAttachment alloc] init] autorelease];
	NSImage *cellImage = [[[NSImage alloc] initWithSize:NSMakeSize([image size].width, [image size].height)] autorelease];
	NSSize cellSize = [cellImage size];
	NSSize imageSize = [image size];
#if defined(_D_E_B_U_G_)
	if ([[NSThread currentThread] isEqual:TheMainThread])
	{
		LOG(@"[%@ setImage] MAIN THREAD !!!", [self className]);
	}
#endif //defined(_D_E_B_U_G_)
	@synchronized([NSApplication sharedApplication])
	{
		[cellImage lockFocus];
		[[NSGraphicsContext currentContext] setShouldAntialias:YES];
		[image drawInRect:NSMakeRect(0, 0, cellSize.width, cellSize.height)
				 fromRect:NSMakeRect(0, 0, imageSize.width, imageSize.height)
				operation:NSCompositeCopy
				 fraction:1.0];
		[cellImage unlockFocus];
	}
	NSTextAttachmentCell *cell = [[[NSTextAttachmentCell alloc] initImageCell:cellImage] autorelease];
	[textAtt setAttachmentCell:cell];
	if (textAtt)
	{
		str = [NSMutableAttributedString attributedStringWithAttachment:textAtt];
		NSMutableDictionary *attach = [NSMutableDictionary
									   dictionaryWithDictionary:attr];
		[attach setValue:[NSNumber numberWithFloat:baseLine]
				  forKey:NSBaselineOffsetAttributeName];
		[str addAttributes:attach
					 range:NSMakeRange(0, [str length])];
	}
	return str;
}

- (NSAttributedString *)icon:(NSString *)icon
				withBaseLine:(float)baseLine
						attr:(NSDictionary *)attr
{
	return [self image:[NSImage imageNamed:icon]
		  withBaseLine:baseLine
				  attr:attr];
}

- (NSString *)formatNotifyTitle:(id)object
				   withService:(Service *)service
				   appearances:(NSDictionary *)appearances
{
	NSString *user = [object valueForKey:KeyUser];
	NSString *name = [object valueForKey:KeyName];
	NSString *channel = [object valueForKey:KeyChannel];
	NSString *header = [NSString stringWithFormat:@"%@%@%@", 
						[service convertText:user],
						[service convertText:name],
						[service convertText:channel]];
	return [NSString stringWithString:header];
}

- (NSString *)formatTextNotify:(id)object
				   withService:(Service *)service
				   appearances:(NSDictionary *)appearances
{
	NSString *text = [object valueForKey:KeyTextEx];
	NSString *comment = [object valueForKey:KeyComment];
	if (!text || [text isKindOfClass:[NSNull class]])
	{
		text = @"";
	}
	NSString *cmtN = @"";
	if (comment && ![comment isKindOfClass:[NSNull class]] && ![comment isEqualToString:@""])
	{
		cmtN = [NSString stringWithFormat:@"\n%@",
				[comment replaceWithExpression:@"\\s\\s\\s+" replace:@" "]];
	}
	else
	{
		comment = @"";
	}
	NSString *user = [object valueForKey:KeyUser];
	NSString *name = [object valueForKey:KeyName];
	NSString *channel = [object valueForKey:KeyChannel];
	NSString *header = [NSString stringWithFormat:@"%@%@%@", 
						[service convertText:user],
						[service convertText:name],
						[service convertText:channel]];
	NSString *notifyTitle = [NSString stringWithString:header];
	if (![[object valueForKey:KeyId] isEqualToString:IdZero])
	{
		[object setValue:notifyTitle forKey:KeyNotifyTitle];
	}
	NSString *txt = [text replaceWithExpression:@"\\s\\s\\s+" replace:@" "];
	NSString *fb_name = [object valueForKey:KeyFbName];
	if (fb_name)
	{
		NSString *lf = @"\n";
		if ([txt isEqualToString:@""])
		{
			lf = @"";
		}
		txt = [NSString stringWithFormat:@"%@%@%@", txt, lf, fb_name];
	}
	NSString *fb_caption = [object valueForKey:KeyFbCaption];
	if (fb_caption)
	{
		NSString *lf = @"\n";
		if ([txt isEqualToString:@""])
		{
			lf = @"";
		}
		txt = [NSString stringWithFormat:@"%@%@%@", txt, lf, fb_caption];
	}
	NSString *fb_description = [object valueForKey:KeyFbDescription];
	if (fb_description)
	{
		NSString *lf = @"\n";
		if ([txt isEqualToString:@""])
		{
			lf = @"";
		}
		txt = [NSString stringWithFormat:@"%@%@%@", txt, lf, fb_description];
	}
	NSString *fb_link = [object valueForKey:KeyFbLink];
	if (fb_link && ([txt lengthOfRegularExpression:[fb_link regularizeString]
									   withOptions:OgreMultilineOption] == 0))
	{
		NSString *lf = @"\n";
		if ([txt isEqualToString:@""])
		{
			lf = @"";
		}
		txt = [NSString stringWithFormat:@"%@%@%@", txt, lf, fb_link];
	}
	NSString *txtN = [NSString stringWithFormat:@"%@%@",
					  txt ,
					  cmtN
					  ];
	if ([_preferences generalGrowlDate])
	{
		NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
		[formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
		NSLocale *locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
		[formatter setLocale:locale];
		[formatter setDateFormat:@"yyyy/MM/dd HH:mm:ss"];
		txtN = [NSString stringWithFormat:@"%@\n%@",
				txtN,
				[formatter stringFromDate:[object valueForKey:KeyCreatedAt]]
				];
	}
	return txtN;
}

- (NSAttributedString *)formatTextDisplay:(id)object
			  withService:(Service *)service
			  appearances:(NSDictionary *)appearances
{
	NSString *retweetedBy = [object valueForKey:KeyRetweetedBy];
	NSString *text = [object valueForKey:KeyTextEx];
	NSString *comment = [object valueForKey:KeyComment];
	if (!text || [text isKindOfClass:[NSNull class]])
	{
		text = @"";
	}
	NSString *cmtD = @"";
	if (comment && ![comment isKindOfClass:[NSNull class]] && ![comment isEqualToString:@""])
	{
		cmtD = [NSString stringWithFormat:@"\n%@",
				[[comment replaceWithExpression:@"\\n" replace:@" "]
				 replaceWithExpression:@"\\s\\s\\s" replace:@" "]];
	}
	else
	{
		comment = @"";
	}
	NSString *user = [object valueForKey:KeyUser];
	NSString *name = [object valueForKey:KeyName];
	NSString *channel = [object valueForKey:KeyChannel];
	NSString *header = [NSString stringWithFormat:@"%@%@%@", 
						[service convertText:user],
						[service convertText:name],
						[service convertText:channel]];
	NSString *txt = [text replaceWithExpression:@"\\s\\s\\s" replace:@" "];
	NSDictionary *fonts = [appearances valueForKey:AppearanceFont];
	NSDictionary *colors = [appearances valueForKey:AppearanceColor];
	NSDictionary *color = [self appearanceColor:object withColors:colors];
	// TextDisplay
	NSMutableAttributedString *txtD = [[[NSMutableAttributedString alloc] init] autorelease];
	NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
						  [fonts valueForKey:AppearanceUser], NSFontAttributeName,
						  [color valueForKey:AppearanceUser], NSForegroundColorAttributeName,
						  nil];
	[txtD beginEditing];
	if (![header isEqualToString:@""])
	{
		if (retweetedBy && ![retweetedBy isEqualToString:@""])
		{
			[txtD appendAttributedString:[self icon:IconRetweet withBaseLine:-2 attr:attr]];
		}
		[txtD appendAttributedString:[service attributedString:header
													  withSize:12
													  baseLine:-2
														  attr:attr]];
		if ([[object valueForKey:KeyVerified] intValue])
		{
			[txtD appendAttributedString:[self icon:IconVerified withBaseLine:-2 attr:attr]];
		}
		if ([[object valueForKey:KeyProtected] intValue])
		{
			[txtD appendAttributedString:[service attributedString:@" "
														  withSize:12
														  baseLine:-2
															  attr:attr]];
			[txtD appendAttributedString:[self icon:IconProtect withBaseLine:-2 attr:attr]];
		}
		if (retweetedBy && ![retweetedBy isEqualToString:@""])
		{
			[txtD appendAttributedString:[service attributedString:@"  by "
														  withSize:12
														  baseLine:-2
															  attr:[NSDictionary dictionaryWithObjectsAndKeys:
																	[fonts valueForKey:AppearanceText], NSFontAttributeName,
																	[color valueForKey:AppearanceText], NSForegroundColorAttributeName,
																	nil]]];
			[txtD appendAttributedString:[service attributedString:retweetedBy
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
		[txtD appendAttributedString:[service attributedString:@"\n"
													  withSize:12
													  baseLine:-2
														  attr:attr]];
	}
	attr = [NSDictionary dictionaryWithObjectsAndKeys:
			[fonts valueForKey:AppearanceText], NSFontAttributeName,
			[color valueForKey:AppearanceText], NSForegroundColorAttributeName,
			nil];
	NSImage *icon = [object valueForKey:KeyIconImage];
	if (icon)
	{
		if ([icon isKindOfClass:[NSNull class]])
		{
			[object removeObjectForKey:KeyIconImage];
		}
		else
		{
			[txtD appendAttributedString:[self image:icon withBaseLine:-2 attr:attr]];
			[txtD appendAttributedString:[service attributedString:@" "
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
	}
	[txtD appendAttributedString:[service attributedString:txt
												  withSize:12
												  baseLine:-2
													  attr:attr]];
	NSString *fb_name = [object valueForKey:KeyFbName];
	NSString *fb_caption = [object valueForKey:KeyFbCaption];
	NSString *fb_description = [object valueForKey:KeyFbDescription];
	NSString *fb_link = [object valueForKey:KeyFbLink];
	if  (fb_name || fb_caption || fb_description || fb_link)
	{
		NSString *textD = txt;
		if (![txt isEqualToString:@""])
		{
			[txtD appendAttributedString:[service attributedString:@"\n"
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
		if (fb_name)
		{
			NSString *lf = @"\n";
			if ([textD isEqualToString:@""])
			{
				lf = @"";
			}
			textD = [NSString stringWithFormat:@"%@%@", textD, fb_name];
			attr = [NSDictionary dictionaryWithObjectsAndKeys:
					[fonts valueForKey:AppearanceUser], NSFontAttributeName,
					[color valueForKey:AppearanceUser], NSForegroundColorAttributeName,
					nil];
			[txtD appendAttributedString:[service attributedString:[NSString stringWithFormat:@"%@%@", lf, fb_name]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
		if (fb_caption)
		{
			NSString *lf = @"\n";
			if ([textD isEqualToString:@""])
			{
				lf = @"";
			}
			textD = [NSString stringWithFormat:@"%@%@", textD, fb_caption];
			attr = [NSDictionary dictionaryWithObjectsAndKeys:
					[fonts valueForKey:AppearanceText], NSFontAttributeName,
					[color valueForKey:AppearanceText], NSForegroundColorAttributeName,
					nil];
			[txtD appendAttributedString:[service attributedString:[NSString stringWithFormat:@"%@%@", lf, fb_caption]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
		if (fb_description)
		{
			NSString *lf = @"\n";
			if ([textD isEqualToString:@""])
			{
				lf = @"";
			}
			textD = [NSString stringWithFormat:@"%@%@", textD, fb_description];
			attr = [NSDictionary dictionaryWithObjectsAndKeys:
					[fonts valueForKey:AppearanceComment], NSFontAttributeName,
					[color valueForKey:AppearanceComment], NSForegroundColorAttributeName,
					nil];
			[txtD appendAttributedString:[service attributedString:[NSString stringWithFormat:@"%@%@", lf, fb_description]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
		if (fb_link && ([textD lengthOfRegularExpression:[fb_link regularizeString]
											 withOptions:OgreMultilineOption] == 0))
		{
			NSString *lf = @"\n";
			if ([textD isEqualToString:@""])
			{
				lf = @"";
			}
			textD = [NSString stringWithFormat:@"%@%@", textD, fb_link];
			attr = [NSDictionary dictionaryWithObjectsAndKeys:
					[fonts valueForKey:AppearanceText], NSFontAttributeName,
					[color valueForKey:AppearanceText], NSForegroundColorAttributeName,
					nil];
			[txtD appendAttributedString:[service attributedString:[NSString stringWithFormat:@"%@%@", lf, fb_link]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
	}
	if ([_preferences generalIndicateSource])
	{
		if (![[object valueForKey:KeySource] isEqualToString:@""])
		{
			attr = [NSDictionary dictionaryWithObjectsAndKeys:
					[fonts valueForKey:AppearanceText], NSFontAttributeName,
					[color valueForKey:AppearanceText], NSForegroundColorAttributeName,
					nil];
			NSString *source = @"\nfrom %@";
			icon = [object valueForKey:KeySourceIconImage];
			if (icon)
			{
				if ([icon isKindOfClass:[NSNull class]])
				{
					[object removeObjectForKey:KeySourceIconImage];
				}
				else
				{
					[txtD appendAttributedString:[service attributedString:@"\n"
																  withSize:12
																  baseLine:-2
																	  attr:attr]];
					[txtD appendAttributedString:[self image:icon withBaseLine:-2 attr:attr]];
					[txtD appendAttributedString:[service attributedString:@" "
																  withSize:12
																  baseLine:-2
																	  attr:attr]];
					source = @"from %@";
				}
			}
			[txtD appendAttributedString:[service attributedString:
										  [NSString stringWithFormat:source,
										   [object valueForKey:KeySource]]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
	}
	attr = [NSDictionary dictionaryWithObjectsAndKeys:
			[fonts valueForKey:AppearanceComment], NSFontAttributeName,
			[color valueForKey:AppearanceComment], NSForegroundColorAttributeName,
			nil];
	[txtD appendAttributedString:[service attributedString:cmtD
												  withSize:12
												  baseLine:-2
													  attr:attr]];
	NSImage *photo = [object valueForKey:KeyImage];
	if (photo && [_preferences generalInlineImage])
	{
		if ([photo isKindOfClass:[NSNull class]])
		{
			[object removeObjectForKey:KeyImage];
		}
		else
		{
			//LOG(@"[%@ formatText] photo\n%@", [self className], photo);
			[txtD appendAttributedString:[service attributedString:photo
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
	}
	[txtD endEditing];
	return txtD;
}

- (NSAttributedString *)formatTextDisplaySelected:(id)object
									  withService:(Service *)service
									  appearances:(NSDictionary *)appearances
{
	NSString *retweetedBy = [object valueForKey:KeyRetweetedBy];
	NSString *text = [object valueForKey:KeyTextEx];
	NSString *comment = [object valueForKey:KeyComment];
	if (!text || [text isKindOfClass:[NSNull class]])
	{
		text = @"";
	}
	NSString *cmtD = @"";
	if (comment && ![comment isKindOfClass:[NSNull class]] && ![comment isEqualToString:@""])
	{
		cmtD = [NSString stringWithFormat:@"\n%@",
				[[comment replaceWithExpression:@"\\n" replace:@" "]
				 replaceWithExpression:@"\\s\\s\\s" replace:@" "]];
	}
	else
	{
		comment = @"";
	}
	NSString *user = [object valueForKey:KeyUser];
	NSString *name = [object valueForKey:KeyName];
	NSString *channel = [object valueForKey:KeyChannel];
	NSString *header = [NSString stringWithFormat:@"%@%@%@", 
						[service convertText:user],
						[service convertText:name],
						[service convertText:channel]];
	NSString *txt = [text replaceWithExpression:@"\\s\\s\\s" replace:@" "];
	NSDictionary *fonts = [appearances valueForKey:AppearanceFont];
	// TextDisplaySelected
	NSMutableAttributedString *txtD = [[[NSMutableAttributedString alloc] init] autorelease];
	NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
						  [fonts valueForKey:AppearanceUser], NSFontAttributeName,
						  [NSColor selectedTextColor], NSForegroundColorAttributeName,
						  nil];
	[txtD beginEditing];
	if (![header isEqualToString:@""])
	{
		if (retweetedBy && ![retweetedBy isEqualToString:@""])
		{
			[txtD appendAttributedString:[self icon:IconRetweet withBaseLine:-2 attr:attr]];
		}
		[txtD appendAttributedString:[service attributedString:header
													  withSize:12
													  baseLine:-2
														  attr:attr]];
		if ([[object valueForKey:KeyVerified] intValue])
		{
			[txtD appendAttributedString:[self icon:IconVerified withBaseLine:-2 attr:attr]];
		}
		if ([[object valueForKey:KeyProtected] intValue])
		{
			[txtD appendAttributedString:[service attributedString:@" "
														  withSize:12
														  baseLine:-2
															  attr:attr]];
			[txtD appendAttributedString:[self icon:IconProtect withBaseLine:-2 attr:attr]];
		}
		if (retweetedBy && ![retweetedBy isEqualToString:@""])
		{
			[txtD appendAttributedString:[service attributedString:@"  by "
														  withSize:16
														  baseLine:4
															  attr:attr]];
			[txtD appendAttributedString:[service attributedString:retweetedBy
														  withSize:12
														  baseLine:-2
															  attr:[NSDictionary dictionaryWithObjectsAndKeys:
																	[fonts valueForKey:AppearanceText], NSFontAttributeName,
																	[NSColor selectedTextColor], NSForegroundColorAttributeName,
																	nil]]];
		}
		[txtD appendAttributedString:[service attributedString:@"\n"
													  withSize:12
													  baseLine:-2
														  attr:attr]];
	}
	attr = [NSDictionary dictionaryWithObjectsAndKeys:
			[fonts valueForKey:AppearanceText], NSFontAttributeName,
			[NSColor selectedTextColor], NSForegroundColorAttributeName,
			nil];
	NSImage *icon = [object valueForKey:KeyIconImage];
	if (icon)
	{
		if ([icon isKindOfClass:[NSNull class]])
		{
			[object removeObjectForKey:KeyIconImage];
		}
		else
		{
			[txtD appendAttributedString:[self image:icon withBaseLine:-2 attr:attr]];
			[txtD appendAttributedString:[service attributedString:@" "
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
	}
	[txtD appendAttributedString:[service attributedString:txt
												  withSize:12
												  baseLine:-2
													  attr:attr]];
	NSString *fb_name = [object valueForKey:KeyFbName];
	NSString *fb_caption = [object valueForKey:KeyFbCaption];
	NSString *fb_description = [object valueForKey:KeyFbDescription];
	NSString *fb_link = [object valueForKey:KeyFbLink];
	if  (fb_name || fb_caption || fb_description || fb_link)
	{
		NSString *textD = txt;
		if (![txt isEqualToString:@""])
		{
			[txtD appendAttributedString:[service attributedString:@"\n"
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
		if (fb_name)
		{
			NSString *lf = @"\n";
			if ([textD isEqualToString:@""])
			{
				lf = @"";
			}
			textD = [NSString stringWithFormat:@"%@%@", textD, fb_name];
			[NSDictionary dictionaryWithObjectsAndKeys:
			 [fonts valueForKey:AppearanceUser], NSFontAttributeName,
			 [NSColor selectedTextColor], NSForegroundColorAttributeName,
			 nil];
			[txtD appendAttributedString:[service attributedString:[NSString stringWithFormat:@"%@%@", lf, fb_name]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
		if (fb_caption)
		{
			NSString *lf = @"\n";
			if ([textD isEqualToString:@""])
			{
				lf = @"";
			}
			textD = [NSString stringWithFormat:@"%@%@", textD, fb_caption];
			attr = [NSDictionary dictionaryWithObjectsAndKeys:
					[fonts valueForKey:AppearanceText], NSFontAttributeName,
					[NSColor selectedTextColor], NSForegroundColorAttributeName,
					nil];
			[txtD appendAttributedString:[service attributedString:[NSString stringWithFormat:@"%@%@", lf, fb_caption]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
		if (fb_description)
		{
			NSString *lf = @"\n";
			if ([textD isEqualToString:@""])
			{
				lf = @"";
			}
			textD = [NSString stringWithFormat:@"%@%@", textD, fb_description];
			attr = [NSDictionary dictionaryWithObjectsAndKeys:
					[fonts valueForKey:AppearanceComment], NSFontAttributeName,
					[NSColor selectedTextColor], NSForegroundColorAttributeName,
					nil];
			[txtD appendAttributedString:[service attributedString:[NSString stringWithFormat:@"%@%@", lf, fb_description]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
		if (fb_link && ([textD lengthOfRegularExpression:[fb_link regularizeString]
											 withOptions:OgreMultilineOption] == 0))
		{
			NSString *lf = @"\n";
			if ([textD isEqualToString:@""])
			{
				lf = @"";
			}
			textD = [NSString stringWithFormat:@"%@%@", textD, fb_link];
			attr = [NSDictionary dictionaryWithObjectsAndKeys:
					[fonts valueForKey:AppearanceText], NSFontAttributeName,
					[NSColor selectedTextColor], NSForegroundColorAttributeName,
					nil];
			[txtD appendAttributedString:[service attributedString:[NSString stringWithFormat:@"%@%@", lf, fb_link]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
	}
	if ([_preferences generalIndicateSource])
	{
		if (![[object valueForKey:KeySource] isEqualToString:@""])
		{
			attr = [NSDictionary dictionaryWithObjectsAndKeys:
					[fonts valueForKey:AppearanceText], NSFontAttributeName,
					[NSColor selectedTextColor], NSForegroundColorAttributeName,
					nil];
			NSString *source = @"\nfrom %@";
			icon = [object valueForKey:KeySourceIconImage];
			if (icon)
			{
				if ([icon isKindOfClass:[NSNull class]])
				{
					[object removeObjectForKey:KeySourceIconImage];
				}
				else
				{
					[txtD appendAttributedString:[service attributedString:@"\n"
																  withSize:12
																  baseLine:-2
																	  attr:attr]];
					[txtD appendAttributedString:[self image:icon withBaseLine:-2 attr:attr]];
					[txtD appendAttributedString:[service attributedString:@" "
																  withSize:12
																  baseLine:-2
																	  attr:attr]];
					source = @"from %@";
				}
			}
			[txtD appendAttributedString:[service attributedString:
										  [NSString stringWithFormat:source,
										   [object valueForKey:KeySource]]
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
	}
	attr = [NSDictionary dictionaryWithObjectsAndKeys:
			[fonts valueForKey:AppearanceComment], NSFontAttributeName,
			[NSColor selectedTextColor], NSForegroundColorAttributeName,
			nil];
	[txtD appendAttributedString:[service attributedString:cmtD
												  withSize:12
												  baseLine:-2
													  attr:attr]];
	NSImage *photo = [object valueForKey:KeyImage];
	if (photo && [_preferences generalInlineImage])
	{
		if ([photo isKindOfClass:[NSNull class]])
		{
			[object removeObjectForKey:KeyImage];
		}
		else
		{
			[txtD appendAttributedString:[service attributedString:photo
														  withSize:12
														  baseLine:-2
															  attr:attr]];
		}
	}
	[txtD endEditing];
	return txtD;
}

- (NSAttributedString *)formatDateDisplay:(id)object
							  withService:(Service *)service
							  appearances:(NSDictionary *)appearances
{
	NSDictionary *fonts = [appearances valueForKey:AppearanceFont];
	NSDictionary *colors = [appearances valueForKey:AppearanceColor];
	NSDictionary *color = [self appearanceColor:object withColors:colors];
	// DateDisplay
	NSString *date = [object valueForKey:KeyDate];
	if (date)
	{
		NSMutableParagraphStyle *paragraph = [[[NSMutableParagraphStyle alloc] init] autorelease];
		[paragraph  setAlignment:NSCenterTextAlignment];
		NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
							  [fonts valueForKey:AppearanceDate], NSFontAttributeName,
							  [color valueForKey:AppearanceDate], NSForegroundColorAttributeName,
							  paragraph, NSParagraphStyleAttributeName,
							  nil];
		return [[[NSMutableAttributedString alloc] initWithString:date
													   attributes:attr] autorelease];
	}
	return nil;
}

- (NSAttributedString *)formatDateDisplaySelected:(id)object
									  withService:(Service *)service
									  appearances:(NSDictionary *)appearances
{
	NSDictionary *fonts = [appearances valueForKey:AppearanceFont];
	// DateDisplaySelected
	NSString *date = [object valueForKey:KeyDate];
	if (date)
	{
		NSMutableParagraphStyle *paragraph = [[[NSMutableParagraphStyle alloc] init] autorelease];
		[paragraph  setAlignment:NSCenterTextAlignment];
		NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
							  [fonts valueForKey:AppearanceDate], NSFontAttributeName,
							  [NSColor selectedTextColor], NSForegroundColorAttributeName,
							  paragraph, NSParagraphStyleAttributeName,
							  nil];
		return [[[NSMutableAttributedString alloc] initWithString:date
													   attributes:attr] autorelease];
	}
	return nil;
}

- (void)formatText:(id)object
	   withService:(Service *)service
	   appearances:(NSDictionary *)appearances
{
	//LOG(@"[%@ formatText]", [self className]);
	@try
	{
		NSString *notifyTitle = [self formatNotifyTitle:object withService:service appearances:appearances];
		if (![[object valueForKey:KeyId] isEqualToString:IdZero])
		{
			[object setValue:notifyTitle forKey:KeyNotifyTitle];
		}
		NSString *txtN = [self formatTextNotify:object withService:service appearances:appearances];
		[object setValue:txtN forKey:KeyTextNotify];
		NSAttributedString *txtD = [self formatTextDisplay:object withService:service appearances:appearances];
		[object setValue:txtD forKey:KeyTextDisplay];
		txtD = [self formatTextDisplaySelected:object withService:service appearances:appearances];
		[object setValue:txtD forKey:KeyTextDisplaySelected];
		txtD = [self formatDateDisplay:object withService:service appearances:appearances];
		if (txtD)
		{
			[object setValue:txtD forKey:KeyDateDisplay];
		}
		txtD = [self formatDateDisplaySelected:object withService:service appearances:appearances];
		if (txtD)
		{
			[object setValue:txtD forKey:KeyDateDisplaySelected];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ formatText] EXCEPTION\n%@: %@\n%@", [self className], [exception name], [exception reason], object);
	}
}

- (BOOL)addTimeline:(NSDictionary *)item
		withService:(Service *)service
			  first:(BOOL)first;
{
	//LOG(@"[%@ addTimeline]", [self className]);
	BOOL notify = NO;
	[item retain];
	[_lockTimeline lock];
	@try
	{
		int keyword = [[item valueForKey:KeyKeyword] intValue];
		NSMutableDictionary *object = [NSMutableDictionary dictionaryWithDictionary:item];
		NSString *serviceName = [object valueForKey:KeyService];
		if ([[object valueForKey:KeyService] isEqualToString:Identica] &&
			[[object valueForKey:KeyFrom] isEqualToString:FromAPI] &&
			![[object valueForKey:KeyId] isEqualToString:IdZero])
		{
			NSArray *xmpp = [self fetchFromXMPPWithService:serviceName];
			if (xmpp)
			{
				NSString *xmppId = [object valueForKey:KeyId];
				int count = [xmpp count];
				int i = 0;
				while (i < [[_timeline content] count])
				{
					id obj = [[_timeline content] objectAtIndex:i];
					if ([[obj valueForKey:KeyService] isEqualToString:serviceName] &&
						[[obj valueForKey:KeyFrom] isEqualToString:FromXMPP])
					{
						//LOG(@"[%@ addObject] from XMPP"\
						//	@"\n%@"\
						//	@"\n\'%@\'",
						//	[self className],
						//	[obj valueForKey:KeyId], [obj valueForKey:KeyText]);
						if([[obj valueForKey:KeyId] isEqualToString:xmppId])
						{
							LOG(@"[%@ addObject] Replace text"\
								@"\n%@"\
								@"\n\'%@\'",
								[self className],
								[object valueForKey:KeyId], [object valueForKey:KeyText]);
							[object setValue:[obj valueForKey:KeyText] forKey:KeyText];
							[object setValue:[obj valueForKey:KeyText] forKey:KeyTextEx];
							[object setValue:[obj valueForKey:KeyTextRaw] forKey:KeyTextRaw];
							break;
						}
						count--;
						if (count <= 0)
						{
							break;
						}
					}
					i++;
				}
			}
		}
		NSString *item_id = [object valueForKey:KeyId];
		id fetch_item = [self fetch:item_id withService:serviceName];
		BOOL add = NO;
		if ([item_id isEqualToString:IdZero])
		{
			add = YES;
		}
		else if ([[object valueForKey:KeyFrom] isEqualToString:FromXMPP])
		{
			if (![self fetchFromAPIWithService:serviceName
									  user:[object valueForKey:KeyUser]
								  text_raw:[object valueForKey:KeyTextRaw]])
			{
				add = YES;
			}
			else if ([[object valueForKey:KeyService] isEqualToString:Jaiku] ||
					 [[object valueForKey:KeyService] isEqualToString:Identica])
			{
				if	(![self fetch:item_id withService:serviceName])
				{
					add = YES;
				}
			}
		}
		else if	(!fetch_item)
		{
			add = YES;
		}
		else if ([[object valueForKey:KeyCategory] isEqualToString:KindReply] &&
				 ![[fetch_item valueForKey:KeyCategory] isEqualToString:KindReply])
		{
			add = YES;
		}
		if (add)
		{
			_inhibitTimeline = YES;
			object = [NSMutableDictionary dictionaryWithDictionary:object];
			//LOG(@"[%@ addTimeline] %@:%@", [self className], serviceName, item_id);
			NSString *userProfile = [object valueForKey:KeyUserProfile];
			id profileImage = [self profileImage:userProfile];
			if (![userProfile isEqualToString:@""] && profileImage)
			{
				[object setValue:profileImage forKey:KeyProfileImage];
			}
			else
			{
				//[self queueingUserProfile:[object valueForKey:KeyUserProfile]];
			}
			if ([[object valueForKey:KeyFrom] isEqualToString:FromXMPP])
			{
				notify = YES;
			}
			else if (![self fetchALL:item_id withService:serviceName] &&
					 ![self fetchFromXMPPWithService:serviceName
												user:[object valueForKey:KeyUser]
											text_raw:[object valueForKey:KeyTextRaw]])
			{
				notify = YES;
			}
			if ((keyword == -1) || !(keyword & 2))
			{
				[self formatText:object
					 withService:service
					 appearances:[_preferences appearances]];
				[_queueAdd addObject:object];
				id reply = [self addTimelineObject:object withFirst:first];
				if (reply)
				{
					[object retain];
					@try
					{
						[service doReply:[object valueForKey:KeyId] withText:NO];
						[service removeDelayedReply:[reply valueForKey:KeyId]];
					}
					@catch (NSException *exception)
					{
						EXPLOG(@"[%@ addTimeline] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
					}
					@finally
					{
						[object release];
						[reply release];
					}
				}
			}
		}
		if (notify)
		{
			if (keyword != -1)
			{
				if (keyword & 1)
				{
					notify = NO;
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ addTimeline] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[_lockTimeline unlock];
		[item release];
	//	[self dictionaryOfItem];
	}
	return notify;
}

- (NSString *)IDs:(NSArray *)array
{
	NSMutableString *str = [NSMutableString stringWithString:@""];
	NSEnumerator *enumerator = [array objectEnumerator];
	id obj;
	while ((obj = [enumerator nextObject]))
	{
		//LOG(@"[%@ IDs] %@\n%@", [self className], [obj className], obj);
		if (![str isEqualToString:@""])
		{
			[str appendString:@"\n"];
		}
		[str appendFormat:@"%@:", [obj valueForKey:KeyService]];
		[str appendFormat:@"%@ ", [obj valueForKey:KeyId]];
		[str appendFormat:@"%@ / ", [obj valueForKey:KeyUser]];
		[str appendFormat:@"%@", [obj valueForKey:KeyTextRaw]];
	}
	return str;
}

- (void)finishAddTimeline:(NSArray *)objects
			   withNotify:(NSArray *)notify
				  service:(Service *)service
{
	//LOG(@"[%@ finishAddTimeline]\n%d / %d / %d", [self className], [objects count], [notify count], [_queueProfile count]);
	[_lockTimeline lock];
	@try
	{
		if ([_queueAdd count] > 0)
		{
			LOG(@"[%@ finishAddTimeline]"\
				@"\nobjects = %d"\
				@", notify = %d"\
				@", profile = %d"\
				@", add = %d",
				[self className], [objects count], [notify count], [_queueProfile count], [_queueAdd count]);
			LOG(@"[%@ finishAddTimeline(%@)]\nperformRearrangeObjects %d", [self className], service, [[_timeline content] count]);
#if 1
			NSDictionary *rowItem = [self dictionaryOfItem];
			[self performSelectorOnMainThread:@selector(performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint:)
								   withObject:rowItem
								waitUntilDone:YES];
			[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChangedAndScrollToPoint]];
#else
			NSString *serviceName = [service service];
			NSDictionary *rowItem = [self dictionaryOfItem];
			[self performSelectorOnMainThread:@selector(performRearrangeObjects:)
								   withObject:[NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]
								waitUntilDone:YES];
			if (([notify count] < [_queueAdd count]) || ([[_controller service:serviceName] changeHeight]))
			{
				[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChanged]];
				[self performSelectorOnMainThread:@selector(performNoteNumberOfRowsChanged:)
									   withObject:nil
									waitUntilDone:YES];
			}
			[self performSelectorOnMainThread:@selector(performScrollToPoint:)
								   withObject:rowItem
								waitUntilDone:YES];
#endif
			while ([_queueAdd count] > 0)
			{
				[self queueingParsePhotoURL:[_queueAdd objectAtIndex:0]];
				[_queueAdd removeObjectAtIndex:0];
			}
		}
		[self queueingNotify:notify];
		[self queueingPast:objects withService:service];
		[self queueingUserProfile:Past withUnlock:YES];
		//[self queueingUserProfile:Past];
		//[self unlockProfile];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ finishAddTimeline] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		//LOG(@"[%@ finishAddTimeline(%@)] done\nprofile = %d, notify = %d, past = %d, photo   = %d", [self className], serviceName, [_queueProfile count], [_queueNotify count], [_queuePast count], [_queuePhotoURL count]);
		_inhibitTimeline = NO;
		[_lockTimeline unlock];
	}
	if (_keyView)
	{
		[self tableView:_view flagsChanged:nil];
	}
}

- (void)performReplacePhotoURL:(NSDictionary *)object
{
	LOG(@"[%@ performReplacePhotoURL]", [self className]);
	@try
	{
		BOOL doReplace = NO;
		@synchronized(_timeline)
		{
			NSString *item_id = [object valueForKey:KeyId];
			NSString *service = [object valueForKey:KeyService];
			int count = [[_timeline content] count];
			int i = 0;
			while (i < count)
			{
				id obj = [[_timeline content] objectAtIndex:i];
				if ([[obj valueForKey:KeyId] isEqualToString:item_id] &&
					[[obj valueForKey:KeyService] isEqualToString:service])
				{
#if 0 //defined(_D_E_B_U_G_)
					NSString *photoURL = [object valueForKey:KeyImageURL];
					LOG(@"[%@ performReplacePhotoURL]\n%@: %@\n%@", [self className], [obj valueForKey:KeyUser], [obj valueForKey:KeyText], photoURL);
#endif //defined(_D_E_B_U_G_)
					NSMutableDictionary *item = [NSMutableDictionary dictionaryWithDictionary:object];
					[self cellSize:item];
					[[_timeline content] replaceObjectAtIndex:i withObject:item];
					[self queueingParsePhotoURL:item];
					doReplace = YES;
					break;
				}
				i++;
			}
		}
		if (doReplace)
		{
#if 1
			NSDictionary *rowItem = [self dictionaryOfItem];
			[self performSelectorOnMainThread:@selector(performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint:)
								   withObject:rowItem
								waitUntilDone:YES];
			[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChangedAndScrollToPoint]];
#else
			NSDictionary *rowItem = [self dictionaryOfItem];
			[self performRearrangeObjects:
			 [NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]];
			[self performSelectorOnMainThread:@selector(performScrollToPoint:)
								   withObject:rowItem
								waitUntilDone:YES];
#endif
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performReplacePhotoURL] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[object release];
	}
}

- (id)replace:(NSString *)item_id
  withService:(NSString *)service
	 photoURL:(NSString *)photoURL
{
	LOG(@"[%@ replace]", [self className]);
	id obj = nil;
	[_lockTimeline lock];
	BOOL inhibitTimeline = _inhibitTimeline;
	@try
	{
		_inhibitTimeline = YES;
		@synchronized(_timeline)
		{
			obj = [self fetchALL:item_id withService:service];
			if (obj)
			{
				NSArray *urls = [photoURL componentsSeparatedByString:ProfilePhoto];
				NSString *url = [urls objectAtIndex:0];
				if ([urls count] > 1)
				{
					url = [urls objectAtIndex:1];
				}
				obj = [NSMutableDictionary dictionaryWithDictionary:obj];
				[obj setValue:photoURL forKey:KeyImageURL];
				NSString *text = [NSString stringWithFormat:@"%@\n%@",
								  [obj valueForKey:KeyText], url];
				[obj setValue:text forKey:KeyText];
				[obj setValue:text forKey:KeyTextEx];
				[obj setValue:[NSNumber numberWithInt:-1] forKey:KeyWidth];
				[self formatText:obj
					 withService:[_controller service:service]
					 appearances:[_preferences appearances]];
			}
		}
		if (obj)
		{
			[self performSelectorOnMainThread:@selector(performReplacePhotoURL:)
								   withObject:[obj retain]
								waitUntilDone:YES];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ replace] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		if (!inhibitTimeline)
		{
			_inhibitTimeline = NO;
		}
		[_lockTimeline unlock];
	}
	if (_keyView)
	{
		[self tableView:_view flagsChanged:nil];
	}
	return [obj retain];
}

- (void)performDeleteTimeline:(NSDictionary *)object
{
	LOG(@"[%@ performDeleteTimeline]", [self className]);
	@try
	{
		BOOL doDelete = NO;
		@synchronized(_timeline)
		{
			NSString *item_id = [object valueForKey:KeyId];
			NSString *service = [object valueForKey:KeyService];
			int count = [[_timeline content] count];
			int i = 0;
			while (i < count)
			{
				id obj = [[_timeline content] objectAtIndex:i];
				if ([[obj valueForKey:KeyId] isEqualToString:item_id] &&
					[[obj valueForKey:KeyService] isEqualToString:service])
				{
					[[_timeline content] removeObjectAtIndex:i];
					doDelete = YES;
					break;
				}
				i++;
			}
		}
		if (doDelete)
		{
#if 1
			NSDictionary *rowItem = [self dictionaryOfItem];
			[self performSelectorOnMainThread:@selector(performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint:)
								   withObject:rowItem
								waitUntilDone:YES];
			[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChangedAndScrollToPoint]];
#else
			NSDictionary *rowItem = [self dictionaryOfItem];
			[self performRearrangeObjects:
			 [NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]];
			[self performSelectorOnMainThread:@selector(performScrollToPoint:)
								   withObject:rowItem
								waitUntilDone:YES];
#endif
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performDeleteTimeline] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[object release];
	}
}

- (BOOL)deleteTimeline:(NSString *)item_id
		   withService:(NSString *)service
{
	LOG(@"[%@ deleteTimeline]", [self className]);
	id obj = nil;
	[_lockTimeline lock];
	BOOL inhibitTimeline = _inhibitTimeline;
	@try
	{
		_inhibitTimeline = YES;
		@synchronized(_timeline)
		{
			obj = [self fetchALL:item_id withService:service];
		}
		if (obj)
		{
			[self performSelectorOnMainThread:@selector(performDeleteTimeline:)
								   withObject:[obj retain]
								waitUntilDone:YES];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ deleteTimeline] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		if (!inhibitTimeline)
		{
			_inhibitTimeline = NO;
		}
		[_lockTimeline unlock];
	}
	if (_keyView)
	{
		[self tableView:_view flagsChanged:nil];
	}
	return (obj != nil);
}

- (float)withOfTimeline
{
	return [_columnText width];
}

- (void)setDateWidth
{
	@try
	{
		NSDictionary *appearances = [_preferences appearances];
		NSDictionary *fonts = [appearances valueForKey:AppearanceFont];
		NSFont *font = [fonts valueForKey:AppearanceDate];
		NSMutableParagraphStyle *paragraph = [[[NSMutableParagraphStyle alloc] init] autorelease];
		//[paragraph  setAlignment:NSJustifiedTextAlignment];
		[paragraph  setAlignment:NSCenterTextAlignment];
		TextFieldCell *cell = [[TextFieldCell alloc] init];
		[cell setAttributedStringValue:
		 [[[NSAttributedString alloc]
		   initWithString:@"88/88\n88:88"
		   attributes:[NSDictionary dictionaryWithObjectsAndKeys:
					   font, NSFontAttributeName,
					   paragraph, NSParagraphStyleAttributeName,
					   nil]] autorelease]];
		float width = [cell cellSizeForBounds:NSMakeRect(0, 0, 2000, 2000)].width;
		float mod = modff(width, &width);
		LOG(@"[%@ setDateWidth]\nwidth = %f\nmod = %f\n%@", [self className], width, mod, font);
		if (mod > 0)
		{
			width += 1.0;
		}
		float diff = width - [_columnDate width];
		if (diff > 0)
		{
			[_columnText setWidth:[_columnText width] - diff];
		}
		[_columnDate setWidth:width];
		if (diff < 0)
		{
			[_columnText setWidth:[_columnText width] - diff];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ setDateWidth] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)performReformat
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSDate *date = [[NSDate dateWithTimeIntervalSinceNow:0] retain];
	BOOL rearrange = NO;
	BOOL inhibit = _inhibitTimeline;
	_inhibitTimeline = YES;
	@try
	{
		[_lockTimeline lock];
		@try
		{
			NSDictionary *appearances = [_preferences appearances];
			int count = [[_timeline content] count];
			@synchronized(_timeline)
			{
				//LOG(@"[%@ performReformat]\n%@", [self className], [_preferences appearances]);
				int i = 0;
				while (i < count)
				{
					NSMutableDictionary *obj = [NSMutableDictionary dictionaryWithDictionary:
												[[_timeline content] objectAtIndex:i]];
					[self formatText:obj
						 withService:[_controller service:[obj valueForKey:KeyService]]
						 appearances:appearances];
					[obj setValue:[NSNumber numberWithInt:-1] forKey:KeyWidth];
					[[_timeline content] replaceObjectAtIndex:i withObject:obj];
					i++;
				}
				if (count > 0)
				{
					rearrange = YES;
				}
			}
			[self performSelectorOnMainThread:@selector(setDateWidth)
								   withObject:nil
								waitUntilDone:YES];
			if (rearrange)
			{
#if 1
				NSDictionary *rowItem = [self dictionaryOfItem];
				[self performSelectorOnMainThread:@selector(performRearrangeObjectsAndNoteNumberOfRowsChangedAndScrollToPoint:)
									   withObject:rowItem
									waitUntilDone:YES];
				[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChangedAndScrollToPoint]];
#else
				NSDictionary *rowItem = [self dictionaryOfItem];
				[self performSelectorOnMainThread:@selector(performRearrangeObjects:)
									   withObject:[NSNumber numberWithBool:[_preferences generalKeepSelectedItem]]
									waitUntilDone:YES];
				[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:WaitNoteNumberOfRowsChanged]];
				[self performSelectorOnMainThread:@selector(performNoteNumberOfRowsChanged:)
									   withObject:nil
									waitUntilDone:YES];
				[self performSelectorOnMainThread:@selector(performScrollToPoint:)
									   withObject:rowItem
									waitUntilDone:YES];
#endif
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ performReformat] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
		@finally
		{
			[_lockTimeline unlock];
		}
		if (rearrange && _keyView)
		{
			[self tableView:_view flagsChanged:nil];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performReformat] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		_inhibitTimeline = inhibit;
	}
#if 0 //defined(_D_E_B_U_G_)
	int count = [[_timeline content] count];
	NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];
	LOG(@"[%@ performReformat]\n%d: %f / %f", [self className], count, interval, interval / (NSTimeInterval)count);
#endif //defined(_D_E_B_U_G_)
	[date release];
	[pool release];
	[NSThread exit];
}

- (void)reformat
{
	[NSThread detachNewThreadSelector:@selector(performReformat)
							 toTarget:self
						   withObject:nil];
}

- (NSArray *)selectedObjects
{
	NSArray* selected = nil;
	[_lockTimeline lock];
	@try
	{
		@synchronized(_timeline)
		{
			selected = [_timeline selectedObjects];
			if ([selected count] <= 0)
			{
				selected = nil;
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ selectedObjects] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[_lockTimeline unlock];
	}
	return selected;
}

- (void)setSelectedItem:(NSString *)item_id
		withService:(NSString *)service
{
	//[_lockTimeline lock];
	@try
	{
		//@synchronized(_timeline)
		{
			[self performSetSelectedItem:item_id withService:service];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ setSelected] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		//[_lockTimeline unlock];
	}
}

- (void)setTitle:(NSString *)title
{
	[_panel setTitle:title];
}

- (NSPredicate *)buildPredicate
{
	NSPredicate *predicate = nil;
	if (_predicateService && _predicateUser)
	{
		predicate = [NSCompoundPredicate andPredicateWithSubpredicates:
					 [NSArray arrayWithObjects:
					  _predicateService,
					  _predicateUser,
					  nil]];
	}
	else if (_predicateService)
	{
		predicate = _predicateService;
	}
	else if (_predicateUser)
	{
		predicate = _predicateUser;
	}
	NSPredicate *segment = nil;
	switch (_selectedSegment)
	{
		case 1:
			segment = [NSPredicate predicateWithFormat:
					   @"(%K == %@)", KeyCategory, KindReply];
			break;
		case 2:
			segment = [NSPredicate predicateWithFormat:
					   @"(%K == %@)", KeyCategory, KindDM];
			break;
	}
	if (segment)
	{
		if (predicate)
		{
			predicate = [NSCompoundPredicate andPredicateWithSubpredicates:
						 [NSArray arrayWithObjects:
						  predicate, segment, nil]];
		}
		else
		{
			predicate = segment;
		}
	}
	if (_predicateSearch)
	{
		if (predicate)
		{
			predicate = [NSCompoundPredicate andPredicateWithSubpredicates:
						 [NSArray arrayWithObjects:
						  predicate, _predicateSearch, nil]];
		}
		else
		{
			predicate = _predicateSearch;
		}
	}
	return predicate;
}

- (void)setFilterPredicate:(NSPredicate *)predicate
{
	[_timeline setFilterPredicate:predicate];
}

- (void)setStatusBar:(NSString *)status
{
	[_statusBar setStringValue:status];
}

- (NSDictionary *)predicate:(id)obj
				 withColumn:(int)column
{
	//LOG(@"[%@ predicate]", [self className]);
	NSPredicate *predicate = nil;
	NSString *status = @"";
	NSString *title = nil;
	@try
	{
		int modifiers = [_controller keyModifiers];
		if (column == 0)
		{
			NSString *channel = [obj valueForKey:KeyChannel];
			if ((modifiers == NSAlternateKeyMask) &&
				(channel && ![channel isEqualToString:@""]))
			{
				predicate = [NSPredicate predicateWithFormat:
							 @"(%K == %@) AND (%K == %@)",
							 KeyService,
							 [obj valueForKey:KeyService],
							 KeyChannel,
							 [obj valueForKey:KeyChannel]];
				title = [NSString stringWithFormat:@"%@ [%@]",
						 [obj valueForKey:KeyService],
						 [channel substringWithRange:NSMakeRange(2, [channel length] - 2)]];
				status = [NSString stringWithFormat:FILTER_CHANNEL, title];
			}
			else
			{
				predicate = [NSPredicate predicateWithFormat:
							 @"%K == %@",
							 KeyService,
							 [obj valueForKey:KeyService]];
				title = [NSString stringWithFormat:@"%@",
						 [obj valueForKey:KeyService]];
				status = [NSString stringWithFormat:FILTER_SERVICE, title];
			}
			if (_predicateService &&
				[[predicate predicateFormat] isEqualToString:
				 [_predicateService predicateFormat]])
			{
				predicate = nil;
				title = _titleService;
			}
		}
		else if (column == 1)
		{
			NSString *user = [obj valueForKey:KeyUser];
			NSString *comment = [obj valueForKey:KeyComment];
			NSString *reply = nil;
			NSString *prefix = @"";
			if ((modifiers == NSAlternateKeyMask) &&
				comment && ![comment isEqualToString:@""] &&
				[[obj valueForKey:KeyService] isEqualToString:Jaiku])
			{
				NSString *item_id = [obj valueForKey:KeyId];
				item_id = [[item_id componentsSeparatedByString:@"-"] objectAtIndex:0];
				title = [comment substringWithRange:NSMakeRange(4, [comment length] - 5)];
				if ([title length] > 32)
				{
					title = [title substringWithRange:NSMakeRange(0, 32)];
					title = [title stringByAppendingString:@"…"];
				}
				prefix = FILTER_THREAD;
				predicate = [NSPredicate predicateWithFormat:
							 @"%K beginswith %@",
							 KeyId,
							 item_id];
				//LOG(@"[%@ predicate] predicate:\n%@", [self className], predicate);
				//LOG(@"[%@ predicate] title:\n%@", [self className], title);
			}
			if (!predicate && user && ![user isEqualToString:@""])
			{
				prefix = FILTER_USER;
				NSString *text = [obj valueForKey:KeyText];
				NSString *inReplyUser = [obj valueForKey:KeyInReplyUser];
				NSString *text_user = [[text componentsSeparatedByString:@" "] objectAtIndex:0];
				text_user = [[text_user componentsSeparatedByString:@"　"] objectAtIndex:0];
				NSString *atmark = @"";
				if ([text_user length] > 0)
				{
					atmark = [text_user substringWithRange:NSMakeRange(0, 1)];
				}
				NSString *inReplyChannel = @"";
				if (inReplyUser && [inReplyUser length] > 0)
				{
					inReplyChannel = [inReplyUser substringWithRange:NSMakeRange(0, 1)];
				}
				if ([atmark isEqualToString:@"@"] &&
					![inReplyUser isEqualTo:@""] &&
					![inReplyChannel isEqualTo:@"#"] &&
					![inReplyChannel isEqualTo:@"!"])
				{
					reply = text_user;
					predicate = [NSPredicate predicateWithFormat:
								 @"(%K == %@) OR (%K contains[c] %@) OR (%K == %@) OR (%K == %@)",
								 KeyUser,
								 [obj valueForKey:KeyUser],
								 KeyTextNotify,
								 [obj valueForKey:KeyUser],
								 KeyUser,
								 [obj valueForKey:KeyInReplyUser],
								 KeyUser,
								 reply];
				}
				else if ([atmark isEqualToString:@"@"])
				{
					reply = text_user;
					predicate = [NSPredicate predicateWithFormat:
								 @"(%K == %@) OR (%K contains[c] %@) OR (%K == %@)",
								 KeyUser,
								 [obj valueForKey:KeyUser],
								 KeyTextNotify,
								 [obj valueForKey:KeyUser],
								 KeyUser,
								 reply];
				}
				else if (![inReplyUser isEqualTo:@""] &&
						 ![inReplyChannel isEqualTo:@"#"] &&
						 ![inReplyChannel isEqualTo:@"!"])
				{
					reply = [obj valueForKey:KeyInReplyUser];
					predicate = [NSPredicate predicateWithFormat:
								 @"(%K == %@) OR (%K contains[c] %@) OR (%K == %@)",
								 KeyUser,
								 [obj valueForKey:KeyUser],
								 KeyTextNotify,
								 [obj valueForKey:KeyUser],
								 KeyUser,
								 reply];
				}
				else
				{
					predicate = [NSPredicate predicateWithFormat:
								 @"(%K == %@) OR (%K contains[c] %@)",
								 KeyUser,
								 [obj valueForKey:KeyUser],
								 KeyTextNotify,
								 [obj valueForKey:KeyUser]];
				}
			}
			if (_predicateUser && predicate &&
				[[predicate predicateFormat] isEqualToString:
				 [_predicateUser predicateFormat]])
			{
				predicate = nil;
				title = _titleUser;
			}
			else if (!title)
			{
				//LOG(@"[%@ performClicked] predicate:\n%@", [self className], predicate);
				//LOG(@"[%@ performClicked] reply:\n%@", [self className], reply);
				if (reply && ![reply isEqualToString:user])
				{
					title = [NSString stringWithFormat:@"%@ + %@", user, reply];
				}
				else
				{
					title = [NSString stringWithFormat:@"%@", user];
				}
			}
			status = [NSString stringWithFormat:@"%@%@", prefix, title];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ predicate] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return [NSDictionary dictionaryWithObjectsAndKeys:
			predicate ? predicate : (id)[NSNull null], Predicate,
			title ? title : (id)[NSNull null], Title,
			status ? status : (id)[NSNull null], Status,
			nil];
}

- (void)indicatePredicate:(NSPoint)point
{
	//LOG(@"[%@ indicatePredicate]", [self className]);
	@try
	{
		NSString *status = @"";
		NSRect bounds = [_scroll bounds];
		float bx = bounds.origin.x;
		float bw = bx + bounds.size.width;
		float by = bounds.origin.y;
		float bh = by + bounds.size.height;
		NSPoint pt = [_scroll convertPoint:point fromView:nil];
		float x = pt.x;
		float y = pt.y;
		LOG(@"[%@ indicatePredicate]\n%f, %f\n%f, %f, %f, %f", [self className], x, y, bx, by, bw, bh);
		if ((x >= bx) && (x < bw) && (y >= by) && (y < bh))
		{
			bounds = [_view bounds];
			bx = bounds.origin.x;
			bw = bx + bounds.size.width;
			by = bounds.origin.y;
			bh = by + bounds.size.height;
			pt = [_view convertPoint:point fromView:nil];
			x = pt.x;
			y = pt.y;
			//LOG(@"[%@ indicatePredicate]\n%f, %f\n%f, %f, %f, %f", [self className], x, y, bx, by, bw, bh);
			if ((x >= bx) && (x < bw) && (y >= by) && (y < bh))
			{
				int row = [_view rowAtPoint:pt];
				int column = [_view columnAtPoint:pt];
				if ((row >= 0) && (column >= 0) && (column < 2))
				{
					id obj = [[_timeline arrangedObjects] objectAtIndex:row];
				//	int index = [[obj valueForKey:KeyIndex] intValue];
				//	if (index >= 0)
				//	{
				//		obj = [[_timeline content] objectAtIndex:index];
						obj = [self predicate:obj withColumn:column];
						NSPredicate *predicate = [obj valueForKey:Predicate];
						if ([predicate isKindOfClass:[NSNull class]])
						{
							status = [NSString stringWithFormat:
									  RELEASE_FILTERS, [obj valueForKey:Status]];
						}
						else
						{
							status = [NSString stringWithFormat:
									  FILTERS_IN, [obj valueForKey:Status]];
						}
				//	}
				}
			}
		}
		if (!_textStatusBar || ![status isEqualToString:_textStatusBar])
		{
			if (_textStatusBar)
			{
				[_textStatusBar release];
			}
			_textStatusBar = [status retain];
			[self setStatusBar:status];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ indicatePredicate] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)performMouseMoved:(NSEvent *)theEvent
{
	LOG(@"[%@ performMouseMoved]\n%@", [self className], theEvent);
	@try
	{
		@synchronized(_timeline)
		{
			NSPoint point;
			if (theEvent)
			{
				point = [theEvent locationInWindow];
			}
			else
			{
				point = [[_view window] convertScreenToBase:[NSEvent mouseLocation]];
			}
			LOG(@"[%@ performMouseMoved]\n%f, %f", [self className], point.x, point.y);
			[self indicatePredicate:point];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performMouseMoved] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)performClicked:(NSNumber *)number
{
	LOG(@"[%@ performClicked]", [self className]);
	@try
	{
		NSArray* selected = nil;
		id obj = nil;
		BOOL doPredicate = NO;
		NSPredicate *predicate = nil;
		NSString *title = TIMELINE;
		//[_lockTimeline lock];
		@try
		{
			@synchronized(_timeline)
			{
				selected = [_timeline selectedObjects];
				if ([selected count] > 0)
				{
					int column = [number intValue];
					obj = [self predicate:[selected objectAtIndex:0]
							   withColumn:column];
					LOG(@"[%@ performClicked]\n%@", [self className], predicate);
					predicate = [obj valueForKey:Predicate];
					if (column == 0)
					{
						doPredicate = YES;
						if (!predicate || [predicate isKindOfClass:[NSNull class]])
						{
							[_predicateService release];
							_predicateService = nil;
							[_titleService release];
							_titleService = nil;
						}
						else
						{
							_predicateService = [predicate retain];
							_titleService = [[obj valueForKey:Title] retain];
						}
					}
					else //if (column == 1)
					{
						doPredicate = YES;
						if (!predicate || [predicate isKindOfClass:[NSNull class]])
						{
							[_predicateUser release];
							_predicateUser = nil;
							if (_titleUser)
							{
								[_titleUser release];
								_titleUser = nil;
							}
						}
						else
						{
							_predicateUser = [predicate retain];
							if (_titleUser)
							{
								[_titleUser release];
							}
							_titleUser = [[obj valueForKey:Title] retain];
						}
					}
				}
			}
			if (doPredicate)
			{
				if (_titleService && _titleUser)
				{
					title = [title stringByAppendingFormat:@" - %@ / %@",
							 _titleService, _titleUser];
				}
				else if (_titleService)
				{
					title = [title stringByAppendingFormat:@" - %@", _titleService];
				}
				else if (_titleUser)
				{
					title = [title stringByAppendingFormat:@" - %@", _titleUser];
				}
#if 0
				if (_predicateService && _predicateUser)
				{
					predicate = [NSCompoundPredicate andPredicateWithSubpredicates:
								 [NSArray arrayWithObjects:
								  _predicateService,
								  _predicateUser,
								  nil]];
				}
				else if (_predicateService)
				{
					predicate = _predicateService;
				}
				else if (_predicateUser)
				{
					predicate = _predicateUser;
				}
				else
				{
					predicate = nil;
				}
				if (predicate)
				{
					LOG(@"[%@ performClicked]\n%@", [self className], [predicate predicateFormat]);
				}
#endif
				[self setFilterPredicate:[self buildPredicate]];
				[self setTitle:title];
				if (obj)
				{
					//LOG(@"[%@ performClicked] setSelectedObject", [self className]);
					[self setSelectedObject:obj];
				}
			}
			//LOG(@"[%@ performClicked] done", [self className]);
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ performClicked] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
		@finally
		{
			//[_lockTimeline unlock];
		}
		if (doPredicate && _keyView)
		{
			[self tableView:_view flagsChanged:nil];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performClicked] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (IBAction)onClicked:(id)sender
{
	//LOG(@"[%@ onClicked]", [self className]);
	int selectedRow = [_view selectedRow];
	if (selectedRow >= 0)
	{
		int column = [_view columnAtPoint:
					  [[_view window] convertScreenToBase:[NSEvent mouseLocation]]];
		if ((column == 0) || (column == 1))
		{
			[self performClicked:[[NSNumber alloc] initWithInt:column]];
		}
	}
}

- (void)performDoubleClicked
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSString *action = nil;
	[_lockTimeline lock];
	@try
	{
		@synchronized(_timeline)
		{
			NSArray* selected = [_timeline selectedObjects];
			if ([selected count] > 0)
			{
				id obj = [selected objectAtIndex:0];
				action = [NSString stringWithFormat:@"%@ %@",
						  [obj valueForKey:KeyService],
						  [obj valueForKey:KeyId]];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performDoubleClicked] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[_lockTimeline unlock];
	}
	if (action)
	{
		[_controller doClickAction:action];
	}
	[pool release];
	[NSThread exit];
}

- (IBAction)onDoubleClicked:(id)sender
{
	//LOG(@"[%@ onDoubleClicked]\n%@", [self className], sender);
	[NSThread detachNewThreadSelector:@selector(performDoubleClicked)
							 toTarget:self
						   withObject:nil];
}

- (void)releaseFiltering
{
	if (_predicateService)
	{
		[_predicateService release];
		_predicateService = nil;
	}
	if (_predicateUser)
	{
		[_predicateUser release];
		_predicateUser = nil;
	}
	[self setFilterPredicate:nil];
	[self setTitle:TIMELINE];
}

- (void)performCopy
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSString *action = nil;
	[_lockTimeline lock];
	@try
	{
		@synchronized(_timeline)
		{
			NSArray* selected = [_timeline selectedObjects];
			if ([selected count] > 0)
			{
				id obj = [selected objectAtIndex:0];
				NSString* txt = [NSString stringWithFormat:@"@%@: %@",
								 [obj valueForKey:KeyUser],
								 [obj valueForKey:KeyText]];
				LOG(@"[%@ performCopy]\n%@", [self className], txt);
				NSPasteboard* pb = [NSPasteboard generalPasteboard];
				[pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
				[pb setString:txt forType:NSStringPboardType];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performCopy] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[_lockTimeline unlock];
	}
	if (action)
	{
		[_controller doClickAction:action];
	}
	[pool release];
	[NSThread exit];
}

#pragma mark NSObject Delegates

- (void)awakeFromNib
{
	//LOG(@"[%@ awakeFromNib]", [self className]);
	NSRect frame = [_panel frame];
	NSString *frameTimeline = [_preferences frameTimeline];
	if (!frameTimeline)
	{
		NSRect screen = [[NSScreen mainScreen] frame];
		frame.origin.x = (screen.size.width - frame.size.width) / 2 + screen.origin.x;
		frame.origin.y = (screen.size.height - frame.size.height) / 4 * 2 + screen.origin.y;
		[_panel setFrame:frame display:YES];
	}
	
	NSArray *columns = [_view tableColumns];
	NSTableColumn *column = [columns objectAtIndex:0];
	[[column dataCell] setImageAlignment:NSImageAlignTop];
	column = [columns objectAtIndex:1];
	[[column dataCell] setImageAlignment:NSImageAlignTop];
	_columnIcon = [columns objectAtIndex:1];
	_columnText = [columns objectAtIndex:2];
	_columnDate = [columns objectAtIndex:3];
	_row_min = [_columnIcon width];
	[self setDateWidth];
	
	[_timeline setSortDescriptors:[NSArray arrayWithObjects:
								   [[[NSSortDescriptor alloc] initWithKey:KeySort ascending:NO] autorelease],
//								   [[[NSSortDescriptor alloc] initWithKey:KeyCreatedAt ascending:NO] autorelease],
								   nil]];
	[_view setDoubleAction:@selector(onDoubleClicked:)];
	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
						 LogoTwitter, KeyUserProfile,
						 [NSImage imageNamed:LogoTwitter], KeyProfileImage,
						 nil]];
	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
						 LogoJaiku, KeyUserProfile,
						 [NSImage imageNamed:LogoJaiku], KeyProfileImage,
						 nil]];
	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
						 LogoTumblr, KeyUserProfile,
						 [NSImage imageNamed:LogoTumblr], KeyProfileImage,
						 nil]];
	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
						 LogoWassr, KeyUserProfile,
						 [NSImage imageNamed:LogoWassr], KeyProfileImage,
						 nil]];
//	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
//						 LogoNowa, KeyUserProfile,
//						 [NSImage imageNamed:LogoNowa], KeyProfileImage,
//						 nil]];
	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
						 LogoIdentica, KeyUserProfile,
						 [NSImage imageNamed:LogoIdentica], KeyProfileImage,
						 nil]];
//	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
//						 LogoJisko, KeyUserProfile,
//						 [NSImage imageNamed:LogoJisko], KeyProfileImage,
//						 nil]];
//	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
//						 LogoChuitter, KeyUserProfile,
//						 [NSImage imageNamed:LogoChuitter], KeyProfileImage,
//						 nil]];
	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
						 LogoFriendFeed, KeyUserProfile,
						 [NSImage imageNamed:LogoFriendFeed], KeyProfileImage,
						 nil]];
	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
						 LogoFaceBook, KeyUserProfile,
						 [NSImage imageNamed:LogoFaceBook], KeyProfileImage,
						 nil]];
	[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
						 Mail, KeyUserProfile,
						 [NSImage imageNamed:Mail], KeyProfileImage,
						 nil]];
	Service *service = [_controller service:Twitter];
	int const*table = [AfficheurPanel tableOfJaikuIcon];
	while (*table)
	{
		NSString *name = [service imageNameJaiku:*table];
		NSImage *image = [NSImage imageNamed:name];
		if (image)
		{
			[_profile addObject:[NSDictionary dictionaryWithObjectsAndKeys:
								 name, KeyUserProfile,
								 image, KeyProfileImage,
								 nil]];
		}
		table++;
	}
	LOG(@"[%@ awakeFromNib] %d", [self className], [[_view window] acceptsMouseMovedEvents]);
	[[_view window] setAcceptsMouseMovedEvents:YES];
	_toolbar = [[NSToolbar alloc] initWithIdentifier:@"AfficheurTimelineToolbar"];
	[_toolbar setDelegate:self];
	[_toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
	[_toolbar setSizeMode:NSToolbarSizeModeSmall];
	if ([_preferences timelineVisibleToolbar])
	{
		[_toolbar setVisible:YES];
		[_controller setToolbarMenuTitle:HideToolbar];
	}
	else
	{
		[_toolbar setVisible:NO];
		[_controller setToolbarMenuTitle:ShowToolbar];
	}
	[_panel setToolbar:_toolbar];
	[_panel setShowsToolbarButton:NO];
}

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
	SEL action = [menuItem action];
	if (action == @selector(onReleaseFiltering:))
	{
		return _predicateService || _predicateUser;
	}
	return YES;
}

- (void)applicationWillTerminate
{
}

#pragma mark NSTableView Delegates

- (float)tableView:(NSTableView *)tableView
		heightOfRow:(int)row
{
//	float height = _row_min;
//	@try
//	{
	float height = [[[[_timeline arrangedObjects] objectAtIndex:row]
					 valueForKey:KeyHeight] floatValue];
//	}
//	@catch (NSException *exception)
//	{
//		height = _row_min;
//	}
	if (height < _row_min)
	{
		height = _row_min;
	}
	return height + 2;
}

- (void)tableView:(NSTableView *)aTableView
  willDisplayCell:(id)aCell
   forTableColumn:(NSTableColumn *)aTableColumn
			  row:(int)rowIndex
{
	//@synchronized(_timeline)
	{
		NSDictionary *item = [[_timeline arrangedObjects] objectAtIndex:rowIndex];
		if (item)
		{
//			@try
//			{
				int selected = [aTableView selectedRow];
				NSDictionary *appearances = [_preferences appearances];
				NSDictionary *colors = [appearances valueForKey:AppearanceColor];
				NSDictionary *color = [self appearanceColor:item withColors:colors];
				if ((selected < 0) || (selected != rowIndex))
				{
					[aCell setDrawsBackground:YES];
					[aCell setBackgroundColor:[color valueForKey:AppearanceBackground]];
					if ([aTableColumn isEqual:_columnText])
					{
						//LOG(@"[%@ willDisplayCell] text", [self className]);
						[aCell setAttributedStringValue:[item valueForKey:KeyTextDisplay]];
					}
					else if ([aTableColumn isEqual:_columnDate])
					{
						//LOG(@"[%@ willDisplayCell] date", [self className]);
						[aCell setAttributedStringValue:[item valueForKey:KeyDateDisplay]];
					}
				}
				else
				{
					[aCell setDrawsBackground:NO];
					if ([aTableColumn isEqual:_columnText])
					{
						//LOG(@"[%@ willDisplayCell] text", [self className]);
						[aCell setAttributedStringValue:[item valueForKey:KeyTextDisplaySelected]];
					}
					else if ([aTableColumn isEqual:_columnDate])
					{
						//LOG(@"[%@ willDisplayCell] date", [self className]);
						[aCell setAttributedStringValue:[item valueForKey:KeyDateDisplaySelected]];
					}
				}
				if ([aTableColumn isEqual:_columnIcon])
				{
					NSString *userProfile = [item valueForKey:KeyUserProfile];
					if (![userProfile isEqualToString:@""])
					{
						id profileImage = [self profileImage:userProfile];
						if (!profileImage && [self isDisplay])
						{
							//LOG(@"[%@ willDisplayCell] queueing\n%@", [self className], userProfile);
							[self queueingUserProfile:userProfile
										   withUnlock:YES];
						}
					}
				}
				else if ([aTableColumn isEqual:_columnText])
				{
					NSString *image_url = [item valueForKey:KeyIcon];
					if (image_url &&
						![image_url isKindOfClass:[NSNull class]] &&
						![image_url isEqualToString:@""])
					{
						id image = [item valueForKey:KeyIconImage];
						if (!image)
						{
							id photo = [self profileImage:image_url];
							if (!photo && [self isDisplay])
							{
								//LOG(@"[%@ willDisplayCell] queueing\n%@", [self className], photo_url);
								[self queueingUserProfile:image_url
											   withUnlock:YES];
							}
						}
					}
					image_url = [item valueForKey:KeySourceIcon];
					if (image_url &&
						![image_url isKindOfClass:[NSNull class]] &&
						![image_url isEqualToString:@""])
					{
						id image = [item valueForKey:KeySourceIconImage];
						if (!image)
						{
							id photo = [self profileImage:image_url];
							if (!photo && [self isDisplay])
							{
								//LOG(@"[%@ willDisplayCell] queueing\n%@", [self className], photo_url);
								[self queueingUserProfile:image_url
											   withUnlock:YES];
							}
						}
					}
					image_url = [item valueForKey:KeyImageURL];
					if ([_preferences generalInlineImage] &&
						image_url &&
						![image_url isKindOfClass:[NSNull class]] &&
						![image_url isEqualToString:@""])
					{
						id image = [item valueForKey:KeyImage];
						if (!image)
						{
							id photo = [self profileImage:image_url];
							if (!photo && [self isDisplay])
							{
								//LOG(@"[%@ willDisplayCell] queueing\n%@", [self className], photo_url);
								[self queueingUserProfile:image_url
											   withUnlock:YES];
							}
						}
					}
				}
//			}
//			@catch (NSException *exception)
//			{
//				EXPLOG(@"[%@ willDisplayCell] EXCEPTION\n%@: %@\n%@", [self className], [exception name], [exception reason], [item valueForKey:KeyService]);
//			}
//			@finally
//			{
//				[item release];
//			}
		}
	}
}

- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
	//LOG(@"[%@ tableViewSelectionDidChange]\n%@", [self className], aNotification);
	int row = [_view selectedRow];
	if (row >= 0)
	{
		_selectedRow = YES;
		if ([_preferences generalKeepCursorPosition])
		{
			NSRect rect = [_view rectOfRow:row];
			NSClipView *superview = (NSClipView *)[_view superview];
			[superview scrollToPoint:NSMakePoint(rect.origin.x, rect.origin.y)];
			[_scroll reflectScrolledClipView:superview];
		}
	}
	else
	{
		_selectedRow = NO;
	}
}

- (void)tableView:(NSTableView *)aTableView
			keyUp:(NSEvent *)theEvent
{
	//LOG(@"[%@ keyUp]", [self className]);
	[self performMouseMoved:nil];
}

- (void)tableView:(NSTableView *)aTableView
	   mouseMoved:(NSEvent *)theEvent
{
	//LOG(@"[%@ mouseMoved]", [self className]);
	[self performMouseMoved:theEvent];
}

- (void)tableView:(NSTableView *)aTableView
	   flagsChanged:(NSEvent *)theEvent
{
	//LOG(@"[%@ flagsChanged] %d", [self className], _keyView);
	[self performMouseMoved:nil];
}

- (void)tableView:(NSTableView *)aTableView
	 scrollWheel:(NSEvent *)theEvent
{
	//LOG(@"[%@ scrollWheel] %d", [self className], _keyView);
	if (_keyView)
	{
		[self performMouseMoved:nil];
	}
}

#pragma mark NSWindow Delegates

- (BOOL)windowShouldClose:(id)window
{
	LOG(@"[%@ windowShouldClose]", [self className]);
	if (![[_controller panel] isVisible] && ![_preferences generalDockIcon])
	{
		[_controller hideAfficheur];
	}
	return YES;
}

- (void)windowDidResize:(NSNotification *)notification
{
	LOG(@"[%@ windowDidResize]", [self className]);
	//[_view noteNumberOfRowsChanged];
	[self queueingCellSize:YES];
	[self unlockCellSize];
	LOG(@"[%@ windowDidResize] done", [self className]);
}

- (void)windowDidBecomeKey:(NSNotification *)notification
{
	LOG(@"[%@ windowDidBecomeKey]\n%@", [self className], notification);
	_keyView = YES;
	[self tableView:_view flagsChanged:nil];
}

- (void)windowDidResignKey:(NSNotification *)notification
{
	LOG(@"[%@ windowDidResignKey]\n%@", [self className], notification);
	_keyView = NO;
	[self setStatusBar:@""];
}

#pragma mark NSToolbar Delegates

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
{
	return [NSArray arrayWithObjects:
			ToolbarSegment,
			NSToolbarFlexibleSpaceItemIdentifier,
			ToolbarSearch,
			nil];
}

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
{
	return [self toolbarDefaultItemIdentifiers:toolbar];
}

- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
{
	return [self toolbarDefaultItemIdentifiers:toolbar];
}

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
	 itemForItemIdentifier:(NSString *)itemIdentifier
 willBeInsertedIntoToolbar:(BOOL)flag
{
	LOG(@"[%@ toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar]\n%@:%d", [self className], itemIdentifier, flag);
	NSToolbarItem *item = nil;
	if ([itemIdentifier isEqualToString:ToolbarSegment])
	{
		item = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
		[item setTarget:self];
		[item setLabel:itemIdentifier];
		[item setView:_segmentView];
		[item setMinSize:[_segmentView bounds].size];
		[item setMaxSize:[_segmentView bounds].size];
		[_segmentBtn setSelectedSegment:_selectedSegment];
	}
	else if ([itemIdentifier isEqualToString:ToolbarSearch])
	{
		item = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
		[item setTarget:self];
		[item setLabel:itemIdentifier];
		[item setView:_searchView];
		[item setMinSize:[_searchView bounds].size];
		[item setMaxSize:[_searchView bounds].size];
	}
	return item;
}

- (void)doPredicateSearch:(NSString *)searchString
{
	LOG(@"[%@ doPredicateSearch]\n%@", [self className], searchString);
	@try
	{
		if (_searchString)
		{
			[_searchString release];
		}
		if ([searchString isEqualToString:@""])
		{
			_searchString = nil;
		}
		else
		{
			_searchString = [searchString copy];
		}
		if (_predicateSearch)
		{
			[_predicateSearch release];
		}
		if (_searchString)
		{
			switch (_searchTag)
			{
				case 2:	//User
					_predicateSearch = [NSPredicate predicateWithFormat:
										@"(%K contains[c] %@) OR "\
										@"(%K contains[c] %@)",
										KeyUser, _searchString,
										KeyName, _searchString ];
					break;
				case 3:	//Text
					_predicateSearch = [NSPredicate predicateWithFormat:
										@"(%K contains[c] %@)",
										KeyTextEx, _searchString ];
					break;
				case 4:	//Channel
					_predicateSearch = [NSPredicate predicateWithFormat:
										@"(%K contains[c] %@)",
										KeyChannel, _searchString ];
					break;
				default://All
					_predicateSearch = [NSPredicate predicateWithFormat:
										@"(%K contains[c] %@) OR "\
										@"(%K contains[c] %@) OR "\
										@"(%K contains[c] %@) OR "\
										@"(%K contains[c] %@)",
										KeyUser, _searchString,
										KeyName, _searchString,
										KeyTextEx, _searchString,
										KeyChannel, _searchString ];
					break;
			}
			if (_predicateSearch)
			{
				[_predicateSearch retain];
			}
		}
		else
		{
			_predicateSearch = nil;
		}
		[self setFilterPredicate:[self buildPredicate]];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ onToolbarSearch] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (IBAction)onToolbarSegment:(id)sender;
{
	LOG(@"[%@ onToolbarSegment]\n%@", [self className], sender);
	LOG(@"[%@ onToolbarSegment]\n%d", [self className], [sender selectedSegment]);
	if (_selectedSegment != [sender selectedSegment])
	{
		_selectedSegment = [sender selectedSegment];
		[self setFilterPredicate:[self buildPredicate]];
	}
}

- (IBAction)onSearchMenu:(id)sender;
{
	LOG(@"[%@ onSearchMenu]\n%@", [self className], sender);
	LOG(@"[%@ onSearchMenu]\n%@", [self className], [sender title]);
	if (_searchTag != [sender tag])
	{
		[[[sender menu] itemWithTag:_searchTag] setState:NSOffState];
		[sender setState:NSOnState];
		_searchTag = [sender tag];
		[[_searchField cell] setPlaceholderString:[sender title]];
		if (_searchString)
		{
			[self doPredicateSearch:_searchString];
		}
	}
}

- (IBAction)onToolbarSearch:(id)sender;
{
	LOG(@"[%@ onToolbarSearch]\n%@", [self className], sender);
	@try
	{
		NSString *searchString = [sender stringValue];
		LOG(@"[%@ onToolbarSearch]\n%@", [self className], searchString);
		if (!_searchString || ![_searchString isEqualToString:searchString])
		{
			[self doPredicateSearch:searchString];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ onToolbarSearch] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (IBAction)onToolbar:(id)sender
{
	LOG(@"[%@ onToolbar]\n%@", [self className], sender);
	if ([[sender title] isEqualToString:ShowToolbar])
	{
		[_toolbar setVisible:YES];
		[_controller setToolbarMenuTitle:HideToolbar];
		[_preferences timelineSetVisibleToolbar:YES];
	}
	else
	{
		[_toolbar setVisible:NO];
		[_controller setToolbarMenuTitle:ShowToolbar];
		[_preferences timelineSetVisibleToolbar:NO];
	}
}

- (IBAction)onFiltering:(id)sender
{
	LOG(@"[%@ onFiltering]\n%@", [self className], sender);
	[_toolbar setVisible:YES];
	[_controller setToolbarMenuTitle:HideToolbar];
	[_panel makeFirstResponder:_searchField];
}

- (IBAction)onCopy:(id)sender
{
	if (_keyView && _selectedRow)
	{
		[NSThread detachNewThreadSelector:@selector(performCopy)
								 toTarget:self
							   withObject:nil];
	}
}

@end
