//
//  AfficheurController.m
//  Afficheur
//
//  Created by kichi on 08/04/05.
//  Copyright 2008 Katsuhiko Ichinose. All rights reserved.
//

#import "AfficheurController.h"
#import "AfficheurPreferences.h"
#import "AfficheurPanel.h"
#import "AfficheurTimeline.h"
#import "AfficheurService.h"
#import "AfficheurGrowl.h"
#import "HotKeyedApplication.h"
#import "KeyEquivView.h"
#import "Service.h"
#import "TinyURL.h"
#import "NSStringOgreKit.h"
#import <OgreKit/OgreKit.h>
#import "i18n.h"
#import "ServiceBitLyKey.h"

@implementation AfficheurController

NSThread* TheMainThread;

NSString	*Afficheur				= @"Afficheur";

NSString	*MenuIcon				= @"MenuIcon";
NSString	*MenuIconAlternate		= @"MenuIconAlternate";

static NSString		*KeySelector	= @"selector";
static NSString		*KeyTarget		= @"target";

//static NSString		*DomainJisko	= @"jisko.net";
//static NSString		*SessionJisko	= @"JSESSIONID";

NSString *stringRegularExpressionURLtrailer = @"0-9a-zA-Z\\-_\\.!~\\*'/\\+@";
NSString *stringURLShorten = @"(:?tinyurl\\.com|ur1\\.ca|bit\\.ly|post\\.ly|ptiturl\\.com|is\\.gd|snipr\\.com|tr\\.im|yourl\\.be|aqurl\\.info|flic\\.kr|bctiny\\.com|ustre\\.am|blip\\.fm\\/~|icio\\.us|j\\.mp|goo\\.gl|ow\\.ly|s\\.nikkei\\.com|nyti\\.ms|on\\.wsj\\.com|tcrn\\.ch|engt\\.co|gizmo\\.do|s\\.qli\\.jp|t\\.co|moi\\.st|macrumo\\.rs|dlvr\\.it)";

#pragma mark implementation AfficheurController

+ (NSString *)stringRegularExpressionURL
{
	NSString *jpn = @"℀-➾　-㏾一-龥豈-鶴！-￬";
	NSString *anc = @"0-9a-zA-Z\\-_\\.!~\\*'\\(\\)/\\?&=,:%#@";
	return [NSString stringWithFormat:@"(?:(?:http|https)://[%@%@]*[%@%@]+|%@/[%@]+)",
			jpn, anc, jpn, stringRegularExpressionURLtrailer,
			stringURLShorten, stringRegularExpressionURLtrailer];
}

- (AfficheurController *)init
{
	//LOG(@"[%@ init]", [self className]);
	TheMainThread = [NSThread currentThread];
	self = [super init];
	if (self)
	{
		_active = NO;
		_preferences = [[AfficheurPreferences alloc] init];
		_leopard = NO;
		_hotKeyRef = nil;
		_hotKeyRefTimeline = nil;
		
		SInt32 gestalt;
		if (!Gestalt(gestaltSystemVersion, &gestalt))
		{
			if (gestalt >= 0x1050)
			{
				_leopard = YES;
			}
			if (gestalt >= 0x1060)
			{
				_snowLeopard = YES;
			}
		}
		_lockedPost = NO;
		_lockPost = [[NSLock alloc] init];
		_statusItem = nil;
		_doPreferences = NO;
		_sleep = NO;
		_lockService = [[NSLock alloc] init];
		_activeApplicationName = nil;
		_appearances = nil;
		_tinyURL = [[NSMutableDictionary dictionary] retain];
//		_jiskoUser = nil;
//		_jiskoPass = nil;
	}
	return self;
}

- (void)dealloc
{
	[_preferences release];
	[_panel release];
	[_timeline release];
	[_lockPost release];
	[_lockService release];
	[_tinyURL release];
//	if (_jiskoUser)
//	{
//		[_jiskoUser release];
//	}
//	if (_jiskoPass)
//	{
//		[_jiskoPass release];
//	}
	[super dealloc];
}

- (void)unregisterHotKey
{
	if (_hotKeyRef)
	{
		[[HotKeyedApplication sharedApplication] unregisterHotKey:_hotKeyRef];
		_hotKeyRef = nil;
	}
	if (_hotKeyRefTimeline)
	{
		[[HotKeyedApplication sharedApplication] unregisterHotKey:_hotKeyRefTimeline];
		_hotKeyRefTimeline = nil;
	}
}

- (void)registerHotKey
{
	UInt32 keyCode = [_preferences generalHotKey];
	[self unregisterHotKey];
	if (keyCode != -1)
	{
		_hotKeyRef = [[HotKeyedApplication sharedApplication]
					  registerHotKey:[KeyEquivView keyCodeForKeyCode:keyCode]
					  withModifier:[KeyEquivView modifierKeyForKeyCode:keyCode]
					  target:self
					  selector:@selector(hotKey)];
	}
	keyCode = [_preferences generalHotKeyTimeline];
	if (keyCode != -1)
	{
		_hotKeyRefTimeline = [[HotKeyedApplication sharedApplication]
							  registerHotKey:[KeyEquivView keyCodeForKeyCode:keyCode]
							  withModifier:[KeyEquivView modifierKeyForKeyCode:keyCode]
							  target:self
							  selector:@selector(hotKeyTimeline)];
	}
}

- (BOOL)isLeopard
{
	return _leopard;
}

- (BOOL)isSnowLeopard
{
	return _snowLeopard;
}

- (BOOL)isURL:(NSString *)string
{
	return ([string lengthOfRegularExpression:@"(?:http|https)://"] != 0);
}

- (NSWindow *)panel
{
	return [_panel panel];
}

- (void)setPanelStringText:(NSString *)string
{
	[_panel setStringText:string];
}

- (void)insertPanelStringText:(NSString *)string
{
	[_panel insertStringText:string];
}

- (void)setPanelEnabledText:(BOOL)enabled
{
	[_panel setEnabledText:enabled];
}

- (void)setPanelEnableTextIfDisabled
{
	if (![_panel isEnabledText])
	{
		[_panel setEnabledText:YES];
		[_panel makeFirstResponderText];
	}
}

- (NSRange)selectedPanelRange
{
	return [_panel selectedRangeText];
}

- (void)setSelectedPanelRange:(NSRange)range
{
	[_panel setSelectedRangeText:range];
}

- (void)performPostFinish:(int)ngCount
{
	LOG(@"[%@ performPostFinish] %d", [self className], ngCount);
	[_panel setEnabledText:YES];
	[_panel makeFirstResponderText];
	//[_spinner setImage:nil];
	if (ngCount <= 0)
	{
		switch ([_preferences generalClearAfterPost])
		{
			case ClearAfterPostReply:
				if ([_panel inReplyTo])
				{
					[self setPanelStringText:@""];
				}
				break;
			case ClearAfterPostAllways:
				[self setPanelStringText:@""];
				break;
		}
		[self setReply:@"" withService:@"" inReplyTo:nil text:nil];
		[_panel clearJaikuIcon:self];
		if (!_doPreferences && [_preferences generalHideAfterPost])
		{
			[self hideAfficheur];
		}
	}
}

- (void)performPostOK
{
	LOG(@"[%@ performPostOK]", [self className]);
	_postCount--;
	if (_postCount == 0)
	{
		[self performPostFinish:_postNG];
	}
}

- (void)performPostNG
{
	LOG(@"[%@ performPostNG]", [self className]);
	_postNG++;
	[self performPostOK];
}

- (void)postOK
{
	[self performSelectorOnMainThread:@selector(performPostOK)
						   withObject:nil
						waitUntilDone:NO];
}

- (void)postNG
{
	[self performSelectorOnMainThread:@selector(performPostNG)
						   withObject:nil
						waitUntilDone:NO];
}

- (void)notifyWithTitle:(NSString *)title
			description:(NSString *)description
	   notificationName:(NSString *)notificationName
			   iconData:(NSData *)iconData
		   clickContext:(id)clickContext
{
	//LOG(@"[%@ notifyWithTitle] '%@'", [self className], notificationName);
	//LOG(@"[%@ notifyWithTitle] '%@'", [self className], title);
	[_growl notifyWithTitle:title
				description:description
		   notificationName:notificationName
				   iconData:iconData
			   clickContext:clickContext];
}

- (NSImage *)profileImage:(NSString *)userProfile
{
	return [_timeline profileImage:userProfile];
}

- (NSDictionary *)fetchTimeline:(NSString *)item_id
					withService:(NSString *)service
{
	return [_timeline fetch:item_id
				withService:service];
}

- (NSDictionary *)fetchTimelineALL:(NSString *)item_id
					   withService:(NSString *)service
{
	return [_timeline fetchALL:item_id
				   withService:service];
}

- (NSDictionary *)fetchTimelineRid:(NSString *)rid
					   withService:(NSString *)service
{
	return [_timeline fetchRid:rid
				   withService:service];
}

- (NSDictionary *)beginsTimeline:(NSString *)item_id
					 withService:(NSString *)service
							user:(NSString *)user
{
	return [_timeline begins:item_id
				 withService:service
						user:user];
}

- (void)queueingParsePhotoURL:(NSDictionary *)item
{
	[_timeline queueingParsePhotoURL:item];
}

- (BOOL)addTimeline:(NSDictionary *)item
		withService:(Service *)service
			  first:(BOOL)first
{
	return [_timeline addTimeline:item withService:service first:first];
}

- (void)finishAddTimeline:(NSArray *)objects
			   withNotify:(NSArray *)notify
				  service:(Service *)service
{
	[_timeline finishAddTimeline:objects withNotify:notify service:service];
}

- (id)replaceTimeline:(NSString *)item_id
		  withService:(NSString *)service
			 photoURL:(NSString *)photoURL
{
	return [_timeline replace:item_id
				  withService:service
					 photoURL:photoURL];
}

- (BOOL)deleteTimeline:(NSString *)item_id
		   withService:(NSString *)service
{
	return [_timeline deleteTimeline:item_id
						 withService:service];
}

- (float)withOfTimeline
{
	return [_timeline withOfTimeline];
}

- (void)setComplete
{
	[_panel setComplete];
}

- (BOOL)tryLockService
{
	return [_lockService tryLock];
}

- (void)lockService
{
//	LOG2(@"[%@ lockService lock]", [self className]);
	[_lockService lock];
//	LOG2(@"[%@ lockService locked]", [self className]);
}

- (void)unlockService
{
//	LOG2(@"[%@ unlockService unlock]", [self className]);
	[_lockService unlock];
//	LOG2(@"[%@ unlockService unlocked]", [self className]);
}

- (Service *)service:(NSString *)service
{
	return [_service service:service];
}

- (int)rateLimitOfTwitter
{
	return [_service rateLimitOfTwitter];
}

- (NSString *)activeApplicationName
{
	//LOG(@"[%@ activeApplicationName]", [self className]);
	@try
	{
		NSWorkspace *ws = [NSWorkspace sharedWorkspace];
		//LOG(@"[%@ activeApplicationName]\nws = %@", [self className], ws);
		if (ws)
		{
			NSDictionary *app = [ws activeApplication];
			//LOG(@"[%@ activeApplicationName]\napp = %@", [self className], app);
			if (app)
			{
				NSString *appName = [app valueForKey:@"NSApplicationName"];
				//LOG(@"[%@ activeApplicationName]\nappName = %@", [self className], appName);
				if (appName)
				{
					LOG(@"[%@ activeApplicationName]\n%@: %@", [self className], [appName className], appName);
					return appName;
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ activeApplicationName] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return nil;
}

- (void)setActiveApplicationName
{
	//LOG(@"[%@ setActiveApplicationName]", [self className]);
	if (_activeApplicationName)
	{
		[_activeApplicationName release];
		_activeApplicationName = nil;
	}
#if defined(_D_E_B_U_G_)
	else
	{
		LOG(@"[%@ setActiveApplicationName] NULL", [self className]);
	}
#endif //defined(_D_E_B_U_G_)
	NSString *activeApplicationName = [self activeApplicationName];
	if (activeApplicationName)
	{
		_activeApplicationName = [activeApplicationName copy];
	}
}

- (BOOL)isActiveApplication:(NSString *)applicationName
{
	if (_activeApplicationName &&
		[_activeApplicationName compare:applicationName
								options:NSCaseInsensitiveSearch] == NSOrderedSame)
	{
		return YES;
	}
	return NO;
}

- (BOOL)isLaunchedApplication:(NSString *)applicationName
{
	@try
	{
		NSWorkspace *ws = [NSWorkspace sharedWorkspace];
		//LOG(@"[%@ isLaunchedApplication]\nws = %@", [self className], ws);
		if (ws)
		{
			NSArray *launched = [ws launchedApplications];
			//LOG(@"[%@ isLaunchedApplication]\napp = %@", [self className], launched);
			if (launched)
			{
				NSEnumerator *enumerator = [launched objectEnumerator];
				id obj;
				while ((obj = [enumerator nextObject]))
				{
					NSString *application = [obj valueForKey:@"NSApplicationName"];
					if (application)
					{
						if ([application compare:applicationName
										 options:NSCaseInsensitiveSearch] == NSOrderedSame)
						{
							return YES;
						}
					}
				}
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ isLaunchedApplication] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	return NO;
}

- (NSString *)appleScript:(NSString *)script
{
	NSString *result = nil;
	NSDictionary* errorDict;
	NSAppleEventDescriptor* returnDescriptor = nil;
	NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:script];
	if (scriptObject)
	{
		@try
		{
			returnDescriptor = [scriptObject executeAndReturnError:&errorDict];
			LOG2(@"[%@ appleScript]\n%@", [self className], script);
			if (returnDescriptor)
			{
				LOG2(@"[%@ appleScript]\n%@", [self className], returnDescriptor);
				result = [returnDescriptor stringValue];
			}
			else
			{
				LOG2(@"[%@ appleScript] ERROR:%@", [self className], errorDict);
			}
		}
		@catch (NSException *exception)
		{
			EXPLOG(@"[%@ appleScript] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
		}
		@finally
		{
			[scriptObject release];
		}
	}
	return result;
}

- (NSString *)titleOfSafari
{
	return [self appleScript:@"\
			tell application \"Safari\"\n\
				get name of document 1\n\
			end tell"];
}

- (NSString *)URLOfSafari
{
	return [self appleScript:@"\
			tell application \"Safari\"\n\
				get URL of document 1\n\
			end tell"];
}

- (NSString *)grabSafari:(NSString *)format
{
	NSString *title = [self titleOfSafari];
	NSString *URL = [self URLOfSafari];
	if (!format || [format isEqualToString:@""])
	{
		format = InitGeneralTextURL;
	}
	if (!title)
	{
		title = @"";
	}
	if (!URL)
	{
		URL = @"";
	}
	format = [format replaceWithExpression:@"%title%"
								   replace:title
								   options:OgreIgnoreCaseOption];
	format = [format replaceWithExpression:@"%url%"
								   replace:URL
								   options:OgreIgnoreCaseOption];
	return format;
}

- (NSString *)nameOfFirefox
{
	return [self appleScript:@"\
			tell application \"System Events\"\n\
				tell process \"Firefox\"\n\
					the name of front window\n\
				end tell\n\
			end tell"];
}

- (NSString *)titleOfFirefox
{
	return [self appleScript:@"\
			tell application \"Firefox\"\n\
				«class pTit» of front window\n\
			end tell"];
}

- (NSString *)pnamOfFirefox
{
	return [self appleScript:@"\
			tell application \"Firefox\"\n\
			«class pnam» of front window\n\
			end tell"];
}

- (NSString *)URLOfFirefox
{
	[self appleScript:@"\
	 tell application \"Firefox\"\n\
		properties of front window\n\
	 end tell"];
	return [self appleScript:@"\
			tell application \"Firefox\"\n\
				«class curl» of front window\n\
			end tell"];
}

- (NSString *)URLOfFirefoxClipboard
{
	return [self appleScript:@"\
			tell application \"Firefox\" to activate\n\
			delay 0.3\n\
			tell application \"System Events\"\n\
				keystroke \"l\" using {command down}\n\
				keystroke \"c\" using {command down}\n\
			end tell\n\
			delay 0.3\n\
			the clipboard"];
}

- (NSString *)grabFirefox:(NSString *)format
{
	NSString *URL = [self URLOfFirefox];
	NSString *title = [self titleOfFirefox];
	if (!URL && [_preferences generalGrabURLwithClipboard])
	{
		URL = [self URLOfFirefoxClipboard];
	}
	if (!title)
	{
		title = [self pnamOfFirefox];
	}
	if (!title)
	{
		title = [self nameOfFirefox];
	}
	if (!format || [format isEqualToString:@""])
	{
		format = InitGeneralTextURL;
	}
	if (!title)
	{
		title = @"";
	}
	if (!URL)
	{
		URL = @"";
	}
	format = [format replaceWithExpression:@"%title%"
								   replace:title
								   options:OgreIgnoreCaseOption];
	format = [format replaceWithExpression:@"%url%"
								   replace:URL
								   options:OgreIgnoreCaseOption];
	return format;
}

- (NSString *)pnamOfChrome
{
	return [self appleScript:@"\
			tell application \"Google Chrome\"\n\
				«class pnam» of active tab of front window\n\
			end tell"];
}

- (NSString *)URLOfChrome
{
	return [self appleScript:@"\
			tell application \"Google Chrome\"\n\
				URL of active tab of front window\n\
			end tell"];
}

- (NSString *)grabChrome:(NSString *)format
{
	NSString *URL = nil;
	NSString *title = [self pnamOfChrome];
	if ([_preferences generalGrabURLwithClipboard])
	{
		URL = [self URLOfChrome];
	}
	if (!format || [format isEqualToString:@""])
	{
		format = InitGeneralTextURL;
	}
	if (!title)
	{
		title = @"";
	}
	if (!URL)
	{
		URL = @"";
	}
	format = [format replaceWithExpression:@"%title%"
								   replace:title
								   options:OgreIgnoreCaseOption];
	format = [format replaceWithExpression:@"%url%"
								   replace:URL
								   options:OgreIgnoreCaseOption];
	return format;
}

- (NSString *)pnamOfChromium
{
	return [self appleScript:@"\
			tell application \"Chromium\"\n\
			«class pnam» of front window\n\
			end tell"];
}

- (NSString *)URLOfChromium
{
	return [self appleScript:@"\
			tell application \"Chromium\" to activate\n\
			delay 0.3\n\
			tell application \"System Events\"\n\
			keystroke \"l\" using {command down}\n\
			keystroke \"c\" using {command down}\n\
			end tell\n\
			delay 0.3\n\
			the clipboard"];
}

- (NSString *)grabChromium:(NSString *)format
{
	NSString *URL = nil;
	NSString *title = [self pnamOfChromium];
	if ([_preferences generalGrabURLwithClipboard])
	{
		URL = [self URLOfChromium];
	}
	if (!format || [format isEqualToString:@""])
	{
		format = InitGeneralTextURL;
	}
	if (!title)
	{
		title = @"";
	}
	if (!URL)
	{
		URL = @"";
	}
	format = [format replaceWithExpression:@"%title%"
								   replace:title
								   options:OgreIgnoreCaseOption];
	format = [format replaceWithExpression:@"%url%"
								   replace:URL
								   options:OgreIgnoreCaseOption];
	return format;
}

- (void)doGrabSafari:(BOOL)insert
{
	LOG(@"[%@ doGrabSafari]", [self className]);
	@try
	{
		NSString *text = nil;
		NSRange range = [self selectedPanelRange];
		[self setPanelEnabledText:NO];
		text = [self grabSafari:[_preferences generalTextURL]];
		[self setPanelEnableTextIfDisabled];
		if (text)
		{
			if (insert)
			{
				[self setSelectedPanelRange:range];
				[self insertPanelStringText:text];
			}
			else
			{
				[self setPanelStringText:text];
			}
		}
		else
		{
			[self setSelectedPanelRange:range];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ doGrabSafari] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)doGrabFirefox:(BOOL)insert
{
	LOG(@"[%@ doGrabFirefox]", [self className]);
	@try
	{
		NSString *text = nil;
		NSRange range = [self selectedPanelRange];
		[self setPanelEnabledText:NO];
		text = [self grabFirefox:[_preferences generalTextURL]];
		[self setPanelEnableTextIfDisabled];
		if (text)
		{
			if (insert)
			{
				[self setSelectedPanelRange:range];
				[self insertPanelStringText:text];
			}
			else
			{
				[self setPanelStringText:text];
			}
		}
		else
		{
			[self setSelectedPanelRange:range];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ doGrabFirefox] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)doGrabChrome:(BOOL)insert
{
	LOG(@"[%@ doGrabChrome]", [self className]);
	@try
	{
		NSString *text = nil;
		NSRange range = [self selectedPanelRange];
		[self setPanelEnabledText:NO];
		text = [self grabChrome:[_preferences generalTextURL]];
		[self setPanelEnableTextIfDisabled];
		if (text)
		{
			if (insert)
			{
				[self setSelectedPanelRange:range];
				[self insertPanelStringText:text];
			}
			else
			{
				[self setPanelStringText:text];
			}
		}
		else
		{
			[self setSelectedPanelRange:range];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ doGrabChrome] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)doGrabChromium:(BOOL)insert
{
	LOG(@"[%@ doGrabChromium]", [self className]);
	@try
	{
		NSString *text = nil;
		NSRange range = [self selectedPanelRange];
		[self setPanelEnabledText:NO];
		text = [self grabChromium:[_preferences generalTextURL]];
		[self setPanelEnableTextIfDisabled];
		if (text)
		{
			if (insert)
			{
				[self setSelectedPanelRange:range];
				[self insertPanelStringText:text];
			}
			else
			{
				[self setPanelStringText:text];
			}
		}
		else
		{
			[self setSelectedPanelRange:range];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ doGrabChrome] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)grabURL
{
	@try
	{
		if ([_preferences generalGrabURL])
		{
			if ([self isActiveApplication:SAFARI])
			{
				[self doGrabSafari:NO];
			}
			else if ([self isActiveApplication:FIREFOX])
			{
				[self doGrabFirefox:NO];
			}
			else if ([self isActiveApplication:CHROME])
			{
				[self doGrabChrome:NO];
			}
			else if ([self isActiveApplication:CHROMIUM])
			{
				[self doGrabChromium:NO];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ grabURL] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (NSString *)titleOfITunes
{
	return [self appleScript:@"\
			tell application \"iTunes\"\n\
				name of current track --曲名\n\
			end tell"];
}

- (NSString *)artistOfITunes
{
	return [self appleScript:@"\
			tell application \"iTunes\"\n\
				artist of current track --演奏者\n\
			end tell"];
}

- (NSString *)albumOfITunes
{
	return [self appleScript:@"\
			tell application \"iTunes\"\n\
				album of current track --アルバム\n\
			end tell"];
}

- (NSString *)countOfITunes
{
	return [self appleScript:@"\
			tell application \"iTunes\"\n\
				played count of current track --再生回数\n\
			end tell"];
}

- (NSString *)playlistOfITunes
{
	return [self appleScript:@"\
			tell application \"iTunes\"\n\
				name of current playlist --プレイリスト名\n\
			end tell"];
}

- (NSString *)ratingOfITunes
{
	return [self appleScript:@"\
			tell application \"iTunes\"\n\
				set myrate to rating of current track --マイレート\n\
			end tell"];
}

- (void)doGrabITunes:(BOOL)insert
{
	LOG(@"[%@ doGrabITunes]", [self className]);
	@try
	{
		NSRange range = [self selectedPanelRange];
		[self setPanelEnabledText:NO];
		NSString *title = [self titleOfITunes];
		NSString *artist = [self artistOfITunes];
		NSString *album = [self albumOfITunes];
		NSString *count = [self countOfITunes];
		NSString *playlist = [self playlistOfITunes];
		NSString *rating = [self ratingOfITunes];
		NSString *format = [_preferences generalTextITunes];
		if (!format || [format isEqualToString:@""])
		{
			format = InitGeneralTextURL;
		}
		[self setPanelEnableTextIfDisabled];
		if (title || (title && ![title isEqualToString:@""]) ||
			artist || (artist && ![artist isEqualToString:@""]) ||
			album || (album && ![album isEqualToString:@""]))
		{
			if (!title)
			{
				title = @"";
			}
			if (!artist)
			{
				artist = @"";
			}
			if (!album)
			{
				album = @"";
			}
			if (!count)
			{
				count = @"";
			}
			if (!playlist)
			{
				playlist = @"";
			}
			if (!rating)
			{
				rating = @"";
			}
			else if ([rating isEqualToString:@"20"])
			{
				rating = @"★";
			}
			else if ([rating isEqualToString:@"40"])
			{
				rating = @"★★";
			}
			else if ([rating isEqualToString:@"60"])
			{
				rating = @"★★★";
			}
			else if ([rating isEqualToString:@"80"])
			{
				rating = @"★★★★";
			}
			else if ([rating isEqualToString:@"100"])
			{
				rating = @"★★★★★";
			}
			else
			{
				rating = @"";
			}
			format = [format replaceWithExpression:@"%title%"
										   replace:title
										   options:OgreIgnoreCaseOption];
			format = [format replaceWithExpression:@"%artist%"
										   replace:artist
										   options:OgreIgnoreCaseOption];
			format = [format replaceWithExpression:@"%album%"
										   replace:album
										   options:OgreIgnoreCaseOption];
			format = [format replaceWithExpression:@"%count%"
										   replace:count
										   options:OgreIgnoreCaseOption];
			format = [format replaceWithExpression:@"%playlist%"
										   replace:playlist
										   options:OgreIgnoreCaseOption];
			format = [format replaceWithExpression:@"%rating%"
										   replace:rating
										   options:OgreIgnoreCaseOption];
			if (insert)
			{
				[self setSelectedPanelRange:range];
				[self insertPanelStringText:format];
			}
			else
			{
				[self setPanelStringText:format];
			}
		}
		else
		{
			[self setSelectedPanelRange:range];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ doGrabITunes] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)grabITunes
{
	@try
	{
		if ([_preferences generalGrabITunes])
		{
			if ([self isActiveApplication:ITUNES])
			{
				[self doGrabITunes:NO];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ grabITunes] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
}

- (void)appHide
{
	LOG(@"[%@ appHide]", [self className]);
	[_panel setCanHide:NO];
	[_timeline setCanHide:NO];
	[NSApp hide:self];
}

- (void)panelOrderOut
{
	LOG(@"[%@ panelOrderOut]", [self className]);
	[_panel panelOrderOut:self];
}

- (void)appUnhide
{
	LOG(@"[%@ appUnhide]", [self className]);
	[NSApp unhideWithoutActivation];
}

- (void)performHideAfficheur
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	LOG(@"[%@ performHideAfficheur]", [self className]);
	@try
	{
		BOOL hideApp = (![_preferences generalDockIcon]);// || ![_timeline isDisplay]);
		if (_active && hideApp)
		{
			[self performSelectorOnMainThread:@selector(appHide)
								   withObject:nil
								waitUntilDone:YES];
			[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
		}
		[self performSelectorOnMainThread:@selector(panelOrderOut)
							   withObject:nil
							waitUntilDone:YES];
		if (_active && hideApp)
		{
			[self performSelectorOnMainThread:@selector(appUnhide)
								   withObject:nil
								waitUntilDone:YES];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performHideAfficheur] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	LOG(@"[%@ performHideAfficheur] done", [self className]);
	[pool release];
	[NSThread exit];
}

- (void)hideAfficheur
{
	LOG(@"[%@ hideAfficheur]", [self className]);
	[NSThread detachNewThreadSelector:@selector(performHideAfficheur)
							 toTarget:self
						   withObject:nil];
}

- (NSString *)encodeTinyURL:(NSString *)string
			 withCreateFlag:(BOOL)flag
{
	LOG(@"[%@ encodeTinyURL]\n%@", [self className], string);
	NSString *text = [NSString stringWithFormat:@"%@", string];
	while ([text lengthOfRegularExpression:[AfficheurController stringRegularExpressionURL]
								 withOptions:OgreMultilineOption])
	{
		NSString *url = [text replaceWithExpression:[NSString stringWithFormat:@"^.*?(%@).*$",
													 [AfficheurController stringRegularExpressionURL]]
											replace:@"\\1"
											options:OgreMultilineOption];
		NSString *tiny = @"http://j.mp/123456";
		if (flag)
		{
			int service = [_preferences accountBitLyService];
			NSString *user = [_preferences accountBitLyUser];
			NSString *key = [_preferences accountBitLyAPIKey];
			if ((user == nil) || [user isEqualToString:@""] ||
				(key == nil) || [key isEqualToString:@""])
			{
				service = 1;
				user = BITLY_LOGIN;
				key = BITLY_KEY;
			}
			if (service)
			{
				tiny = [TinyURL createJmp:url
								withLogin:user
								   apiKey:key];
			}
			else
			{
				tiny = [TinyURL createBitLy:url
								withLogin:user
								   apiKey:key];
			}
		}
		if (tiny)
		{
			string = [string replaceWithExpression:[url regularizeString]
										   replace:tiny
										   options:OgreMultilineOption];
		}
		else
		{
			return nil;
		}
		text = [text replaceWithExpression:[url regularizeString]
								   replace:@""
								   options:OgreMultilineOption];
	}
	LOG(@"[%@ encodeTinyURL]\n%@", [self className], string);
	return string;
}

- (NSString *)encodeTinyURL:(NSString *)string
{
	return [self encodeTinyURL:string withCreateFlag:YES];
}

- (void)setTinyURL:(NSString *)tinyURL withURL:(NSString *)url
{
	[_tinyURL setValue:url forKey:tinyURL];
}

- (NSString *)tinyURL:(NSString *)tinyURL
{
	return [_tinyURL valueForKey:tinyURL];
}

- (UInt32)keyModifiers
{
	UInt32 modifiers = GetCurrentKeyModifiers();
	UInt32 nsmodifiers = 0;
	if (modifiers & (1 << cmdKeyBit))
	{
		nsmodifiers |= NSCommandKeyMask;
	}
	if (modifiers & (1 << shiftKeyBit))
	{
		nsmodifiers |= NSShiftKeyMask;
	}
	if (modifiers & (1 << optionKeyBit))
	{
		nsmodifiers |= NSAlternateKeyMask;
	}
	if (modifiers & (1 << controlKeyBit))
	{
		nsmodifiers |= NSControlKeyMask;
	}
	return nsmodifiers;
}

- (void)setSelectedItem:(id)object
{
	[_timeline setSelectedItem:[object valueForKey:KeyId] withService:[object valueForKey:KeyService]];
}

- (void)activateTimeline
{
	LOG(@"[%@ activateTimeline]", [self className]);
	BOOL display = [_timeline isDisplay];
	if (!display)
	{
		[_timeline setDisplay:YES];
	}
	if (!_active)
	{
		[NSApp activateIgnoringOtherApps:YES];
	}
	[_timeline makeKeyAndOrderFront];
}

- (void)performClickAction:(id)action
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		[self doClickAction:action];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performClickAction] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[action release];
	}
	[pool release];
	[NSThread exit];
}

- (void)doClickAction:(id)action
{
	//LOG(@"[%@ doClickAction] %@", [self className], action);
	NSString *service = [action replaceWithExpression:@"^(.*?) (.*)$" replace:@"\\1"];
	NSString *item_id = [action replaceWithExpression:@"^(.*?) (.*)$" replace:@"\\2"];
	LOG(@"[%@ doClickAction] %@:%@", [self className], service, item_id);
	if (![item_id isEqualToString:IdZero])
	{
		NSString *removed = [_timeline removed:item_id withService:service];
		if (removed)
		{
			item_id = removed;
			LOG(@"[%@ doClickAction] %@", [self className], item_id);
		}
		[self setSelectedItem:[NSDictionary dictionaryWithObjectsAndKeys:
							   item_id, KeyId, service, KeyService, nil]];
		
		UInt32 nsmodifiers = [self keyModifiers];
		LOG(@"[%@ doClickAction] %08X", [self className], nsmodifiers);
		if (nsmodifiers == 0)
		{
			LOG(@"[%@ doClickAction] None", [self className]);
			[self activateTimeline];
		}
		else if (nsmodifiers == [_preferences generalReplyKeyMask])
		{
			LOG(@"[%@ doClickAction] doReply:%@", [self className], service);
			[_service doReply:item_id withService:service];
		}
		else if (nsmodifiers == [_preferences generalFavoriteKeyMask])
		{
			LOG(@"[%@ doClickAction] doFavorite:%@", [self className], service);
			[_service doFavorite:item_id withService:service];
		}
		else if (nsmodifiers == [_preferences generalPermalinkKeyMask])
		{
			LOG(@"[%@ doClickAction] doPermalink:%@", [self className], service);
			[_service doPermalink:item_id withService:service];
		}
		else if (nsmodifiers == [_preferences generalURLKeyMask])
		{
			LOG(@"[%@ doClickAction] doOpenURL:%@", [self className], service);
			[_service doOpenURL:item_id withService:service];
		}
		else if (nsmodifiers == [_preferences generalRetweetKeyMask])
		{
			LOG(@"[%@ doClickAction] doRetweet:%@", [self className], service);
			if ([_timeline isEnabledRetweetViaAPI])
			{
				LOG(@"[%@ doClickAction] doRetweetViaAPI:%@", [self className], service);
				[_service doRetweetViaAPI:item_id withService:service];
			}
		}
		else if (nsmodifiers == [_preferences generalDirectMessageKeyMask])
		{
			LOG(@"[%@ doClickAction] doDirectMessage:%@", [self className], service);
			[_service doDirectMessage:item_id withService:service];
		}
	}
}

- (BOOL)isEnabled:(NSString *)service
{
	return [_panel isCheckBoxEnabled:service];
}

- (void)doPost:(NSString *)text
withAltEncodeTinyURL:(BOOL)altEncodeTinyURL
{
	LOG(@"[%@ doPost]", [self className]);
	[_service post:text
	 withInReplyTo:[_panel inReplyTo]
  inReplyToService:[_panel inReplyToService]
  altEncodeTinyURL:altEncodeTinyURL];
}

- (void)syncPost
{
	LOG(@"[%@ syncPost]", [self className]);
	[_lockPost lock];
	[_lockPost unlock];
	LOG(@"[%@ syncPost] done", [self className]);
}

- (void)performLockPost:(id)object
{
	@synchronized(_lockPost)
	{
		_lockedPost = YES;
		[_lockPost lock];
	}
}

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

- (void)performUnlockPost:(id)object
{
	@synchronized(_lockPost)
	{
		if (_lockedPost)
		{
			_lockedPost = NO;
			[_lockPost unlock];
		}
	}
}

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

- (void)performPosted:(id)object
{
	int postCount = [object intValue];
	LOG(@"[%@ performPosted] %d", [self className], postCount);
	if (postCount > 0)
	{
		_postNG = 0;
		_postCount = postCount;
		[_panel setEnabledText:NO];
		//[_spinner setImage:[NSImage imageNamed:@"spinner_16"]];
		//[_spinner setAnimates:YES];
	}
	else if (postCount < 0)
	{
		[self notifyWithTitle:Afficheur
				  description:@"Shorten URL was failure."
			 notificationName:GrowlAfficheurFailureNotify
					 iconData:nil
				 clickContext:nil];
	}
	LOG(@"[%@ performPosted] done", [self className]);
}

- (void)posted:(int)postCount
{
	LOG(@"[%@ posted] %d", [self className], postCount);
	[self performSelectorOnMainThread:@selector(performPosted:)
						   withObject:[NSNumber numberWithInt:postCount]
						waitUntilDone:YES];
}

- (void)userStream:(id)userStream
	   withService:(NSString *)service
{
	[_service userStream:userStream
			 withService:service];
}

- (void)finishUserStreamWithService:(NSString *)service
{
	[_service finishUserStreamWithService:service];
}

- (void)errorUserStream:(NSError *)error
			withService:(NSString *)service
{
	[_service errorUserStream:error
				  withService:service];
}

- (int)iconCode
{
	return [_panel iconCode];
}

- (void)setReply:(NSAttributedString *)reply
	 withService:(NSString *)servie
	   inReplyTo:(NSString *)inReplyTo
			text:(NSString *)text
	  tinyURLize:(BOOL)tinyURLize
{
	[_panel setReply:reply
		 withService:servie
		   inReplyTo:inReplyTo
				text:text
		  tinyURLize:tinyURLize
			  sender:self];
}

- (void)setReply:(NSAttributedString *)reply
	 withService:(NSString *)servie
	   inReplyTo:(NSString *)inReplyTo
			text:(NSString *)text
{
	[_panel setReply:reply
		 withService:servie
		   inReplyTo:inReplyTo
				text:text
			  sender:self];
}

- (void)saveState:(BOOL)state
	   andEnabled:(BOOL)enabled
		  service:(NSString *)service
{
	[_service saveState:state
			 andEnabled:enabled
				service:service];
}

- (BOOL)status:(NSString *)service
{
	return [_service status:service];
}

- (BOOL)enable:(NSString *)service
{
	return [_service enable:service];
}

- (void)tickTimer:(id)object
{
	//LOG(@"[%@ tickTimer]", [self className]);
//	NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:0];
	@try
	{
		[_service tickTimer];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ tickTimer] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
//	@finally
//	{
//		[date release];
//	}
}

- (void)performOnOpen
{
	LOG(@"[%@ performOnOpen]", [self className]);
	@try
	{
		//LOG(@"[%@ performOnOpen] activateIgnoringOtherApps", [self className]);
		[NSApp activateIgnoringOtherApps:YES];
		//LOG(@"[%@ performOnOpen] panelOrderFront", [self className]);
		[_panel panelOrderFront:self];
		//LOG(@"[%@ performOnOpen] panelMakeKeyAndOrderFront", [self className]);
		[_panel panelMakeKeyAndOrderFront:self];
		//LOG(@"[%@ performOnOpen] controlTextDidChange", [self className]);
		[_panel controlTextDidChange];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performOnOpen] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	LOG(@"[%@ performOnOpen] done", [self className]);
}

- (void)performThreadOnOpen:(id)sender
{
	@try
	{
		LOG(@"[%@ performThreadOnOpen]", [self className]);
		//LOG(@"[%@ performThreadOnOpen] setActiveApplicationName", [self className]);
		[self setActiveApplicationName];
		if (sender)
		{
			LOG(@"[%@ performThreadOnOpen] performOnOpen", [self className]);
			[self performSelectorOnMainThread:@selector(performOnOpen)
								   withObject:nil
								waitUntilDone:YES];
			//LOG(@"[%@ performThreadOnOpen] grabURL", [self className]);
			[self grabURL];
			//LOG(@"[%@ performThreadOnOpen] grabITunes", [self className]);
			[self grabITunes];
		}
		if (sender && sender != self)
		{
			LOG(@"[%@ performThreadOnOpen] performOnOpen", [self className]);
			[self performSelectorOnMainThread:@selector(performOnOpen)
								   withObject:nil
								waitUntilDone:YES];
			//LOG(@"[%@ performThreadOnOpen] makeFirstResponder", [self className]);
			[_panel makeFirstResponderText];
			LOG(@"[%@ performThreadOnOpen] performSetReply", [self className]);
			if (sender && [sender respondsToSelector:@selector(performSetReply)])
			{
				[sender performSelector:@selector(performSetReply)];
			}
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performThreadOnOpen] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	//LOG(@"[%@ performThreadOnOpen] done", [self className]);
}

- (void)threadOnOpen:(id)sender
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		//LOG(@"[%@ threadOnOpen]", [self className]);
		[self performThreadOnOpen:sender];
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadOnOpen] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	@finally
	{
		[sender release];
	}
	//LOG(@"[%@ threadOnOpen] done", [self className]);
	[pool release];
	[NSThread exit];
}

- (void)threadGrabSafai:(id)sender
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	LOG(@"[%@ threadGrabSafai]", [self className]);
	@try
	{
		if ([self isLaunchedApplication:SAFARI])
		{
			[self doGrabSafari:YES];
			[self performThreadOnOpen:sender];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadGrabSafai] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	LOG(@"[%@ threadGrabSafai] done", [self className]);
	[pool release];
	[NSThread exit];
}

- (void)threadGrabFirefox:(id)sender
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	LOG(@"[%@ threadGrabFirefox]", [self className]);
	@try
	{
		if ([self isLaunchedApplication:FIREFOX])
		{
			[self doGrabFirefox:YES];
			[self performThreadOnOpen:sender];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadGrabFirefox] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	LOG(@"[%@ threadGrabFirefox] done", [self className]);
	[pool release];
	[NSThread exit];
}

- (void)threadGrabChrome:(id)sender
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	LOG(@"[%@ threadGrabChrome]", [self className]);
	@try
	{
		if ([self isLaunchedApplication:CHROME])
		{
			[self doGrabChrome:YES];
			[self performThreadOnOpen:sender];
		}
		else if ([self isLaunchedApplication:CHROMIUM])
		{
			[self doGrabChromium:YES];
			[self performThreadOnOpen:sender];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadGrabChrome] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	LOG(@"[%@ threadGrabChrome] done", [self className]);
	[pool release];
	[NSThread exit];
}

- (void)threadGrabITunes:(id)sender
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	LOG(@"[%@ threadGrabITunes]", [self className]);
	@try
	{
		if ([self isLaunchedApplication:ITUNES])
		{
			[self doGrabITunes:YES];
			[self performThreadOnOpen:sender];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ threadGrabITunes] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	LOG(@"[%@ threadGrabITunes] done", [self className]);
	[pool release];
	[NSThread exit];
}

- (void)alertDidEnd:(NSWindow*)sheet
		 returnCode:(int)returnCode
		contextInfo:(void*)contextInfo
{
	LOG(@"[%@ alertDidEnd]", [self className]);
	if (contextInfo && [(id)contextInfo isKindOfClass:[NSDictionary class]])
	{
		id command = [(id)contextInfo valueForKey:KeySelector];
		id	target = [(id)contextInfo valueForKey:KeyTarget];
		if (command && ![command isKindOfClass:[NSNull class]] &&
			target && ![target isKindOfClass:[NSNull class]])
		{
			SEL	selector = NSSelectorFromString(command);
			if ([target respondsToSelector:selector])
			{
				[target performSelector:selector];
			}
		}
	}
	if (contextInfo)
	{
		[(id)contextInfo release];
	}
}

- (void)alert:(NSString *)message
	withTitle:(NSString *)title
	 selector:(SEL)selector
	   target:(id)target
{
	LOG(@"[%@ alert]", [self className]);
	[[_panel panel] makeKeyAndOrderFront:self];
	NSAlert *alert = [NSAlert alertWithMessageText:title
									 defaultButton:@"OK"
								   alternateButton:nil
									   otherButton:nil
						 informativeTextWithFormat:@"%@", message];
	id command = [NSNull null];
	if (selector)
	{
		command = NSStringFromSelector(selector);
	}
	id targetObject = [NSNull null];
	if (target)
	{
		targetObject = target;
	}
	[alert beginSheetModalForWindow:[_panel panel]
					  modalDelegate:self
					 didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
						contextInfo:[[NSDictionary dictionaryWithObjectsAndKeys:
									  command, KeySelector,
									  targetObject, KeyTarget,
									  nil] retain]];
	LOG(@"[%@ alert] done", [self className]);
}


- (void)alert:(NSString *)message
 withSelector:(SEL)selector
	   target:(id)target
{
	[self alert:message
	  withTitle:@"Error"
	   selector:selector
		 target:target];
}

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

- (BOOL)isSleep
{
	return _sleep;
}

- (void)notification:(NSNotification *)notification
{
	LOG(@"[%@ notification]\n%@", [self className], notification);
	if ([[notification valueForKey:@"name"] isEqualToString:NSWorkspaceWillSleepNotification])
	{
		_sleep = YES;
	}
	else if ([[notification valueForKey:@"name"] isEqualToString:NSWorkspaceDidWakeNotification])
	{
		_sleep = NO;
	}
}

- (void)performOnReply
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			[_service doReply:[obj valueForKey:KeyId]
				  withService:[obj valueForKey:KeyService]];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performOnReply] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[pool release];
	[NSThread exit];
}

- (void)performOnFavorite
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			[_service doFavorite:[obj valueForKey:KeyId]
					 withService:[obj valueForKey:KeyService]];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performOnFavorite] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[pool release];
	[NSThread exit];
}

- (void)performOnOpenPermalink
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			[_service doPermalink:[obj valueForKey:KeyId]
					  withService:[obj valueForKey:KeyService]];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performOnOpenPermalink] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[pool release];
	[NSThread exit];
}

- (void)performOnOpenURL
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			[_service doOpenURL:[obj valueForKey:KeyId]
					withService:[obj valueForKey:KeyService]];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performOnOpenURL] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[pool release];
	[NSThread exit];
}

- (void)performOnRetweetViaAPI
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			[_service doRetweetViaAPI:[obj valueForKey:KeyId]
						  withService:[obj valueForKey:KeyService]];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performOnRetweetViaAPI] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[pool release];
	[NSThread exit];
}

- (void)performOnDirectMessage
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			[_service doDirectMessage:[obj valueForKey:KeyId]
						  withService:[obj valueForKey:KeyService]];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performOnDirectMessage] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[pool release];
	[NSThread exit];
}

- (void)performOnCopyPermalink
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	@try
	{
		NSArray *selected = [_timeline selectedObjects];
		if (selected && ([selected count] > 0))
		{
			id obj = [selected objectAtIndex:0];
			[_service doCopyPermalink:[obj valueForKey:KeyId]
						  withService:[obj valueForKey:KeyService]];
		}
	}
	@catch (NSException *exception)
	{
		EXPLOG(@"[%@ performOnOpenURL] EXCEPTION\n%@: %@", [self className], [exception name], [exception reason]);
	}
	[pool release];
	[NSThread exit];
}

- (void)setMenuIcon:(NSNumber *)icon
{
	NSImage* image = [NSImage imageNamed:MenuIcon];
	if ([icon intValue])
	{
		image = [NSImage imageNamed:MenuIconAlternate];
	}
	[_statusItem setImage:image];
}

- (void)setStatusMenuIcon
{
	NSStatusBar*	statusBar = [NSStatusBar systemStatusBar];
	if ([_preferences generalStatusMenuIcon])
	{
		if (_statusItem == nil)
		{
			_statusItem = [[statusBar statusItemWithLength:24/*NSVariableStatusItemLength*/] retain];
			[_statusItem setTitle:nil];
			[_statusItem setHighlightMode:YES];
			[_statusItem setMenu:_menu];
			[_statusItem setEnabled:YES];
		}
		[self setMenuIcon:[NSNumber numberWithInt:[_preferences generalMenuIcon]]];
	}
	else if (_statusItem)
	{
		[statusBar removeStatusItem:_statusItem];
		[_statusItem release];
		_statusItem = nil;
	}
}

- (void)clearCookies
{
	NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
	NSArray *cookies = [cookieStorage cookies];
	NSEnumerator *enumerator = [cookies objectEnumerator];
	NSHTTPCookie *cookie;
	while ((cookie = [enumerator nextObject]))
	{
//		if ([[cookie domain] isEqualToString:DomainJisko])
//		{
//			LOG(@"[%@ clearCookies]\n%@", [self className], cookie);
//			if ([[cookie name] isEqualToString:SessionJisko])
//			{
//				LOG(@"[%@ clearCookies] deleteCookie", [self className]);
//				[cookieStorage deleteCookie:cookie];
//			}
//		}
	}
	LOG(@"[%@ clearCookies] clear", [self className]);
	cookies = [cookieStorage cookies];
	enumerator = [cookies objectEnumerator];
	cookie;
	while ((cookie = [enumerator nextObject]))
	{
//		if ([[cookie domain] isEqualToString:DomainJisko])
//		{
//			LOG(@"[%@ clearCookies]\n%@", [self className], cookie);
//		}
	}
}

- (void)setToolbarMenuTitle:(NSString *)title
{
	[_mainMenuToolbar setTitle:title];
	[_menuToolbar setTitle:title];
}

#pragma mark NSObject Delegates

- (void)awakeFromNib
{
	//LOG(@"[%@ awakeFromNib]", [self className]);
	[_window setAlphaValue:0.0];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
	LOG(@"[%@ applicationDidFinishLaunching]", [self className]);
	[self clearCookies];
	//[[NSData dataWithData:[[NSImage imageNamed:@"NSColorPanel"] TIFFRepresentation]] writeToFile:@"/Users/ichi/ColorPanel.tiff" atomically:YES];
	[self setStatusMenuIcon];

	[self registerHotKey];
	[NSApp activateIgnoringOtherApps:YES];

	[_service setupWithController:self preferences:_preferences];
	
	_panel = [[AfficheurPanel alloc] initWithPreferences:_preferences];
	[_panel setController:self];
	[_panel startThread];
	[_panel setCheckBoxEnabled];
	[_panel setCanHide:NO];
	
	LOG(@"[%@ applicationDidFinishLaunching]\n%@", [self className], [self service:Twitter]);
	_timeline = [[AfficheurTimeline alloc] initWithController:self
												  preferences:_preferences];
	[_timeline startThread];
	[_timeline setCanHide:NO];
	[_growl startWithDelegate:self];

	[_preferences setController:self];
	[_preferences setTimeline:_timeline];
	[_preferences applicationDidFinishLaunching];
	
	[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
														   selector:@selector(notification:)
															   name:NSWorkspaceWillSleepNotification
															 object:nil];
	[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
														   selector:@selector(notification:)
															   name:NSWorkspaceDidWakeNotification
															 object:nil];
	
	[self performSelectorOnMainThread:@selector(performGetMainThread:)
						   withObject:nil
						waitUntilDone:YES];
	
	if (![_preferences generalHideStartup])
	{
		[self onOpen:self];
	}
	
	[NSTimer scheduledTimerWithTimeInterval:3.0
									 target:self
								   selector:@selector(tickTimer:)
								   userInfo:nil
									repeats:YES];
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
	[self unregisterHotKey];
	[_panel  applicationWillTerminate];
	[_service applicationWillTerminate];
	[_timeline applicationWillTerminate];
	[_preferences applicationWillTerminate];
	[self clearCookies];
}

- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
	SEL action = [menuItem action];
	LOG(@"[%@ validateMenuItem]\n%@", [self className], menuItem);
	if (action == @selector(onReply:))
	{
		return ![_panel isKeyView] && [_timeline isEnabledReply];
	}
	else if (action == @selector(onFavorite:))
	{
		return ![_panel isKeyView] && [_timeline isEnabledFavorite];
	}
	else if (action == @selector(onOpenPermalink:))
	{
		return ![_panel isKeyView] && [_timeline isEnabledOpenPermaLink];
	}
	else if (action == @selector(onOpenURL:))
	{
		return ![_panel isKeyView] && [_timeline isEnabledOpenURL];
	}
	else if (action == @selector(onRetweetViaAPI:))
	{
		return ![_panel isKeyView] && [_timeline isEnabledRetweetViaAPI];
	}
	else if (action == @selector(onDirectMessage:))
	{
		return ![_panel isKeyView] && [_timeline isEnabledDirectMessage];
	}
	else if (action == @selector(onReleaseFiltering:))
	{
		return [_timeline validateMenuItem:menuItem];
	}
	else if (action == @selector(onEmoji:))
	{
		return [_panel isEnabledButtonEmoji];
	}
	else if (action == @selector(onGrabSafari:))
	{
		return [self isLaunchedApplication:SAFARI];
	}
	else if (action == @selector(onGrabFirefox:))
	{
		return [self isLaunchedApplication:FIREFOX];
	}
	else if (action == @selector(onGrabChrome:))
	{
		return ([self isLaunchedApplication:CHROME] || [self isLaunchedApplication:CHROMIUM]);
	}
	else if (action == @selector(onGrabITunes:))
	{
		return [self isLaunchedApplication:ITUNES];
	}
	else if (action == @selector(onCopy:))
	{
		if ([_timeline isKeyView])
		{
			return [_timeline isEnabledCopy];
		}
		return YES;
	}
	else if (action == @selector(onCopyPermalink:))
	{
		return ![_panel isKeyView] && [_timeline isEnabledCopyPermalink];
	}
	return YES;
}

#pragma mark NSApplication Delegates

- (void)applicationWillBecomeActive:(NSNotification *)aNotification
{
	//LOG(@"[%@ applicationWillBecomeActive]\n%@", [self className], aNotification);
}

- (void)applicationDidBecomeActive:(NSNotification *)aNotification
{
	LOG(@"[%@ applicationDidBecomeActive]\n%@", [self className], aNotification);
	_active = YES;
}

- (void)applicationWillResignActive:(NSNotification *)aNotification
{
	//LOG(@"[%@ applicationWillResignActive]\n%@", [self className], aNotification);
}

- (void)applicationDidResignActive:(NSNotification *)aNotification
{
	LOG(@"[%@ applicationDidResignActive]\n%@", [self className], aNotification);
	_active = NO;
}

#pragma mark Growl Delegates

- (void)growlNotificationWasClicked:(id)clickContext
{
	if (clickContext && [clickContext isKindOfClass:[NSString class]])
	{
		//LOG(@"[%@ growlNotificationWasClicked]\n%@", [self className], clickContext);
		//[self doClickAction:clickContext];
		[NSThread detachNewThreadSelector:@selector(performClickAction:)
								 toTarget:self
							   withObject:[clickContext copy]];
	}
}

- (void)growlNotificationTimedOut:(id)clickContext
{
}

#pragma mark HotKey Action

- (void)hotKey
{
	[self onOpen:self];
}

- (void)hotKeyTimeline
{
	[self onViewTimeline:nil];
}

#pragma mark IBActions

- (IBAction)onOpen:(id)sender
{
	LOG(@"[%@ onOpen]\n%@", [self className], sender);
	if (![_panel isDisplay] || ![_panel isKeyView] || (sender != self))
	{
		[NSThread detachNewThreadSelector:@selector(threadOnOpen:)
								 toTarget:self
							   withObject:[sender retain]];
	}
	else
	{
		[self hideAfficheur];
	}
	LOG(@"[%@ onOpen] done", [self className]);
}

- (void)endPreferences
{
	[self registerHotKey];
	[self setStatusMenuIcon];
	[self setComplete];
	[_panel setCheckBoxEnabled];
	[_service setupXMPP];
	if (![self isLeopard])
	{
		[self performSelectorOnMainThread:@selector(performOnOpen)
							   withObject:nil
							waitUntilDone:YES];
	}
	NSDictionary *appearances = [_preferences appearances];
	if (_appearances)
	{
		if (![_appearances isEqualToDictionary:appearances])
		{
			[_timeline reformat];
		}
		[_appearances release];
		_appearances = nil;
	}
//	if (_jiskoUser && _jiskoPass)
//	{
//		if (![_jiskoUser isEqualToString:[_preferences accountJiskoUser]] ||
//			![_jiskoPass isEqualToString:[_preferences accountJiskoPass]])
//		{
//			[self clearCookies];
//		}
//	}
//	if (_jiskoUser)
//	{
//		[_jiskoUser release];
//		_jiskoUser = nil;
//	}
//	if (_jiskoPass)
//	{
//		[_jiskoUser release];
//		_jiskoUser = nil;
//	}
	_doPreferences = NO;
}

- (IBAction)onPreferences:(id)sender
{
	_doPreferences = YES;
	_appearances = [[_preferences appearances] retain];
//	_jiskoUser = [[_preferences accountJiskoUser] retain];
//	_jiskoPass = [[_preferences accountJiskoPass] retain];
	[NSApp activateIgnoringOtherApps:YES];
	[self unregisterHotKey];
	[_preferences doDialogWithWindow:[_panel panel]
							selector:@selector(endPreferences)
							  target:self];
}

- (IBAction)onRetrieveTimeline:(id)sender
{
	//LOG(@"[%@ onRetrieveTimeline]", [self className]);
	_sleep = NO;
	[_service doRetrieveTimeline];
}

- (IBAction)onRetrieveReplies:(id)sender
{
	//LOG(@"[%@ onRetrieveReplies]", [self className]);
	_sleep = NO;
	[_service doRetrieveReplies];
}

- (IBAction)onRetrieveDirectMessage:(id)sender
{
	//LOG(@"[%@ onRetrieveDirectMessage]", [self className]);
	_sleep = NO;
	[_service doRetrieveDirectMessage];
}

- (IBAction)onViewTimeline:(id)sender
{
	LOG(@"[%@ onViewTimeline] %@", [self className], sender);
	BOOL display = [_timeline isDisplay];
	BOOL keyView = [_timeline isKeyView];
	if ((!display && ![_panel isDisplay]) || (!sender && display && !keyView))
	{
		[self activateTimeline];
	}
	else if (display && ![_panel isDisplay] && ![_preferences generalDockIcon])
	{
		[_timeline setDisplay:NO];
		[self hideAfficheur];
	}
	else
	{
		[_timeline setDisplay:!display];
	}
}

- (IBAction)onKeepCursor:(id)sender
{
	[_preferences setGeneralKeepCursorPosition:![_preferences generalKeepCursorPosition]];
}

- (IBAction)onGrabSafari:(id)sender
{
	[NSThread detachNewThreadSelector:@selector(threadGrabSafai:)
							 toTarget:self
						   withObject:sender];
}

- (IBAction)onGrabFirefox:(id)sender
{
	[NSThread detachNewThreadSelector:@selector(threadGrabFirefox:)
							 toTarget:self
						   withObject:sender];
}
- (IBAction)onGrabChrome:(id)sender
{
	[NSThread detachNewThreadSelector:@selector(threadGrabChrome:)
							 toTarget:self
						   withObject:sender];
}

- (IBAction)onGrabITunes:(id)sender
{
	[NSThread detachNewThreadSelector:@selector(threadGrabITunes:)
							 toTarget:self
						   withObject:sender];
}

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

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

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

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

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

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

- (IBAction)onReleaseFiltering:(id)sender
{
	[_timeline releaseFiltering];
}

- (IBAction)onAction:(id)sender
{
	[self doPost:[sender stringValue] withAltEncodeTinyURL:NO];
}

- (IBAction)onService:(id)sender
{
}

- (void)setOffStateWithTiele:(NSString *)title
{
	if ([title isEqualToString:[_menuItemDocomo title]])
	{
		[_menuItemAu setState:NSOffState];
		[_mainMenuItemAu setState:NSOffState];
		[_menuItemSoftBank setState:NSOffState];
		[_mainMenuItemSoftBank setState:NSOffState];
		[_menuItemJaiku setState:NSOffState];
		[_mainMenuItemJaiku setState:NSOffState];
	}
	else if ([title isEqualToString:[_menuItemAu title]])
	{
		[_menuItemDocomo setState:NSOffState];
		[_mainMenuItemDocomo setState:NSOffState];
		[_menuItemSoftBank setState:NSOffState];
		[_mainMenuItemSoftBank setState:NSOffState];
		[_menuItemJaiku setState:NSOffState];
		[_mainMenuItemJaiku setState:NSOffState];
	}
	else if ([title isEqualToString:[_menuItemSoftBank title]])
	{
		[_menuItemDocomo setState:NSOffState];
		[_mainMenuItemDocomo setState:NSOffState];
		[_menuItemAu setState:NSOffState];
		[_mainMenuItemAu setState:NSOffState];
		[_menuItemJaiku setState:NSOffState];
		[_mainMenuItemJaiku setState:NSOffState];
	}
	else if ([title isEqualToString:[_menuItemSoftBank title]])
	{
		[_menuItemDocomo setState:NSOffState];
		[_mainMenuItemDocomo setState:NSOffState];
		[_menuItemAu setState:NSOffState];
		[_mainMenuItemAu setState:NSOffState];
		[_menuItemSoftBank setState:NSOffState];
		[_mainMenuItemSoftBank setState:NSOffState];
	}
	else
	{
		[_menuItemDocomo setState:NSOffState];
		[_mainMenuItemDocomo setState:NSOffState];
		[_menuItemAu setState:NSOffState];
		[_mainMenuItemAu setState:NSOffState];
		[_menuItemSoftBank setState:NSOffState];
		[_mainMenuItemSoftBank setState:NSOffState];
		[_menuItemJaiku setState:NSOffState];
		[_mainMenuItemJaiku setState:NSOffState];
	}
}

- (void)doEmojiWithMainMenutem:(NSMenuItem *)mainMenuItem
					  menuItem:(NSMenuItem *)menuItem
{
	if (mainMenuItem && menuItem)
	{
		if ([menuItem state] != NSOnState)
		{
			[self setOffStateWithTiele:[menuItem title]];
			[menuItem setState:NSOnState];
			[mainMenuItem setState:NSOnState];
			[_panel setButtonEmoji:[menuItem title]];
		}
		else
		{
			[menuItem setState:NSOffState];
			[mainMenuItem setState:NSOffState];
			[_panel setButtonEmoji:EMOJI];
		}
	}
	else
	{
		[self setOffStateWithTiele:@""];
		[_panel setButtonEmoji:EMOJI];
	}
}

- (IBAction)onEmoji:(id)sender
{
	LOG(@"[%@ onEmoji]\n%@", [self className], sender);
	if ([[sender title] isEqualToString:DOCOMO])
	{
		[self doEmojiWithMainMenutem:_mainMenuItemDocomo
							menuItem:_menuItemDocomo];
	}
	else if ([[sender title] isEqualToString:AU])
	{
		[self doEmojiWithMainMenutem:_mainMenuItemAu
							menuItem:_menuItemAu];
	}
	else if ([[sender title] isEqualToString:SOFTBANK])
	{
		[self doEmojiWithMainMenutem:_mainMenuItemSoftBank
							menuItem:_menuItemSoftBank];
	}
	else if ([[sender title] isEqualToString:JAIKU])
	{
		[self doEmojiWithMainMenutem:_mainMenuItemJaiku
							menuItem:_menuItemJaiku];
	}
	else
	{
		[self doEmojiWithMainMenutem:nil
							menuItem:nil];
	}
}

- (IBAction)onToolbar:(id)sender
{
	LOG(@"[%@ onToolbar]\n%@", [self className], sender);
	[_timeline onToolbar:sender];
}

- (IBAction)onFiltering:(id)sender
{
	LOG(@"[%@ onFiltering]\n%@", [self className], sender);
	[_timeline onFiltering:sender];
}

- (IBAction)onCopy:(id)sender;
{
	LOG(@"[%@ onCopy]\n%@", [self className], sender);
	if ([_timeline isKeyView])
	{
		[_timeline onCopy:sender];
	}
	else
	{
		[[NSApplication sharedApplication] sendAction:@selector(copy:) to:nil from:self];
	}
}

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

@end
