/*
SRAppDelegate.m

Author: Makoto Kinoshita

Copyright 2004 The Shiira Project. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions 
  and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of 
  conditions and the following disclaimer in the documentation and/or other materials provided 
  with the distribution.

THIS SOFTWARE IS PROVIDED BY THE SHIIRA PROJECT ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE SHIIRA PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

#import "SRDefaultsKey.h"
#import "SRContextMenu.h"

#import "SRMainDocument.h"
#import "SRMainWindowController.h"

#import "SRAppDelegate.h"
#import "SRSidebarController.h"
#import "SRPreferencesController.h"
#import "SRSearchEnginesController.h"
#import "SRFindWindowController.h"
#import "SRCacheController.h"
#import "SRJSErrorLogController.h"
#import "SRDownloadCenter.h"
#import "SRDownloadHistoryItem.h"
#import "SRDownloadsController.h"
#import "SRIconInstaller.h"
#import "SRAboutController.h"
#import "SRVisualHistoryStorage.h"
#import "SRVHBackForwardController.h"
#import "SRRSSManager.h"

#import "SRBookmark.h"
#import "SRBookmarkStorage.h"
#import "SRDownloadHistory.h"
#import "SREncodings.h"

#import "SRMenu.h"

#import "SRUtil.h"
#import "SRFeedURLProtocol.h"
#import "AppKitEx.h"
#import "NSDateEx.h"

#import "SRSDSyndication.h"

// Consntats
int  SRNumberOfDefaultHistoryMenu = 7;
int  SRNumberOfRecentHistoryItem = 8;
int  SRNumberOfDefaultBookmarkMenu = 6;

// Setting file names
NSString*   SRShiiraDirectory = @"Shiira";
NSString*   SRHistoryFileName = @"Shiira/History.plist";


// For Growl support
#ifdef SR_SUPPORT_GROWL
NSString*   SRGrowlDownloadCompletedNotification = @"Download completion";
NSString*   SRGrowlRSSComesNotification = @"New RSS articles comes";
#endif // SR_SUPPORT_GROWL

@implementation SRHistoryMenuCreator

//--------------------------------------------------------------//
#pragma mark -- NSMenu delegate --
//--------------------------------------------------------------//

- (BOOL)menuHasKeyEquivalent:(NSMenu*)menu 
        forEvent:(NSEvent*)event 
        target:(id*)target 
        action:(SEL*)action
{
    return NO;
}

- (int)_numberOfHistoryItemsWithLimit:(int)limit
{
    int count = 0;
    
    WebHistory*     history;
    NSArray*        lastVisitedDays;
    NSCalendarDate* calendarDate;
    NSEnumerator*   enumerator;
    history = [WebHistory optionalSharedHistory];
    lastVisitedDays = [history orderedLastVisitedDays];
    enumerator = [lastVisitedDays objectEnumerator];
    while (calendarDate = [enumerator nextObject]) {
        // Get history items
        NSArray*    historyItems;
        historyItems = [history orderedItemsLastVisitedOnDay:calendarDate];
        if ([historyItems count] >= limit - count) {
            count = limit;
            break;
        }
        
        count += [historyItems count];
    }
    
    return count;
}

- (NSArray*)_lastHistoryItemsWithLimit:(int)limit
{
    NSMutableArray* items;
    items = [NSMutableArray array];
    
    int count = 0;
    
    WebHistory*     history;
    NSArray*        lastVisitedDays;
    NSCalendarDate* calendarDate;
    NSEnumerator*   enumerator;
    history = [WebHistory optionalSharedHistory];
    lastVisitedDays = [history orderedLastVisitedDays];
    enumerator = [lastVisitedDays objectEnumerator];
    while (calendarDate = [enumerator nextObject]) {
        // Get history items
        NSArray*    historyItems;
        historyItems = [history orderedItemsLastVisitedOnDay:calendarDate];
        if ([historyItems count] >= limit - count) {
            [items addObjectsFromArray:[historyItems subarrayWithRange:NSMakeRange(0, limit - count)]];
            break;
        }
        
        [items addObjectsFromArray:historyItems];
        count += [historyItems count];
    }
    
    return items;
}

- (int)numberOfItemsInMenu:(NSMenu*)menu
{
    if ([menu delegate] != self) {
        return 0;
    }
    
    // Get web history and last visited days
    WebHistory* history;
    NSArray*    lastVisitedDays;
    history = [WebHistory optionalSharedHistory];
    lastVisitedDays = [history orderedLastVisitedDays];
    
    // For history menu
    if (menu == [SRAppDelegate historyMenu]) {
        return SRNumberOfDefaultHistoryMenu + [lastVisitedDays count];
    }
    // For calendar date menu
    else {
        // Create calendar date from menu title
        NSCalendarDate* calendarDate;
        calendarDate = [[NSCalendarDate alloc] initWithString:[menu title]];
        [calendarDate autorelease];
        
        // Get web history items
        NSArray*    historyItems;
        historyItems = [history orderedItemsLastVisitedOnDay:calendarDate];
        
        return [historyItems count];
    }
    
    return [menu numberOfItems];
}

- (BOOL)menu:(NSMenu*)menu 
        updateItem:(NSMenuItem*)item 
        atIndex:(int)index 
        shouldCancel:(BOOL)shouldCancel
{
    if ([menu delegate] != self) {
        return NO;
    }
    
    // Get web history and last visited days
    WebHistory* history;
    NSArray*    lastVisitedDays;
    history = [WebHistory optionalSharedHistory];
    lastVisitedDays = [history orderedLastVisitedDays];
    
    // For history menu
    if (menu == [SRAppDelegate historyMenu]) {
        if (index < SRNumberOfDefaultHistoryMenu) {
            return YES;
        }
        
        // Get calendar date
        if ([lastVisitedDays count] < index - SRNumberOfDefaultHistoryMenu) {
            return YES;
        }
        NSCalendarDate* calendarDate;
        calendarDate = [lastVisitedDays objectAtIndex:index - SRNumberOfDefaultHistoryMenu];
        
        // Update item
        NSDictionary*   locale;
        locale = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
        [item setTitle:[calendarDate 
                descriptionWithCalendarFormat:NSLocalizedString(@"%a, %B %e", nil) 
                locale:locale]];
        [item setTag:SRHistoryItemTag];
        
        // Create sub menu
        if (![item submenu]) {
            NSMenu* submenu;
            submenu = [[NSMenu alloc] initWithTitle:[calendarDate description]];
            [submenu setDelegate:self];
            
            [item setSubmenu:submenu];
        }
        else {
            NSMenu* submenu;
            submenu = [item submenu];
            [submenu setTitle:[calendarDate description]];
            [submenu update];
        }
        
        return YES;
    }
    // For calendar date menu
    else {
        NSUserDefaults* defaults;
        defaults = [NSUserDefaults standardUserDefaults];
        
        // Check favicon availability
        BOOL    isFaviconUsed, isFaviconUsedBookmarkMenu;
        isFaviconUsed = [defaults boolForKey:SRIconUseFavicon];
        isFaviconUsedBookmarkMenu = [defaults boolForKey:SRIconUseFaviconBookmarkMenu];
        
        // Create calendar date from menu title
        NSCalendarDate* calendarDate;
        calendarDate = [[NSCalendarDate alloc] initWithString:[menu title]];
        [calendarDate autorelease];
        
        // Get web history item
        NSArray*        historyItems;
        WebHistoryItem* historyItem;
        historyItems = [history orderedItemsLastVisitedOnDay:calendarDate];
        if ([historyItems count] < index) {
            return YES;
        }
        historyItem = [historyItems objectAtIndex:index];
        
        // Updaate item
        NSImage*    icon;
        [item setTitle:SRAlternateTitleOfWebHistoryItem(historyItem)];
        [item setAction:@selector(openHistoryItemAction:)];
        icon = [historyItem icon];
        if (isFaviconUsed && isFaviconUsedBookmarkMenu && icon) {
            [item setImage:icon];
        }
        [item setRepresentedObject:historyItem];
        
        return YES;
    }
    
    return YES;
}

@end

#pragma mark -

@implementation SRBookmarkMenuCreator

//--------------------------------------------------------------//
#pragma mark -- NSMenu delegate --
//--------------------------------------------------------------//

- (BOOL)menuHasKeyEquivalent:(NSMenu*)menu 
        forEvent:(NSEvent*)event 
        target:(id*)target 
        action:(SEL*)action
{
    return NO;
}

- (void)_appendBookmarks:(NSArray*)bookmarks inMenu:(NSMenu*)menu
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Check favicon availability
    BOOL    isFaviconUsed, isFaviconUsedBookmarkMenu;
    isFaviconUsed = [defaults boolForKey:SRIconUseFavicon];
    isFaviconUsedBookmarkMenu = [defaults boolForKey:SRIconUseFaviconBookmarkMenu];
    
    // Enumerate bookmark
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    id<NSMenuItem>  menuItem;
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // Case of not folder type
        if (![bookmark isFolderType]) {
            // Create menu item
            menuItem = [menu addItemWithTitle:[bookmark RSSTitle] 
                    action:@selector(openBookmarkAction:) 
                    keyEquivalent:@""];
            [menuItem setRepresentedObject:bookmark];
            if (isFaviconUsed && isFaviconUsedBookmarkMenu) {
                [menuItem setImage:[bookmark icon]];
            }
        }
        
        // Case of bookmark folder
        else {
            // Create menu item and submenu
            SRMenu* submenu;
            menuItem = [menu addItemWithTitle:[bookmark RSSTitle] 
                    action:NULL 
                    keyEquivalent:@""];
            submenu = [[SRMenu alloc] initWithTitle:@""];
            [submenu autorelease];
            [submenu setRepresentedObject:bookmark];
            [submenu setDelegate:self];
            
            [menuItem setSubmenu:submenu];
            if (isFaviconUsed && isFaviconUsedBookmarkMenu) {
                [menuItem setImage:[bookmark icon]];
            }
        }
    }
    
    // Check tab availability
    BOOL    isTabAvailable;
    isTabAvailable = [defaults boolForKey:SRTabEnableTabbedBrowsing];
    if (!isTabAvailable) {
        return;
    }
    
    // Append open all in tabs
    static NSString*    _openBookmarksInTabTitle = nil;
    if (!_openBookmarksInTabTitle) {
        _openBookmarksInTabTitle = [[[SRContextMenu bookmarkContextMenu] itemWithTag:SROpenBookmarkInTabsTag] title];
        [_openBookmarksInTabTitle retain];
    }
// Do not append a separator above open all in tabs item
//    [menu addItem:[NSMenuItem separatorItem]];
    menuItem = [menu addItemWithTitle:_openBookmarksInTabTitle 
            action:@selector(openBookmarkInTabsAction:) 
            keyEquivalent:@""];
    [menuItem setRepresentedObject:bookmarks];
}

- (void)menuNeedsUpdate:(NSMenu*)menu
{
    if ([menu delegate] != self) {
        return;
    }
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Check favicon availability
    BOOL    isFaviconUsed, isFaviconUsedBookmarkMenu;
    isFaviconUsed = [defaults boolForKey:SRIconUseFavicon];
    isFaviconUsedBookmarkMenu = [defaults boolForKey:SRIconUseFaviconBookmarkMenu];
    
    SRBookmarkStorage*  bookmarkStorage;
    bookmarkStorage = [SRBookmarkStorage sharedInstance];
    
    // Get bookmarks bar
    SRBookmark* bookmarksBar = nil;
    switch ([defaults integerForKey:SRBookmarkBookmarksBar]) {
    case SRBrowserShiira: {
        bookmarksBar = [bookmarkStorage shiiraBookmarksBar];
        break;
    }
    case SRBrowserSafari: {
        bookmarksBar = [bookmarkStorage safariBookmarksBar];
        break;
    }
    case SRBrowserFirefox: {
        bookmarksBar = [bookmarkStorage firefoxBookmarksBar];
        break;
    }
    }
    
    // Get bookmarks menu
    NSArray*    shiiraBookmarksMenu;
    SRBookmark* safariBookmarksMenu;
    SRBookmark* firefoxBookmarksMenu;
    shiiraBookmarksMenu = [bookmarkStorage shiiraBookmarksMenu];
    safariBookmarksMenu = [bookmarkStorage safariBookmarksMenu];
    firefoxBookmarksMenu = [bookmarkStorage firefoxBookmarks];
    
    // Get menu avaialability
    NSArray*    isUsing;
    isUsing = [defaults objectForKey:SRBookmarkIsUsing];
    
    NSMenuItem* menuItem;
    
    // For bookmark menu
    if (menu == [SRAppDelegate bookmarkMenu]) {
        // Remove old bookmarks
        int i;
        for (i = [menu numberOfItems] - 1; i > SRNumberOfDefaultBookmarkMenu - 1; i--) {
            [menu removeItemAtIndex:i];
        }
        
        // Bookmarks bar
        if (bookmarksBar) {
            // Create bookmarks bar item
            menuItem = [[NSMenuItem alloc] 
                    initWithTitle:[bookmarksBar title] action:NULL keyEquivalent:@""];
            [menuItem autorelease];
            if (isFaviconUsed && isFaviconUsedBookmarkMenu) {
                [menuItem setImage:[bookmarksBar icon]];
            }
            
            // Create bookmarks bar menu
            SRMenu*     bookmarksBarMenu;
            bookmarksBarMenu = [[SRMenu alloc] initWithTitle:@"Bookmarks bar"];
            [bookmarksBarMenu autorelease];
            [bookmarksBarMenu setRepresentedObject:bookmarksBar];
            [bookmarksBarMenu setDelegate:self];
            [menuItem setSubmenu:bookmarksBarMenu];
            
            // Add bookmarks bar
            [menu addItem:menuItem];
            [menu addItem:[NSMenuItem separatorItem]];
        }
        
        // Safari bookmark availability
        BOOL    isSafariAvailable = NO;
        if ([isUsing count] > SRBrowserSafari) {
            isSafariAvailable = [[isUsing objectAtIndex:SRBrowserSafari] boolValue];
        }
        
        // Firefox bookmark availability
        BOOL    isFirefoxAvailable = NO;
        if ([isUsing count] > SRBrowserFirefox) {
            isFirefoxAvailable = [[isUsing objectAtIndex:SRBrowserFirefox] boolValue];
        }
        
        // Shiira bookmarks menu
        if (shiiraBookmarksMenu && [shiiraBookmarksMenu count] > 0) {
            // When it has only Shiira menu, dose not include menu title
            if (isSafariAvailable || isFirefoxAvailable) {
                menuItem = (NSMenuItem*)[menu addItemWithTitle:
                        [NSString stringWithFormat:@"- %@ -", 
                                NSLocalizedStringFromTable(@"ShiiraBookmarks", @"Bookmark", @"Shiira Bookmarks")] 
                        action:NULL 
                        keyEquivalent:@""];
                [menuItem setEnabled:NO];
            }
            [self _appendBookmarks:shiiraBookmarksMenu inMenu:menu];
        }
        
        // Safari bookmarks menu
        if (isSafariAvailable && safariBookmarksMenu && [[safariBookmarksMenu children] count] > 0) {
            // Add separator
            [menu addItem:[NSMenuItem separatorItem]];
            
            // Add Safari bookmarks menu
            menuItem = (NSMenuItem*)[menu addItemWithTitle:
                    [NSString stringWithFormat:@"- %@ -", 
                            NSLocalizedStringFromTable(@"SafariBookmarks", @"Bookmark", @"Safari Bookmarks")] 
                    action:NULL 
                    keyEquivalent:@""];
            [menuItem setEnabled:NO];
            [self _appendBookmarks:[safariBookmarksMenu children] inMenu:menu];
        }
        
        // Firefox bookmarks menu
        if (isFirefoxAvailable && firefoxBookmarksMenu && [[firefoxBookmarksMenu children] count] > 0) {
            // Add separator
            [menu addItem:[NSMenuItem separatorItem]];
            
            // Add Firefox bookmarks menu
            menuItem = (NSMenuItem*)[menu addItemWithTitle:
                    [NSString stringWithFormat:@"- %@ -", 
                            NSLocalizedStringFromTable(@"FirefoxBookmarks", @"Bookmark", @"Firefox Bookmarks")] 
                    action:NULL 
                    keyEquivalent:@""];
            [menuItem setEnabled:NO];
            [self _appendBookmarks:[firefoxBookmarksMenu children] inMenu:menu];
        }
    }
    // Other sub menu
    else {
        if ([menu isKindOfClass:[SRMenu class]]) {
            // Get represented object
            id  object;
            object = [(SRMenu*)menu representedObject];
            if (object) {
                SRBookmark* bookmark;
                bookmark = (SRBookmark*)object;
                
                // Append boomarks
                if ([[bookmark children] count] > 0) {
                    [self _appendBookmarks:[bookmark children] inMenu:menu];
                }
            }
        }
    }
}

@end

#pragma mark -

@implementation SRAppDelegate

//--------------------------------------------------------------//
#pragma mark -- Initialize --
//--------------------------------------------------------------//

- (id)init
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // Initialize instance variables
    _willSaveHistory = NO;
    
    return self;
}

- (void)_setFactoryDefaults
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Load FactoryDefaults.plist
    NSString*               path;
    NSMutableDictionary*    dict;
    path = [[NSBundle mainBundle] pathForResource:@"FactoryDefaults" ofType:@"plist"];
    if (path) {
        dict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
    }
    else {
        dict = [NSMutableDictionary dictionary];
    }
    
    [dict setObject:[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"] 
            forKey:SRGeneralDownloadPath];
    
    // Remove key for Web Kit defaults, this value is managed by ShiirePreferences
    if ([defaults objectForKey:@"WebKitShowsURLsInToolTips"]) {
        [defaults removeObjectForKey:@"WebKitShowsURLsInToolTips"];
    }
    
    [dict setObject:[NSNumber numberWithUnsignedInt:SRJapaneseAutoDetectEncoding] 
            forKey:SRDefaultTextEncoding];
    
    // Set Sfhit JIS as default encoding, it enables Japanese auto detection
    NSValueTransformer* transformer;
    NSString*           encodingName;
    transformer = [NSValueTransformer valueTransformerForName:SRIANAToEncodingTransformerName];
    encodingName = [transformer reverseTransformedValue:
            [NSNumber numberWithUnsignedInt:SRConvertedShiftJISStringEncoding]];
    [[SRPreferencesController defaultWebPreferences] setDefaultTextEncodingName:encodingName];
    
    NSColor*    color;
    color = [NSColor blackColor];
    [dict setObject:[NSArchiver archivedDataWithRootObject:color] 
            forKey:SRSourceDefaultColor];
    color = [NSColor colorWithDeviceRed:0.1 green:0.1 blue:0.648 alpha:1.0];
    [dict setObject:[NSArchiver archivedDataWithRootObject:color] 
            forKey:SRSourceTagColor];
    color = [NSColor colorWithDeviceRed:0.632 green:0 blue:0 alpha:1.0];
    [dict setObject:[NSArchiver archivedDataWithRootObject:color] 
            forKey:SRSourceCommentColor];
    color = [NSColor whiteColor];
    [dict setObject:[NSArchiver archivedDataWithRootObject:color] 
            forKey:SRSourceBackgroundColor];
    
    // Register factory defaults
    [defaults registerDefaults:dict];
    
    // Swap icon database direcotry
    NSString*   databaseDirectory;
    databaseDirectory = [defaults objectForKey:@"WebIconDatabaseDirectoryDefaultsKey"];
    if (databaseDirectory && 
        ![databaseDirectory isEqualToString:[dict objectForKey:@"WebIconDatabaseDirectoryDefaultsKey"]])
    {
        [defaults setObject:[dict objectForKey:@"WebIconDatabaseDirectoryDefaultsKey"] 
                forKey:@"WebIconDatabaseDirectoryDefaultsKey"];
    }
}

- (void)awakeFromNib
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Check user defaults
    [self _setFactoryDefaults];
    
    // Create WebHistory
    WebHistory* history;
    int         historyAge, days = 7;
    history = [[WebHistory alloc] init];
    historyAge = [defaults integerForKey:SRHistoryAgeInDaysLimit];
    switch (historyAge) {
    case SRHistoryAgeIn1Day: {
        days = 1;
        break;
    }
    case SRHistoryAgeIn3Days: {
        days = 3;
        break;
    }
    case SRHistoryAgeIn1Week: {
        days = 7;
        break;
    }
    case SRHistoryAgeIn2Weeks: {
        days = 14;
        break;
    }
    }
    [history setHistoryAgeInDaysLimit:days];
    [WebHistory setOptionalSharedHistory:history];
    
    // Load history and make history menu
    [self loadHistory];
    
    // Create download center
    [SRDownloadCenter sharedInstance];
    
    // Load download history
    [[SRDownloadHistory sharedInstance] loadDownloadHistory];
    [[SRDownloadHistory sharedInstance] setHistoryAgeInDaysLimit:days];
    
    // Start cache management
    [SRCacheManager startCacheManager];
    
    // Create visual history storage
    //[SRVisualHistoryStorage sharedInstance];
    
    // Add date formats
    NSString*   path;
    path = [[NSBundle mainBundle] pathForResource:@"DateFormats" ofType:@"plist"];
    if (path) {
        [NSDate addDateFromatsWithContentsOfFile:path];
    }
    
    // Load SRXML
	NSBundle*	mainBundle;
	NSBundle*	xmlBundle;
	mainBundle = [NSBundle mainBundle];
	path = [mainBundle privateFrameworksPath];
    // For Tiger
    if (NSAppKitVersionNumber >= SRAppKitVersionNumber10_4) {
        path = [path stringByAppendingPathComponent:@"SRXMLTiger.framework"];
        //path = [path stringByAppendingPathComponent:@"SRXMLPanther.framework"];
    }
    // For Panther
    else {
        path = [path stringByAppendingPathComponent:@"SRXMLPanther.framework"];
    }
	xmlBundle = [NSBundle bundleWithPath:path];
	if (!xmlBundle) {
		NSLog(@"Can't make bundle at path %@", path);
	}
	else {
		if (![xmlBundle load]) {
			NSLog(@"Can't load SRXML bundle");
		}
	}
    
#ifdef SR_SUPPORT_RSS
    // Load not update feeds
    [[SRRSSManager sharedInstance] loadNotUpdateFeeds];
#endif // SR_SUPPORT_RSS
    
    // Update shortcut of 'Show All Tabs'
    int shortcutTag;
    shortcutTag = [defaults integerForKey:SRTabShowAllTabsShortcut];
    if (shortcutTag >= 0 && shortcutTag <= 14) {
        id <NSMenuItem> showAllTabsItem;
        showAllTabsItem = [[SRAppDelegate windowMenu] itemWithTag:SRShowAllTabsTag];
        
        unichar c;
        c = NSF1FunctionKey + shortcutTag;
        [showAllTabsItem setKeyEquivalent:[NSString stringWithCharacters:&c length:1]];
    }
    
    // Set menu delegate
    _historyMenuCreator = [[SRHistoryMenuCreator alloc] init];
    [[SRAppDelegate historyMenu] setDelegate:_historyMenuCreator];
    _bookmarkMenuCreator = [[SRBookmarkMenuCreator alloc] init];
    [[SRAppDelegate bookmarkMenu] setDelegate:_bookmarkMenuCreator];
    
    // Update protocol library
    [self updateProtocolLibary];
    
    // Register notification
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    
    [center addObserver:self 
            selector:@selector(webHistoryItemsUpdated:) name:WebHistoryItemsAddedNotification object:nil];
    [center addObserver:self 
            selector:@selector(webHistoryItemsUpdated:) name:WebHistoryItemsRemovedNotification object:nil];
    [center addObserver:self 
            selector:@selector(webHistoryItemsUpdated:) name:WebHistoryAllItemsRemovedNotification object:nil];
    
    [center addObserver:self 
            selector:@selector(bookmarkUpdated:) name:SRBookmarkAddedNotificationName object:nil];
    [center addObserver:self 
            selector:@selector(bookmarkUpdated:) name:SRBookmarkRemovedNotificationName object:nil];
    [center addObserver:self 
            selector:@selector(bookmarkUpdated:) name:SRBookmarkChangedNotificationName object:nil];
    
#ifdef SR_SUPPORT_RSS
    [center addObserver:self 
            selector:@selector(rssUpdated:) name:SRRSSWillStartRefresh object:nil];
    [center addObserver:self 
            selector:@selector(rssUpdated:) name:SRRSSProgressRefresh object:nil];
    [center addObserver:self 
            selector:@selector(rssUpdated:) name:SRRSSDidEndRefresh object:nil];
    [center addObserver:self 
            selector:@selector(rssUpdated:) name:SRRSSItemsRemovedAll object:nil];
    [center addObserver:self 
            selector:@selector(rssUpdated:) name:SRRSSItemsPreviewed object:nil];
    [center addObserver:self 
            selector:@selector(rssUpdated:) name:SRRSSFeedsDidChanged object:nil];
#endif // SR_SUPPORT_RSS
    
    // Load search engines
    [[SRSearchEnginesManager sharedInstance] loadSearchEngines];
    
    // Register key value observation
    [defaults addObserver:self 
            forKeyPath:SRProtocolLibrary options:NSKeyValueObservingOptionNew context:NULL];
    [defaults addObserver:self 
            forKeyPath:SRBookmarkBookmarksBar options:NSKeyValueObservingOptionNew context:NULL];
    [defaults addObserver:self 
            forKeyPath:SRIconUseFavicon options:NSKeyValueObservingOptionNew context:NULL];
    [defaults addObserver:self 
            forKeyPath:SRTabShowAllTabsShortcut options:NSKeyValueObservingOptionNew context:NULL];
    [defaults addObserver:self 
            forKeyPath:SRRSSShowOnDock options:NSKeyValueObservingOptionNew context:NULL];
}

- (void)dealloc
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    [_findWindowController release];
    [_preferencesController release];
    [_downloadsController release];
    [_searchEnginesController release];
    [_cacheController release];
    [_iconInstaller release];
    [_historyMenuCreator release];
	[_jsErrorLogController release];
    
	[[NSNotificationCenter defaultCenter] removeObserver:self];
    
    [defaults removeObserver:self forKeyPath:SRBookmarkBookmarksBar];
    [defaults removeObserver:self forKeyPath:SRIconUseFavicon];
    [super dealloc];
}

- (void)applicationWillFinishLaunching:(NSNotification*)notification
{
	[[NSAppleEventManager sharedAppleEventManager]
            setEventHandler:self
		    andSelector:@selector(openURL:withReplyEvent:)
            forEventClass:'GURL' andEventID:'GURL']; 
}

- (void)applicationDidFinishLaunching:(NSNotification*)notification
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Create new document if there is no document
    NSDocumentController*   documentController;
    documentController = [NSDocumentController sharedDocumentController];
    if ([[documentController documents] count] == 0) {
        [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    }
    
// For Growl support
#ifdef SR_SUPPORT_GROWL
#if 1
    NSString*   growlPath;
    NSBundle*   growlBundle;
    growlPath = [[[NSBundle mainBundle] privateFrameworksPath] stringByAppendingPathComponent:@"Growl.framework"];
    growlBundle = [NSBundle bundleWithPath:growlPath];
    if (growlBundle && [growlBundle load]) {
        [GrowlApplicationBridge setGrowlDelegate:self];
    }
#else
// For old Growl
    // Launch Growl
    [GrowlAppBridge launchGrowlIfInstalledNotifyingTarget:self 
            selector:@selector(registerGrowl:) 
            context:NULL];
#endif
#endif // SR_SUPPORT_GROWL
    
// For RSS support
#ifdef SR_SUPPORT_RSS
    // Register feed protocol
    //[NSURLProtocol registerClass:[SRFeedURLProtocol class]];
    
    // Refresh all feeds
    if ([[NSUserDefaults standardUserDefaults] boolForKey:SRRSSEnable]) {
        int RSSUpdate;
        RSSUpdate = [[NSUserDefaults standardUserDefaults] integerForKey:SRRSSUpdate];
        if (RSSUpdate != SRRSSUpdateNever) {
            [[SRRSSManager sharedInstance] refreshAllFeeds];
        }
    }
#endif
    
    // Setup sound
    NSArray*        actionNames;
    NSEnumerator*   enumerator;
    NSString*       actionName;
    actionNames = [NSArray arrayWithObjects:SRSoundPageLoadDone, SRSoundPageLoadError, nil];
    enumerator = [actionNames objectEnumerator];
    while (actionName = [enumerator nextObject]) {
        // Set sound
        NSString*   soundPath;
        soundPath = [defaults stringForKey:actionName];
        if (soundPath) {
            [self setSound:soundPath forAction:actionName];
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- NSApplication delegate --
//--------------------------------------------------------------//

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender
{
    // Check download center
    NSArray*                downloadItems;
    NSEnumerator*           enumerator;
    SRDownloadHistoryItem*  downloadItem;
    int                     activeDownloadNum = 0;
    downloadItems = [[SRDownloadCenter sharedInstance] downloadItems];
    enumerator = [downloadItems objectEnumerator];
    while (downloadItem = [enumerator nextObject]) {
        if ([downloadItem status] == SRDownloadStatusActive) {
            activeDownloadNum++;
        }
    }
    
    if (activeDownloadNum > 0) {
        // Show alert
        NSAlert*    alert;
        alert = [[NSAlert alloc] init];
        [alert autorelease];
        [alert setAlertStyle:NSCriticalAlertStyle];
        [alert setMessageText:NSLocalizedString(@"Downloads are in progress.", @"Downloads are in progress.")];
        [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"Are you sure you want to quit? Shiira is currently downloading %d files. If you quit now, it will not finish downloading these files.", @"Are you sure you want to quit? Shiira is currently downloading %d files. If you quit now, it will not finish downloading these files."), activeDownloadNum]];
        [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK")];
        [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"Cancel")];
        
        int result;
        result = [alert runModal];
        if (result != NSAlertFirstButtonReturn) {
            return NSTerminateCancel;
        }
        
        // Pause active items
        enumerator = [downloadItems objectEnumerator];
        while (downloadItem = [enumerator nextObject]) {
            if ([downloadItem status] == SRDownloadStatusActive) {
                [[SRDownloadCenter sharedInstance] pauseDownloadForItem:downloadItem];
            }
        }
    }
    
    return NSTerminateNow;
}

- (void)applicationWillTerminate:(NSNotification*)notification
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Remove active download items
    if ([defaults integerForKey:SRDownloadItemRemove] == SRDownloadItemRemoveAtTremination) {
        [[SRDownloadCenter sharedInstance] removeAllCompletedActiveItems];
    }
    
    // Remove cookies
    if ([defaults boolForKey:SRCookieRemoveAtTermination]) {
        NSHTTPCookieStorage*    storage;
        NSEnumerator*           enumerator;
        NSHTTPCookie*           cookie;
        storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        enumerator = [[storage cookies] objectEnumerator];
        while (cookie = [enumerator nextObject]) {
            [storage deleteCookie:cookie];
        }
        
        // Save cookie change forcefully
        Ivar    ivar;
        void*   internal = NULL;
        ivar = object_getInstanceVariable(storage, "_internal", &internal);
        if (internal) {
            void*   diskStorage = NULL;
            ivar = object_getInstanceVariable((id)internal, "storage", &diskStorage);
            if (diskStorage) {
                objc_msgSend(diskStorage, @selector(_saveCookies));
            }
        }
    }
    
    // Remove cache
    if ([defaults boolForKey:SRCacheRemoveAtTermination]) {
        [[NSURLCache sharedURLCache] removeAllCachedResponses];
    }
    
    // Save settings
    [self saveHistory];
    [[SRBookmarkStorage sharedInstance] saveBookmarks];
    [[SRSearchEnginesManager sharedInstance] saveSearchEngines];
    [[SRDownloadHistory sharedInstance] saveDownloadHistory];
    
#ifdef SR_SUPPORT_RSS
    [[SRRSSManager sharedInstance] saveFeeds];
    [[SRRSSManager sharedInstance] seveNotUpdateFeeds];
    [[SRRSSManager sharedInstance] terminateRSSSyndication];
#endif // SR_SUPPORT_RSS
    
    // Reset app icon
    [NSApp setApplicationIconImage:[NSImage imageNamed:@"NSApplicationIcon"]];
    
    // Sync user defaults
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    // Remove tmp directory
    NSString*   tmpPathDir;
    tmpPathDir = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"Shiira"]];
    if ([[NSFileManager defaultManager] fileExistsAtPath:tmpPathDir]) {
        BOOL    result;
        result = [[NSFileManager defaultManager] removeFileAtPath:tmpPathDir handler:NULL];
    }
    
    // Get path
    NSString*   dirPath;
    dirPath = [SRRSSManager feedsFolderPath];
    dirPath = [dirPath stringByAppendingPathComponent:@"tmp"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:dirPath]) {
        NSDirectoryEnumerator*  enumerator;
        NSString*               file;
        enumerator = [[NSFileManager defaultManager] enumeratorAtPath:dirPath];
        while (file = [enumerator nextObject]) {
            // Remove inner*.xml file
            if ([[file pathExtension] isEqualToString:@"xml"] && 
                [file hasPrefix:@"inner"])
            {
                NSString*   path;
                path = [dirPath stringByAppendingPathComponent:file];
                [[NSFileManager defaultManager] removeFileAtPath:path handler:NULL];
            }
        }
    }
    
    // Release sound action
    if (_pageLoadDoneSoundActionId > 0) {
        SystemSoundRemoveActionID(_pageLoadDoneSoundActionId);
    }
    if (_pageLoadErrorSoundActionId > 0) {
        SystemSoundRemoveActionID(_pageLoadErrorSoundActionId);
    }
}

- (BOOL)applicationShouldHandleReopen:(NSApplication*)application hasVisibleWindows:(BOOL)flag
{
    // Create new document when there is no window
    NSDocumentController*   documentController;
    documentController = [NSDocumentController sharedDocumentController];
    if (flag == NO) {
        [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
        return NO;
    }
    
    // Check there is browser window or not
    NSArray*        windows;
    NSEnumerator*   enumerator;
    NSWindow*       window;
    NSWindow*       miniaturizedWindow = nil;
    windows = [NSApp windows];
    enumerator = [windows objectEnumerator];
    while (window = [enumerator nextObject]) {
        id  windowController;
        windowController = [window windowController];
        if (!windowController) {
            continue;
        }
        
        if ([windowController isKindOfClass:[SRMainWindowController class]]) {
            // When window is miniaturized
            if ([window isMiniaturized]) {
                miniaturizedWindow = window;
                continue;
            }
            
            // There is a visible window
            return NO;
        }
    }
    
    // Show miniaturized window if it has one
    if (miniaturizedWindow) {
        [miniaturizedWindow deminiaturize:self];
        return NO;
    }
    
    // Create new document
    [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    
    return NO;
}

//--------------------------------------------------------------//
#pragma mark -- Preferences --
//--------------------------------------------------------------//

- (SRPreferencesController*)preferencesController
{
    if (!_preferencesController) {
        // Load preferences panel
        _preferencesController = [[SRPreferencesController alloc] init];
        if (![NSBundle loadNibNamed:@"Preferences" owner:_preferencesController]) {
            // Fatal
            SR_FATAL(@"Could not load Preferences.nib");
        }
    }
    
    return _preferencesController;
}

//--------------------------------------------------------------//
#pragma mark -- Menu --
//--------------------------------------------------------------//

+ (NSMenu*)fileMenu
{
    return [[[NSApp mainMenu] itemWithTag:SRFileTag] submenu];
}

+ (NSMenu*)editMenu
{
    return [[[NSApp mainMenu] itemWithTag:SREditTag] submenu];
}

+ (NSMenu*)viewMenu
{
    return [[[NSApp mainMenu] itemWithTag:SRViewTag] submenu];
}

+ (NSMenu*)browserMenu
{
    return [[[[[NSApp mainMenu] itemWithTag:SRViewTag] 
            submenu] itemWithTag:SROpenURLWithTag] submenu];
}

+ (NSMenu*)textEncodingMenu
{
    return [[[[[NSApp mainMenu] itemWithTag:SRViewTag] 
            submenu] itemWithTag:SRTextEncodingTag] submenu];
}

+ (NSMenu*)historyMenu
{
    return [[[NSApp mainMenu] itemWithTag:SRHistoryTag] submenu];
}

+ (NSMenu*)bookmarkMenu
{
    return [[[NSApp mainMenu] itemWithTag:SRBookmarkTag] submenu];
}

+ (NSMenu*)windowMenu
{
    return [[[NSApp mainMenu] itemWithTag:SRWindowTag] submenu];
}

//--------------------------------------------------------------//
#pragma mark -- Text encoding menu --
//--------------------------------------------------------------//

- (void)updateTextEncodingMenu
{
    // Get encoding menu
    NSMenu* encodingMenu;
    encodingMenu = [SRAppDelegate textEncodingMenu];
    
    // Append text encoding menu
    NSArray*        encodingItems;
    NSEnumerator*   enumerator;
    id<NSMenuItem>  item;
    encodingItems = [SREncodings textEncodingMenuItems];
    enumerator = [encodingItems objectEnumerator];
    while (item = [enumerator nextObject]) {
        // Except Japanese auto encoding
        if ([item tag] != SRJapaneseAutoDetectEncoding) {
            [encodingMenu addItem:item];
        }
    }
    
    // Set action for 'Default'
    id<NSMenuItem>  defaultItem;
    defaultItem = [encodingMenu itemAtIndex:0];
    [defaultItem setAction:[[encodingItems objectAtIndex:0] action]];
}

//--------------------------------------------------------------//
#pragma mark -- Application support folder --
//--------------------------------------------------------------//

- (NSString*)libraryFolder
{
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    
    // Get the paths of ~/Library/
    NSArray*	libraryPaths;
    NSString*	libraryPath = nil;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    if ([libraryPaths count] > 0) {
        libraryPath = [libraryPaths objectAtIndex:0];
    }
    
    if (!libraryPath || ![fileMgr fileExistsAtPath:libraryPath]) {
        // Error
        SR_ERROR(@"Could not find library directory");
        return nil;
    }
    
    // Check Shiira directory
    NSString*   shiiraPath;
    shiiraPath = [libraryPath stringByAppendingPathComponent:@"Shiira"];
    if (![fileMgr fileExistsAtPath:shiiraPath]) {
        // Create Shiira directory
        [fileMgr createDirectoryAtPath:shiiraPath attributes:nil];
    }
    
    return shiiraPath;
}

- (NSString*)applicationSupportFolder
{
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    
    // Get the paths of ~/Library/Application Support/
    NSArray*	libraryPaths;
    NSString*	libraryPath = nil;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSApplicationSupportDirectory, NSUserDomainMask, YES);
    if ([libraryPaths count] > 0) {
        libraryPath = [libraryPaths objectAtIndex:0];
    }
    
    if (!libraryPath || ![fileMgr fileExistsAtPath:libraryPath]) {
        // Error
        SR_ERROR(@"Could not find applicaiton support directory");
        return nil;
    }
    
    // Check Shiira directory
    NSString*   shiiraPath;
    shiiraPath = [libraryPath stringByAppendingPathComponent:@"Shiira"];
    if (![fileMgr fileExistsAtPath:shiiraPath]) {
        // Create Shiira directory
        [fileMgr createDirectoryAtPath:shiiraPath attributes:nil];
    }
    
    return shiiraPath;
}

//--------------------------------------------------------------//
#pragma mark -- Bookmark menu --
//--------------------------------------------------------------//

- (void)insertBookmarks:(NSArray*)bookmarks 
        inMenu:(NSMenu*)menu 
        atIndex:(int)index
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Check favicon availability
    BOOL    isFaviconUsed;
    isFaviconUsed = [defaults boolForKey:SRIconUseFavicon];
    
    // Enumerate bookmark
    NSEnumerator*   enumerator;
    SRBookmark*     bookmark;
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        // Case of HTML type
        if (![bookmark isFolderType]) {
            // Create menu item
            NSMenuItem* menuItem;
            menuItem = [[NSMenuItem alloc] initWithTitle:[bookmark title] 
                    action:@selector(openBookmarkAction:) 
                    keyEquivalent:@""];
            [menuItem autorelease];
            [menuItem setRepresentedObject:bookmark];
            if (isFaviconUsed) {
                [menuItem setImage:[bookmark icon]];
            }
            
            [menu insertItem:menuItem atIndex:index++];
        }
        
        // Case of bookmark folder
        else {
            // Create menu item and submenu
            NSMenuItem* menuItem;
            NSMenu*     submenu;
            menuItem = [[NSMenuItem alloc] initWithTitle:[bookmark title] 
                    action:NULL 
                    keyEquivalent:@""];
            [menuItem autorelease];
            submenu = [[NSMenu alloc] initWithTitle:@""];
            [submenu autorelease];
            [menuItem setSubmenu:submenu];
            if (isFaviconUsed) {
                [menuItem setImage:[bookmark icon]];
            }
            
            [menu insertItem:menuItem atIndex:index++];
            
            // Insert child menu
            [self insertBookmarks:[bookmark children] inMenu:submenu atIndex:0];
        }
    }
}

- (void)updateBookmarkMenu
{
    NSMenu*         bookmarkMenu;
    NSEnumerator*   enumerator;
    NSMenuItem*     menuItem;
    bookmarkMenu = [SRAppDelegate bookmarkMenu];
    [bookmarkMenu update];
    
    enumerator = [[bookmarkMenu itemArray] objectEnumerator];
    while (menuItem = [enumerator nextObject]) {
        if ([menuItem submenu]) {
            [[menuItem submenu] update];
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- History menu --
//--------------------------------------------------------------//

- (void)loadHistory
{
    // Get the paths of ~/Library/Shiira/History.plist
    NSArray*	libraryPaths;
    NSString*	historyPath;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    historyPath = [[libraryPaths objectAtIndex:0] 
            stringByAppendingPathComponent:SRHistoryFileName];
    
    // Check existense
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:historyPath]) {
        return;
    }
    
    // Load history
    WebHistory* history;
    NSError*    error;
    history = [WebHistory optionalSharedHistory];
    if (![history loadFromURL:[NSURL fileURLWithPath:historyPath] error:&error]) {
        // Error
        return;
    }
}

- (void)saveHistory
{
    // Get the paths of ~/Library/Shiira/Bookmarks.plist
    NSArray*	libraryPaths;
    NSString*	historyPath;
    libraryPaths = NSSearchPathForDirectoriesInDomains(
            NSLibraryDirectory, NSUserDomainMask, YES);
    historyPath = [[libraryPaths objectAtIndex:0] 
            stringByAppendingPathComponent:SRHistoryFileName];
    
    // Check existense
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:historyPath]) {
        // Create file
        SRCreateFile(historyPath);
    }
    
    // Save history
    WebHistory* history;
    NSError*    error;
    history = [WebHistory optionalSharedHistory];
    if (![history saveToURL:[NSURL fileURLWithPath:historyPath] error:&error]) {
        // Error
        return;
    }
}

- (void)updateHistoryMenu
{
    NSMenu* historyMenu;
    historyMenu = [SRAppDelegate historyMenu];
    [historyMenu update];
}

//--------------------------------------------------------------//
#pragma mark -- WebHistory notification --
//--------------------------------------------------------------//

- (void)webHistoryItemsUpdated:(NSNotification*)notification
{
    // Update history menu
    [self updateHistoryMenu];
    
    // Save history after delay
    if (!_willSaveHistory) {
        [self performSelector:@selector(_saveHistory) withObject:nil afterDelay:60];
        
        _willSaveHistory = YES;
    }
}

- (void)_saveHistory
{
    // Save search engines
    [self saveHistory];
    
    _willSaveHistory = NO;
}

//--------------------------------------------------------------//
#pragma mark -- SRBookmarkStorage notification --
//--------------------------------------------------------------//

- (void)bookmarkUpdated:(NSNotification*)notification
{
    // Update bookmark menu
    [self updateBookmarkMenu];
}

//--------------------------------------------------------------//
#pragma mark -- Browser menu --
//--------------------------------------------------------------//

- (void)updateBrowserMenu
{
    // Get broser menu
    NSMenu* browserMenu;
    browserMenu = [SRAppDelegate browserMenu];
    
    // Remove all items
    int i;
    for (i = [browserMenu numberOfItems] - 1; i >= 0; i--) {
        [browserMenu removeItemAtIndex:i];
    }
    
    // Update browser menu
    NSArray*        browserMenuItems;
    NSEnumerator*   enumerator;
    NSMenuItem*     menuItem;
    browserMenuItems = [self browserMenuItemsWithShiira:NO action:@selector(openURLWithAction:)];
    enumerator = [browserMenuItems objectEnumerator];
    while (menuItem = [enumerator nextObject]) {
        [browserMenu addItem:menuItem];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Search engines --
//--------------------------------------------------------------//

- (SRSearchEnginesController*)searchEnginesController
{
    if (!_searchEnginesController) {
        // Load search engines panel
        _searchEnginesController = [[SRSearchEnginesController alloc] initWithWindowNibName:@"SearchEngines"];
        if (!_searchEnginesController) {
            // Fatal
            SR_FATAL(@"Could not load SearchEngines.nib");
        }
        [_searchEnginesController window];
    }
    
    return _searchEnginesController;
}

- (SRFindWindowController*)findWindowController
{
    if (!_findWindowController) {
        // Load find panel
        _findWindowController = [[SRFindWindowController alloc] initWithWindowNibName:@"FindPanel"];
        if (!_findWindowController) {
            // Fatal
            SR_FATAL(@"Could not load FindPanel.nib");
        }
        //load nib
        [_findWindowController window];
    }
    
    return _findWindowController;
}

//--------------------------------------------------------------//
#pragma mark -- Downloads --
//--------------------------------------------------------------//

- (SRDownloadsController*)downloadsController
{
    if (!_downloadsController) {
        // Load downloads panel
        _downloadsController = [[SRDownloadsController alloc] initWithWindowNibName:@"Downloads"];
        if (!_downloadsController) {
            // Fatal
            SR_FATAL(@"Could not load Downloads.nib");
        }
    }
    
    return _downloadsController;
}

//--------------------------------------------------------------//
#pragma mark -- Cache --
//--------------------------------------------------------------//

- (SRCacheController*)cacheController
{
    if (!_cacheController) {
        // Load cache panel
        _cacheController = [[SRCacheController alloc] initWithWindowNibName:@"Cache"];
        if (!_cacheController) {
            // Fatal
            SR_FATAL(@"Could not load Cache.nib");
        }
    }
    
    return _cacheController;
}

//--------------------------------------------------------------//
#pragma mark -- JavaScript Error Log --
//--------------------------------------------------------------//

- (SRJSErrorLogController*)javaScriptErrorLogController
{
    if (!_jsErrorLogController) {
        // Load search engines panel
        _jsErrorLogController = [[SRJSErrorLogController alloc] initWithWindowNibName:@"JavaScriptErrorLog"];
        if (!_jsErrorLogController) {
            // Fatal
            SR_FATAL(@"Could not load JavaScriptErrorLog.nib");
        }
        [_jsErrorLogController window];
    }
    
    return _jsErrorLogController;
}

//--------------------------------------------------------------//
#pragma mark -- Icon installer --
//--------------------------------------------------------------//

- (SRIconInstaller*)iconInstaller
{
    if (!_iconInstaller) {
        // Create icon installer
        _iconInstaller = [[SRIconInstaller alloc] init];
        
        // Load 'IconInstaller.nib'
        if (![NSBundle loadNibNamed:@"IconInstaller" owner:_iconInstaller]) {
            // Fatal
            SR_FATAL(@"Could not load IconInstaller.nib");
        }
    }
    
    return _iconInstaller;
}

//--------------------------------------------------------------//
#pragma mark -- Protocol library --
//--------------------------------------------------------------//

- (void)updateProtocolLibary
{
// Drop it
#if 0
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    static BOOL _isCURLRegistered = NO;
    
    // Get protocol library
    int library;
    library = [defaults integerForKey:SRProtocolLibrary];
    
    if (library == SRProtocolLibraryWebKit) {
        if (_isCURLRegistered) {
            // Unregister URL protocol
            [NSURLProtocol unregisterClass:[SRHTTPURLProtocol class]];
            _isCURLRegistered = NO;
        }
    }
    if (library == SRProtocolLibraryCURL) {
        if (!_isCURLRegistered) {
            // Register URL protocol
            [NSURLProtocol registerClass:[SRHTTPURLProtocol class]];
            _isCURLRegistered = YES;
        }
    }
#endif
}

//--------------------------------------------------------------//
#pragma mark -- Sound --
//--------------------------------------------------------------//

- (SystemSoundActionID)soundForAction:(NSString*)actionName
{
    // Return action ID
    if ([actionName isEqualToString:SRSoundPageLoadDone]) {
        return _pageLoadDoneSoundActionId;
    }
    if ([actionName isEqualToString:SRSoundPageLoadError]) {
        return _pageLoadErrorSoundActionId;
    }
    
    return 0;
}

- (SystemSoundActionID)setSound:(NSString*)soundFilePath forAction:(NSString*)actionName
{
    if (!soundFilePath || !actionName) {
        return 0;
    }
    
    // Get action ID variable
    SystemSoundActionID*    actionId = NULL;
    if ([actionName isEqualToString:SRSoundPageLoadDone]) {
        actionId = &_pageLoadDoneSoundActionId;
    }
    if ([actionName isEqualToString:SRSoundPageLoadError]) {
        actionId = &_pageLoadErrorSoundActionId;
    }
    
    if (!actionId) {
        return 0;
    }
    
    SystemSoundActionID soundActionId = 0;
    
    // For 'None'
    if ([soundFilePath isEqualToString:@"None"]) {
        if (*actionId) {
            SystemSoundRemoveActionID(*actionId);
        }
        *actionId = 0;
    }
    else {
        // Create action ID
        OSStatus            err;
        FSRef               soundFileRef;
        err = FSPathMakeRef((const UInt8*)[soundFilePath fileSystemRepresentation], &soundFileRef, NULL);
        if (err != noErr) {
            return 0;
        }
        err = SystemSoundGetActionID(&soundFileRef, &soundActionId);
        if (err != noErr) {
            return 0;
        }
        
        // Set action ID
        if (*actionId) {
            SystemSoundRemoveActionID(*actionId);
        }
        *actionId = soundActionId;
    }
    
    // Update defaults
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    if (![soundFilePath isEqualToString:[defaults stringForKey:actionName]]) {
        [defaults setObject:soundFilePath forKey:actionName];
    }
    
    return soundActionId;
}

//--------------------------------------------------------------//
#pragma mark -- For Growl --
//--------------------------------------------------------------//

// For Growl support
#ifdef SR_SUPPORT_GROWL

- (NSDictionary*)registrationDictionaryForGrowl
{
    // Create registration information for Growl
    NSArray*        notifications;
    NSDictionary*   infoDict;
    notifications = [NSArray arrayWithObjects:
            SRGrowlDownloadCompletedNotification, 
            SRGrowlRSSComesNotification, 
            nil];
    infoDict = [NSDictionary dictionaryWithObjectsAndKeys:
            notifications, GROWL_NOTIFICATIONS_ALL, 
            notifications, GROWL_NOTIFICATIONS_DEFAULT, 
            nil];
    
    return infoDict;
}

#if 0
// For old Growl
- (void)registerGrowl:(void*)context
{
    // Activate itself
    [NSApp activateIgnoringOtherApps:YES];
    
    // Get application name
    NSString*   appName;
    appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
    
    // Create registration information for Growl
    NSArray*        notifications;
    NSDictionary*   infoDict;
    notifications = [NSArray arrayWithObjects:
            SRGrowlDownloadCompletedNotification, 
            nil];
    infoDict = [NSDictionary dictionaryWithObjectsAndKeys:
            appName, GROWL_APP_NAME, 
            [[NSApp applicationIconImage] TIFFRepresentation], GROWL_APP_ICON, 
            notifications, GROWL_NOTIFICATIONS_ALL, 
            notifications, GROWL_NOTIFICATIONS_DEFAULT, 
            nil];
    
    // Register notifications
	[[NSDistributedNotificationCenter defaultCenter] 
            postNotificationName:GROWL_APP_REGISTRATION 
            object:nil 
            userInfo:infoDict];
}
#endif

#endif // SR_SUPPORT_GROWL

//--------------------------------------------------------------//
#pragma mark -- RSS --
//--------------------------------------------------------------//

#ifdef SR_SUPPORT_RSS

- (void)rssUpdated:(NSNotification*)notification
{
    // Check user defaults
    if (![[NSUserDefaults standardUserDefaults] boolForKey:SRRSSShowOnDock]) {
        // Reset app icon
        [NSApp setApplicationIconImage:[NSImage imageNamed:@"NSApplicationIcon"]];
    }
    else {
        // Get not read articles number
        int count;
        count = [[SRRSSManager sharedInstance] numberOfNotPreviewedItems];
        
        // Get application icon
        NSImage*    icon;
        icon = [NSImage imageNamed:@"NSApplicationIcon"];
        
        // When no articles
        if (count == 0) {
            [NSApp setApplicationIconImage:icon];
            return;
        }
        
        // Composite counts on the application icon
        NSImage*    image;
        NSImage*    badge;
        NSRect      srcRect, destRect;
        image = [[NSImage alloc] initWithSize:[icon size]];
        if (count < 100) {
            badge = [NSImage imageNamed:@"dockBadge1-2"];
        }
        else if (count < 1000) {
            badge = [NSImage imageNamed:@"dockBadge3"];
        }
        else if (count < 10000) {
            badge = [NSImage imageNamed:@"dockBadge4"];
        }
        else {
            badge = [NSImage imageNamed:@"dockBadge5"];
        }
        
        [image lockFocus];
        
        // Draw icon
        srcRect.origin = NSZeroPoint;
        srcRect.size = [icon size];
        [icon drawAtPoint:NSZeroPoint fromRect:srcRect operation:NSCompositeSourceOver fraction:1.0f];
        
        // Draw badge
        static float    _alpha = 0.8f;
        srcRect.origin = NSZeroPoint;
        srcRect.size = [badge size];
        destRect.origin.x = 0;
        destRect.origin.y = [icon size].height - srcRect.size.height;
        destRect.size = srcRect.size;
        [badge drawInRect:destRect fromRect:srcRect operation:NSCompositeSourceOver fraction:_alpha];
        
        // Draw count
        static NSDictionary*    _attr = nil;
        static float            _ascender = 0.0f;
        if (!_attr) {
            NSFont* font;
            font = [NSFont boldSystemFontOfSize:24.0f];
            _attr = [[NSDictionary dictionaryWithObjectsAndKeys:
                    [NSColor colorWithCalibratedWhite:1.0f alpha:_alpha], NSForegroundColorAttributeName, 
                    font, NSFontAttributeName, 
                    nil] retain];
            _ascender = [font ascender];
        }
        
        NSAttributedString* attrStr;
        NSSize              strSize;
        attrStr = [[NSAttributedString alloc] 
                initWithString:[NSString stringWithFormat:@"%d", count] attributes:_attr];
        strSize = [attrStr size];
        [attrStr drawInRect:NSMakeRect(
                destRect.origin.x + (destRect.size.width - strSize.width) / 2.0f, 
                destRect.origin.y + (destRect.size.height - _ascender) / 2.0f - 2.0f, 
                strSize.width, 
                strSize.height)];
        [attrStr release];
        
        [image unlockFocus];
        
        [NSApp setApplicationIconImage:image];
        [image release];
    }
    
// For Growl support
#ifdef SR_SUPPORT_GROWL
    if ([[notification name] isEqualToString:SRRSSDidEndRefresh]) {
        int newComingArticles;
        newComingArticles = [[SRRSSManager sharedInstance] numberOfNewComingArticles];
        if (newComingArticles > 0) {
            // Notify Growl
            NSString*   description;
            if (newComingArticles == 1) {
                description = NSLocalizedString(@"There is an new article", nil);
            }
            else {
                description = [NSString stringWithFormat:
                        NSLocalizedString(@"There are %d new articles", nil), newComingArticles];
            }
            
            [GrowlApplicationBridge notifyWithTitle:NSLocalizedString(@"New RSS articles come", nil) 
                    description:description 
                    notificationName:SRGrowlRSSComesNotification 
                    iconData:nil 
                    priority:0 
                    isSticky:NO 
                    clickContext:nil];
        }
    }
#endif // SR_SUPPORT_GROWL
}

#endif // SR_SUPPORT_RSS

//--------------------------------------------------------------//
#pragma mark -- Actions --
//--------------------------------------------------------------//

- (IBAction)findAction:(id)sender
{
    // Show find panel
    [[self findWindowController] showWindow:self];
}

- (IBAction)findNextAction:(id)sender
{
    // Pass through to find panel
    [[self findWindowController] findNextAction:sender];
}

- (IBAction)findPreviousAction:(id)sender
{
    // Pass through to find panel
    [[self findWindowController] findPreviousAction:sender];
}

- (IBAction)useSelectionForFindAction:(id)sender
{
    // Pass through to find panel
    [[self findWindowController] useSelectionForFindAction:sender];
}

#if 0
- (IBAction)jumpToSelectionAction:(id)sender
{
    // Pass through to find panel
    //[[self findWindowController] jumpToSelectionAction:sender];
}
#endif

- (IBAction)setBrowseModeAction:(id)sender
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Set browse mode
    int tag;
    int browseMode = -1;
    tag = [sender tag];
    switch (tag) {
    case SRDefaultBrowsingTag: { browseMode = SRDefaultBrowseMode; break; }
    case SRAlwaysInSameTabTag: { browseMode = SRAlwaysInSameTabBrowseMode; break; }
    case SRAlwaysInNewTabTag: { browseMode = SRAlwaysInNewTabBrowseMode; break; }
    case SRAlwaysInNewBackgroundTabTag: { browseMode = SRAlwaysInNewBackgroundTabBrowseMode; break; }
    }
    if (browseMode == -1) {
        return;
    }
    
    [defaults setInteger:browseMode forKey:SRBrowseMode];
}

- (IBAction)clearHistoryAction:(id)sender
{
    // Remove all web history items
    [[WebHistory optionalSharedHistory] removeAllItems];
}

- (IBAction)showPreferencesAction:(id)sender
{
    [[self preferencesController] showPreferences];
}

- (void)blockPopupAction:(id)sender
{
    // Negate javaScriptCanOpenWindowsAutomatically
    BOOL    flag;
    flag = [[SRPreferencesController defaultWebPreferences] javaScriptCanOpenWindowsAutomatically];
    [[SRPreferencesController defaultWebPreferences] setJavaScriptCanOpenWindowsAutomatically:!flag];
}

- (void)emptyCacheAction:(id)sender
{
    // Show alert
    NSAlert*    alert;
    alert = [[NSAlert alloc] init];
    [alert autorelease];
    [alert setAlertStyle:NSCriticalAlertStyle];
    [alert setMessageText:NSLocalizedString(@"Are you sure you want to empty the cache?", @"Are you sure you want to empty the cache?")];
    [alert setInformativeText:NSLocalizedString(UTF8STR("Shiira saves the contents of web pages you open in a cache so that it’s faster to visit them again."), UTF8STR("Shiira saves the contents of web pages you open in a cache so that it’s faster to visit them again."))];
    [alert addButtonWithTitle:NSLocalizedString(@"Empty", @"Empty")];
    [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"Cancel")];
    
    int result;
    result = [alert runModal];
    if (result != NSAlertFirstButtonReturn) {
        return;
    }
    
    // Empty cache
    [[self cacheController] removeAllCacheAction:self];
}

- (IBAction)showDownloadsPanelAction:(id)sender
{
    [[self downloadsController] showWindow:sender];
}

- (IBAction)showSearchEnginesAction:(id)sender
{
    [[self searchEnginesController] showWindow:sender];
}

- (IBAction)showCacheAction:(id)sender
{
    [[self cacheController] showWindow:sender];
}

- (IBAction)showJavaScriptErrorLogAction:(id)sender
{
	[[self javaScriptErrorLogController] showWindow:sender];
}

- (IBAction)openBookmarkAction:(id)sender
{
    // Get bookmark as represented object
    SRBookmark* bookmark;
    bookmark = [sender representedObject];
    
    if (bookmark && [bookmark isKindOfClass:[SRBookmark class]]) {
        NSDocumentController*   documentController;
        documentController = [NSDocumentController sharedDocumentController];
        
        // Get current document
        SRMainDocument* document;
        document = [documentController currentDocument];
        if (!document) {
            document = [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
        }
        
        // Open bookmark
        [[document mainWindowController] openBookmark:bookmark];
    }
}

- (void)openBookmarkInTabsAction:(id)sender
{
    NSDocumentController*   documentController;
    documentController = [NSDocumentController sharedDocumentController];
    
    // Get current document
    SRMainDocument* document;
    document = [documentController currentDocument];
    if (!document) {
        document = [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    }
    
    // Open bookmark in tabs
    [[document mainWindowController] openBookmarkInTabsAction:sender];
}

- (void)openHistoryItemAction:(id)sender
{
    NSDocumentController*   documentController;
    documentController = [NSDocumentController sharedDocumentController];
    
    // Get current document
    SRMainDocument* document;
    document = [documentController currentDocument];
    if (!document) {
        document = [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    }
    
    // Open history
    [[document mainWindowController] openHistoryItemAction:sender];
}

- (void)goHomeAction:(id)sender
{
    NSDocumentController*   documentController;
    documentController = [NSDocumentController sharedDocumentController];
    
    // Get current document
    SRMainDocument* document;
    document = [documentController currentDocument];
    if (!document) {
        document = [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    }
    
    // Go home
    [[document mainWindowController] goHomeAction:sender];
}

- (void)closeAllWindowsAction:(id)sender
{
    // Close all windows
    NSEnumerator*   enumerator;
    NSWindow*       window;
    enumerator = [[NSApp windows] objectEnumerator];
    while (window = [enumerator nextObject]) {
        if  ([window styleMask] & NSClosableWindowMask) {
            [window performClose:self];
        }
    }
}

- (void)showHelpAction:(id)sender
{
    // Open 'http://hmdt-web.net/shiira/help/'
    [self _openURL:[NSURL URLWithString:@"http://hmdt-web.net/shiira/help/"]];
}

- (void)showShiiraHomePageAction:(id)sender
{
    // Open 'http://hmdt-web.net/shiira/'
    [self _openURL:[NSURL URLWithString:@"http://hmdt-web.net/shiira/index-e.html"]];
}

- (void)showVisualHistoryBackForwardAction:(id)sender
{
    // Show panel
    //[[[SRVHBackForwardController sharedInstance] window] orderFront:self];
    //[[SRVHBackForwardController sharedInstance] update];
}

- (void)togglePrivateBrowseAction:(id)sender
{
    // Get WebPreferences
    WebPreferences* preferences;
    preferences = [SRPreferencesController defaultWebPreferences];
    
    // Get currnet setting
    BOOL    isPrivate;
    isPrivate = [preferences privateBrowsingEnabled];
    
    // Show alert
    if (!isPrivate) {
        NSAlert*    alert;
        alert = [[NSAlert alloc] init];
        [alert setMessageText:NSLocalizedString(@"Are you sure you want to turn on private browsing?", @"Are you sure you want to turn on private browsing?")];
        [alert setInformativeText:NSLocalizedString(@"When private browsing is turned on, webpages are not added to the history, items are automatically removed from the Downloads window, information isn't saved for AutoFill (including names and passwords), and searches are not added to the pop-up menu in the Google search box. Until you close the window, you can still click the Back and Forward buttons to return to webpages you have opened.", @"When private browsing is turned on, webpages are not added to the history, items are automatically removed from the Downloads window, information isn't saved for AutoFill (including names and passwords), and searches are not added to the pop-up menu in the Google search box. Until you close the window, you can still click the Back and Forward buttons to return to webpages you have opened.")];
        [alert setAlertStyle:NSInformationalAlertStyle];
        [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK")];
        [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"Cancel")];
        
        int result;
        result = [alert runModal];
        [alert release];
        if (result != NSAlertFirstButtonReturn) {
            return;
        }
    }
    
    // Toggle private browse mode
    [preferences setPrivateBrowsingEnabled:!isPrivate];
}

//--------------------------------------------------------------//
#pragma mark -- NSResponder override --
//--------------------------------------------------------------//

- (BOOL)performKeyEquivalent:(NSEvent*)event
{
    if ([event type] == NSKeyDown) {
        // Get modifier flags and key code
        unsigned    modifierFlags;
        unsigned    cmdFlag, optFlag, shiftFlag;
        short       keyCode;
        NSString*   characters;
        unichar     unicodeChar = 0;
        modifierFlags = [event modifierFlags];
        cmdFlag = modifierFlags & NSCommandKeyMask;
        optFlag = modifierFlags & NSAlternateKeyMask;
        shiftFlag = modifierFlags & NSShiftKeyMask;
        keyCode = [event keyCode];
        characters = [event characters];
        if (characters && [characters length] > 0) {
            unicodeChar = [characters characterAtIndex:0];
        }
        
        // For cmd + '0' ~ '9'
        if (cmdFlag && unicodeChar >= '0' && unicodeChar <= '9') {
            // Get current document
            NSDocumentController*   documentController;
            SRMainDocument*         document;
            documentController = [NSDocumentController sharedDocumentController];
            document = [documentController currentDocument];
            if (!document) {
                document = [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
            }
            
            // Open bookmark at bar
            id  windowController;
            windowController = [document mainWindowController];
            if ([windowController isKindOfClass:[SRMainWindowController class]]) {
                [windowController openBookmarkBarAtAction:event];
                return YES;
            }
        }
    }
    
    return NO;
}

//--------------------------------------------------------------//
#pragma mark -- NSMenu delegate --
//--------------------------------------------------------------//

- (void)menuNeedsUpdate:(NSMenu*)menu
{
    // Browser menu
    if (menu == [SRAppDelegate browserMenu]) {
        [self updateBrowserMenu];
    }
    
    // Text encoding menu
    if (menu == [SRAppDelegate textEncodingMenu]) {
        [self updateTextEncodingMenu];
    }
}

- (BOOL)menuHasKeyEquivalent:(NSMenu*)menu 
        forEvent:(NSEvent*)event 
        target:(id*)target 
        action:(SEL*)action
{
    // Get key window and its window controller
    NSWindow*   keyWindow;
    id          windowController;
    keyWindow = [NSApp keyWindow];
    windowController = [keyWindow windowController];
    
    // Get attributes
    unsigned int    modifierFlags;
    BOOL            optFlag, shiftFlag;
    NSString*       characters;
    modifierFlags = [event modifierFlags];
    optFlag = modifierFlags & NSAlternateKeyMask;
    shiftFlag = modifierFlags & NSShiftKeyMask;
    characters = [event charactersIgnoringModifiers];
    
    // For file menu
    if (menu == [SRAppDelegate fileMenu]) {
        // For cmd + 'w'
        if (!optFlag && !shiftFlag && [characters isEqualToString:@"w"]) {
            // For main window controller
            if (windowController && [windowController isKindOfClass:[SRMainWindowController class]]) {
                return [windowController menuHasKeyEquivalent:menu 
                        forEvent:event 
                        target:target 
                        action:action];
            }
            
            // Use close window action
            *target = nil;
            *action = @selector(closeWindowAction:);
            
            return YES;
        }
        
        // For cmd + opt + 'w'
        if (optFlag && !shiftFlag && [characters isEqualToString:@"w"]) {
            // Use close all windows action
            *target = nil;
            *action = @selector(closeAllWindowsAction:);
            
            return YES;
        }
    }
    
    return NO;
}

//--------------------------------------------------------------//
#pragma mark -- NSMenuValidataion method --
//--------------------------------------------------------------//

- (BOOL)validateMenuItem:(id<NSMenuItem>)menuItem
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Get tag
    int tag;
    tag = [menuItem tag];
    
    // Get tab browsing
    BOOL    isTabbedBrowsing;
    isTabbedBrowsing = [defaults boolForKey:SRTabEnableTabbedBrowsing];
    
    // Get browse mode
    int browseMode;
    browseMode = [defaults integerForKey:SRBrowseMode];
    
    switch (tag) {
    // Shiira menu
    case SRBlockPopupWindowTag: {
        BOOL    isBlockedPopup;
        isBlockedPopup = ![[SRPreferencesController defaultWebPreferences] javaScriptCanOpenWindowsAutomatically]; 
        [menuItem setState:isBlockedPopup ? NSOnState : NSOffState];
        return YES;
    }
    case SRPrivateBrowseTag: {
        // Get private browse setting
        WebPreferences* preferences;
        BOOL            isPrivate;
        preferences = [SRPreferencesController defaultWebPreferences];
        isPrivate = [preferences privateBrowsingEnabled];
        
        [menuItem setState:isPrivate ? NSOnState : NSOffState];
        return YES;
    }
    
    // View menu
    case SRDefaultBrowsingTag: {
        [menuItem setState:(browseMode == SRDefaultBrowseMode ? NSOnState : NSOffState)];
        return YES;
    }
    case SRAlwaysInSameTabTag: {
        static  NSString*   _openLinkInSameTab = nil;
        static  NSString*   _openLinkInSameWindow = nil;
        if (!_openLinkInSameTab) {
            _openLinkInSameTab = [NSLocalizedString(@"Always Open Link in Same Tab", nil) retain];
            _openLinkInSameWindow = [NSLocalizedString(@"Always Open Link in Same Window", nil) retain];
        }
        
        [menuItem setTitle:(isTabbedBrowsing ? _openLinkInSameTab : _openLinkInSameWindow)];
        [menuItem setState:(browseMode == SRAlwaysInSameTabBrowseMode ? NSOnState : NSOffState)];
        return YES;
    }
    case SRAlwaysInNewTabTag: {
        [menuItem setState:(browseMode == SRAlwaysInNewTabBrowseMode ? NSOnState : NSOffState)];
        return isTabbedBrowsing;
    }
    case SRAlwaysInNewBackgroundTabTag: {
        [menuItem setState:(browseMode == SRAlwaysInNewBackgroundTabBrowseMode ? NSOnState : NSOffState)];
        return isTabbedBrowsing;
    }
    }
    
    return YES;
}

//--------------------------------------------------------------//
#pragma mark -- Key value observation --
//--------------------------------------------------------------//

- (void)observeValueForKeyPath:(NSString*)keyPath 
        ofObject:(id)object 
        change:(NSDictionary*)change 
        context:(void*)context
{
    // For NSUserDefault
    if (object == [NSUserDefaults standardUserDefaults]) {
        // General preferences
        if ([keyPath isEqualToString:SRProtocolLibrary]) {
            // Update protocol library
            [self updateProtocolLibary];
            return;
        }
        
        // Bookmark preferences
        if ([keyPath isEqualToString:SRBookmarkBookmarksBar]) {
            // Update bookmark menu
            [self updateBookmarkMenu];
            return;
        }
        if ([keyPath isEqualToString:SRIconUseFavicon] || 
            [keyPath isEqualToString:SRIconUseFaviconBookmarkMenu])
        {
            // Update bookmark and history menu
            [self updateBookmarkMenu];
            [self updateHistoryMenu];
            return;
        }
        if ([keyPath isEqualToString:SRTabShowAllTabsShortcut]) {
            // Get new shortcut
            int shortcutTag;
            shortcutTag = [object integerForKey:SRTabShowAllTabsShortcut];
            if (shortcutTag < 0 || shortcutTag > 14) {
                return;
            }
            
            // Change shortcut of 'Show All Tabs'
            id <NSMenuItem> showAllTabsItem;
            showAllTabsItem = [[SRAppDelegate windowMenu] itemWithTag:SRShowAllTabsTag];
            
            unichar c;
            c = NSF1FunctionKey + shortcutTag;
            [showAllTabsItem setKeyEquivalent:[NSString stringWithCharacters:&c length:1]];
            return;
        }
        if ([keyPath isEqualToString:SRRSSShowOnDock]) {
            [self rssUpdated:nil];
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- openURL --
//--------------------------------------------------------------//

- (void)openURL:(NSAppleEventDescriptor*)event 
        withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
	// Create requested URL
    NSString*   urlString;
    NSURL*      URL;
    urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
	URL = [NSURL URLWithString:urlString];
    
    // Open URL
    [self _openURL:URL];
}

- (void)_openURL:(NSURL*)URL
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    SRMainWindowController*  currentWC=nil;
    NSDocumentController*   docCtl=[NSDocumentController sharedDocumentController];
    // Check exists WebView and get frontmostBrowserDocument
    int i,cnt;
    NSArray* documents=[NSApp orderedDocuments];
    cnt=[documents count];
    for(i=0;i<cnt;i++){
        NSDocument* aDocument=[documents objectAtIndex:i];
        if([aDocument isKindOfClass:[SRMainDocument class]]){
            SRMainWindowController*  mainWindowController;
        
            mainWindowController=(SRMainWindowController*)[(SRMainDocument*)aDocument mainWindowController];
            if(currentWC==nil)  currentWC=mainWindowController;
            if([mainWindowController reloadURLIfExists:URL]){
                return;
            }
        }
    }
    
    if(currentWC){
        // Check web data source of selected web view
        if (![[[currentWC selectedWebView] mainFrame] dataSource]) {
            // Use front empty webView
            [currentWC openURL:URL];
            // Bring to front
            [currentWC showWindow:self];
            return;
        }
        
        // Make new tab of current window
        if([defaults boolForKey:SRTabEnableTabbedBrowsing] && [defaults boolForKey:SRTabOpenURLUseTab]){
            [currentWC openInNewTabURL:URL select:YES];
            // Bring to front
            [currentWC showWindow:self];
            return;
        }
    }
    
    // Create new document
    SRMainDocument* document;
    document = [docCtl openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    
    // Get main window controller
    currentWC = [document mainWindowController];
    
    // Open bookmark in new window
    [currentWC openURL:URL];
    
    // Open document with URL
//    [[NSDocumentController sharedDocumentController] 
//            openDocumentWithContentsOfURL:URL display:YES];

}

//--------------------------------------------------------------//
#pragma mark -- With other browsers --
//--------------------------------------------------------------//

- (NSArray*)browserURLsWithShiira:(BOOL)flag
{
    // URLs for checking
    NSURL*  httpURL;
    NSURL*  httpsURL;
    NSURL*  fileURL;
    httpURL = [NSURL URLWithString:@"http://www.apple.com/"];
    httpsURL = [NSURL URLWithString:@"https://www.apple.com/"];
    fileURL = [NSURL fileURLWithPath:@"/Users/name/index.html"];
    
    // Get application URLs for http
    CFArrayRef      applicationsRef;
    NSMutableArray* applications;
    applicationsRef = LSCopyApplicationURLsForURL((CFURLRef)httpURL, kLSRolesAll);
    applications = [NSMutableArray array];
    
    int i;
    for (i = 0; i < CFArrayGetCount(applicationsRef); i++) {
        CFURLRef    appURLRef;
        CFStringRef stringRef;
        appURLRef = (CFURLRef)CFArrayGetValueAtIndex(applicationsRef, i);
        stringRef = CFURLGetString(appURLRef);
        
        [applications addObject:[NSURL URLWithString:(NSString*)stringRef]];
        
//        CFRelease(appURLRef);
    }
    
    CFRelease(applicationsRef);
    
    // Check with https and file scheme
    NSMutableArray* browsers;
    NSEnumerator*   enumerator;
    NSURL*          appURL;
    browsers = [NSMutableArray array];
    enumerator = [applications objectEnumerator];
    while (appURL = [enumerator nextObject]) {
        // Except Shiira
        if (!flag) {
            if ([[[appURL path] lastPathComponent] rangeOfString:@"Shiira"].length != 0) {
                continue;
            }
        }
        
        // Check with https
        OSStatus    status;
        Boolean     accepts;
        status = LSCanURLAcceptURL(
                (CFURLRef)httpsURL, (CFURLRef)appURL, kLSRolesViewer/*kLSRolesAll*/, kLSAcceptDefault, &accepts);
        if (!accepts) {
            continue;
        }
        
#if 0
// Dose Tiger support file scheme?
        // Check with file
        status = LSCanURLAcceptURL(
                (CFURLRef)fileURL, (CFURLRef)appURL, kLSRolesViewer/*kLSRolesAll*/, kLSAcceptDefault, &accepts);
        if (!accepts) {
            continue;
        }
#endif
        
        // Recognize it as an browser
        [browsers addObject:appURL];
    }
    
    return browsers;
}

- (NSArray*)browserMenuItemsWithShiira:(BOOL)flag action:(SEL)action
{
    // Get browser URLs
    NSArray*    browserURLs;
    browserURLs = [self browserURLsWithShiira:flag];
    
    // Create menu items
    NSMutableArray* menuItems;
    NSEnumerator*   enumerator;
    NSURL*          appURL;
    menuItems = [NSMutableArray array];
    enumerator = [browserURLs objectEnumerator];
    while (appURL = [enumerator nextObject]) {
        // Get app name
        NSString*   appPath;
        NSString*   appName;
        appPath = [appURL path];
        appName = [[appPath lastPathComponent] stringByDeletingPathExtension];
        
        // Check duplication
        NSEnumerator*   tmpEnumerator;
        NSURL*          tmpAppURL;
        tmpEnumerator = [browserURLs objectEnumerator];
        while (tmpAppURL = [tmpEnumerator nextObject]) {
            if (appURL != tmpAppURL) {
                if ([[[tmpAppURL path] lastPathComponent] isEqualToString:appName]) {
                    // Composite file path to name
                    appName = [NSString stringWithFormat:@"%@ - %@", 
                            appName, [appPath stringByDeletingLastPathComponent]];
                    break;
                }
            }
        }
        
        // Get icon for this app
        NSImage*    icon;
        icon = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
        [icon setSize:NSMakeSize(16, 16)];
        
        // Create menu item
        NSMenuItem* menuItem;
        menuItem = [[NSMenuItem alloc] 
                initWithTitle:appName action:action keyEquivalent:@""];
        [menuItem autorelease];
        [menuItem setTag:SROpenURLWithBrowserItemTag];
        [menuItem setRepresentedObject:appURL];
        if (icon) {
            [menuItem setImage:icon];
        }
        
        // Add menu item
        [menuItems addObject:menuItem];
    }
        
    return menuItems;
}

//--------------------------------------------------------------//
#pragma mark -- About panel --
//--------------------------------------------------------------//

- (SRAboutController*)aboutController
{
    if (!_aboutController) {
        // Load about panel
        _aboutController = [[SRAboutController alloc] initWithWindowNibName:@"About"];
        if (!_aboutController) {
            // Fatal
            SR_FATAL(@"Could not load About.nib");
        }
        //load nib
        [_aboutController window];
    }
    
    return _aboutController;
}

- (void)orderFrontStandardAboutPanel:(id)sender
{
    // Get about controller
    SRAboutController*  aboutController;
    aboutController = [self aboutController];
    
    // Set version
    NSString*   shortVersion;
    NSString*   version;
    shortVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
    version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
    
    NSString*   versionString = @"";
    if (shortVersion && version && [version length] > 1) {
        versionString = [NSString stringWithFormat:@"%@\n(%@)", shortVersion, version];
    }
    else if (shortVersion) {
        versionString = shortVersion;
    }
    [[aboutController versionTextField] setStringValue:versionString];
    
    
    // Set credit
    NSString*   creditPath;
    creditPath = [[NSBundle mainBundle] pathForResource:@"Credits" ofType:@"rtf"];
    if (creditPath) {
        NSAttributedString* credit;
        credit = [[NSAttributedString alloc] initWithPath:creditPath documentAttributes:NULL];
        [credit autorelease];
        if (credit) {
            [[[aboutController creditTextView] textStorage] setAttributedString:credit];
        }
    }
    
    [[aboutController window] makeKeyAndOrderFront:self];
}

@end

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- Utitlity --
//--------------------------------------------------------------//

NSString* SRAlternateTitleOfWebHistoryItem(
        WebHistoryItem* historyItem)
{
    NSString*   title;
    
    // Decide title
    title = [historyItem title];
    if (!title || [title length] == 0) {
        title = [historyItem URLString];
    }
    if (!title || [title length] == 0) {
        title = NSLocalizedString(@"Untitled", @"Untitled");
    }
    
    // Truncate title
    int length;
    length = [title length];
    if (length > 51) {
        static NSString*    _horizontalEllipsis = nil;
        if (!_horizontalEllipsis) {
            unichar uni[1];
            uni[0] = 0x2026;
            _horizontalEllipsis = [[NSString stringWithCharacters:uni length:1] retain];
        }
        
        title = [NSString stringWithFormat:@"%@%@%@", 
                [title substringWithRange:NSMakeRange(0, 25)], 
                _horizontalEllipsis, 
                [title substringWithRange:NSMakeRange(length - 25, 25)]];
    }
    
    return title;
}
