/*
SRAppController.m

Author: Makoto Kinoshita

Copyright 2004-2006 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 "SRBookmark.h"
#import "SRBookmarkConverter.h"
#import "SRSearchEngineConverter.h"

#import "SRAboutController.h"
#import "SRAppController.h"
#import "SRBrowserController.h"
#import "SRBrowserDocument.h"
#import "SRBookmarkController.h"
#import "SRCacheController.h"
#import "SRDownloadController.h"
#import "SRFindController.h"
#import "SRHistoryController.h"
#import "SRPageInfoController.h"
#import "SRPrefController.h"
#import "SRPlugInController.h"
#import "SRMenu.h"
#import "SRDefaultKeys.h"

#import "SREncodings.h"

#import "SRPreference.h"
#import "SRPrefDefaultKeys.h"

#import "SRConstants.h"

#import "SRHistoryMenuProvider.h"
#import "SRBookmarkMenuProvider.h"
#import "SRBrowserMenuProvider.h"

@implementation SRAppController

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

+ (void)load
{
    NSAutoreleasePool*  pool;
    pool = [[NSAutoreleasePool alloc] init];
    
    // Initialize HMDT frameworks
    HMDTAppKitInitialize();
    HMDTWebKitInitialize();
    
    [pool release];
}

static SRAppController* _sharedInstance = nil;

+ (id)sharedInstance
{
    return _sharedInstance;
}

- (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:SRDownloadPath];
    
    // Remove key for Web Kit defaults, this value is managed by Shiire
    if ([defaults objectForKey:@"WebKitShowsURLsInToolTips"]) {
        [defaults removeObjectForKey:@"WebKitShowsURLsInToolTips"];
    }
    
#if 0
    [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];
#endif
    
    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"];
    }
}

- (id)init
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Initialize class variables
    if (!_sharedInstance) {
        _sharedInstance = self;
    }
    
    // Set factroy defaults
    [self _setFactoryDefaults];

    // Initialize instance variables
    _willSaveHistory = NO;
    
    // Add date formats
    NSString*   path;
    path = [[NSBundle mainBundle] pathForResource:@"DateFormats" ofType:@"plist"];
    if (path) {
        [NSDate addDateFromatsWithContentsOfFile:path];
    }
    
    // Load plug-ins
    [[SRPlugInController sharedInstance] loadShiiraPlugIns];
    
    // Register notification
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    
    [center addObserver:self selector:@selector(webHistoryItemsAdded:) 
            name:WebHistoryItemsAddedNotification object:nil];
    [center addObserver:self selector:@selector(webHistoryItemsRemoved:) 
            name:WebHistoryItemsRemovedNotification object:nil];
    [center addObserver:self selector:@selector(webHistoryAllItemsRemoved:) 
            name:WebHistoryAllItemsRemovedNotification object:nil];
    
    // Register key value observation
    [defaults addObserver:self forKeyPath:SRBookmarkMenuUsageFlags 
            options:NSKeyValueObservingOptionNew context:NULL];
    
    return self;
}

- (void)_convertBookmark
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Create bookmark converter
    SRBookmarkConverter*    bookmarkConverter;
    bookmarkConverter = [SRBookmarkConverter sharedInstance];
    [bookmarkConverter setAppController:self];
    
    // Convert Shiira bookmarks
    if (![defaults boolForKey:SRIsConvertedOldShiiraBookmarks]) {
        [bookmarkConverter convertBookmarksOfBrowser:SRBrowserShiira];
        
        // Convert search engines
        SRSearchEngineConverter*    searchEngineConverter;
        searchEngineConverter = [SRSearchEngineConverter sharedInstance];
        [searchEngineConverter setAppController:self];
        [searchEngineConverter convertSearchEnginesOfShiira1];
        
        // Set flag
        [defaults setBool:YES forKey:SRIsConvertedOldShiiraBookmarks];
    }
    
    // Convert other browser bookmarks
    [bookmarkConverter convertBookmarksOfBrowser:SRBrowserSafari];
    [bookmarkConverter convertBookmarksOfBrowser:SRBrowserFirefox];
    
    // Get bookmark for bookmark bar
    SRBookmark* bookmark;
    bookmark = [self bookmarkForBookmarkBar];
    if (!bookmark) {
        // Add defaults bookmark bar
        bookmark = [SRBookmark bookmarkForBookmarkBarWithContext:[self managedObjectContext] 
                persistentStore:[self persistentStore]];
        [[self rootBookmarkOfBrowser:SRBrowserShiira] 
                insertBookmarks:[NSArray arrayWithObject:bookmark] atIndex:0];
    }
}

- (void)applicationWillFinishLaunching:(NSNotification*)notification
{
    // Convert bookmarks
    [self _convertBookmark];
    
    // Set handler for Apple event
	[[NSAppleEventManager sharedAppleEventManager]
            setEventHandler:self
		    andSelector:@selector(openURL:withReplyEvent:)
            forEventClass:'GURL' andEventID:'GURL']; 
}

- (void)applicationDidFinishLaunching:(NSNotification*)notification
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Set default text encoding
    NSStringEncoding    encoding;
    NSValueTransformer* transformer;
    NSString*           encodingName;
    encoding = [[defaults objectForKey:SRDefaultTextEncoding] unsignedIntValue];
    transformer = [NSValueTransformer valueTransformerForName:SRIANAToEncodingTransformerName];
    
    // For Japanese aut detection
    if (encoding == SRJapaneseAutoDetectEncoding) {
        // Set Sfhit JIS as default encoding, it enables Japanese auto detection
        encodingName = [transformer reverseTransformedValue:
                [NSNumber numberWithUnsignedInt:SRConvertedShiftJISStringEncoding]];
    }
    else {
        // Set web preferences default encoding
        encodingName = [transformer reverseTransformedValue:
                [NSNumber numberWithUnsignedInt:encoding]];
    }
    
    if (encodingName) {
        [[SRPreference webPreferences] setDefaultTextEncodingName:encodingName];
    }
    
    // Create WebHistory
    WebHistory* history;
    //int         historyAge, days = 7;
    history = [[WebHistory alloc] init];
#if 0
    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];
#endif
    [WebHistory setOptionalSharedHistory:history];
    
    // Load web history
    [self loadWebHistory];
    
    // Load key bindings
    [self loadKeyBindings];
    
    // Set menu delegate
    SRHistoryMenuProvider*  historyMenuProvider;
    SRBookmarkMenuProvider* bookmarkMenuProvider;
    historyMenuProvider = [SRHistoryMenuProvider sharedInstance];
    bookmarkMenuProvider = [SRBookmarkMenuProvider sharedInstance];
    [SRHistoryMenu() setDelegate:historyMenuProvider];
    [SRBookmarkMenu() setDelegate:bookmarkMenuProvider];
    
    // Create bookmark bar menu to make available cmd+1 to cmd+0 short cuts
    [bookmarkMenuProvider menuNeedsUpdate:SRBookmarkMenu()];
    NSEnumerator*   enumerator;
    NSMenuItem*     menuItem;
    enumerator = [[SRBookmarkMenu() itemArray] objectEnumerator];
    while (menuItem = [enumerator nextObject]) {
        if ([menuItem hasSubmenu]) {
            SRMenu*     submenu;
            SRBookmark* bookmark;
            submenu = (SRMenu*)[menuItem submenu];
            bookmark = [submenu representedObject];
            if ([[bookmark type] isEqualToString:SRBookmarkTypeBookmarkBar]) {
                [bookmarkMenuProvider menuNeedsUpdate:[menuItem submenu]];
            }
        }
    }
    
    // Copy templates
    static BOOL _copyDone = NO;
    if (!_copyDone) {
        NSFileManager*  fileMgr;
        NSString*       srcPath;
        NSString*       destPath;
        fileMgr = [NSFileManager defaultManager];
        srcPath = [[[NSBundle mainBundle] resourcePath] 
                stringByAppendingPathComponent:@"template"];
        destPath = [HMApplicationSupportFolder(@"Shiira") stringByAppendingPathComponent:@"template"];
        if ([fileMgr fileExistsAtPath:destPath]) {
            [fileMgr removeFileAtPath:destPath handler:NULL];
        }
        [fileMgr copyPath:srcPath toPath:destPath handler:NULL];
        
        _copyDone = YES;
    }
}

- (void)dealloc
{
    // Remove observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    [defaults removeObserver:self forKeyPath:SRBookmarkMenuUsageFlags];
    
    [_managedObjectContext release], _managedObjectContext = nil;
    [_persistentStoreCoordinator release], _persistentStoreCoordinator = nil;
    [_managedObjectModel release], _managedObjectModel = nil;
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Toolbar items --
//--------------------------------------------------------------//

+ (NSArray*)allToolbarItemIdentifiers
{
    static NSMutableArray*  _toolarItemIdentifiers = nil;
    if (!_toolarItemIdentifiers) {
        _toolarItemIdentifiers = [[NSMutableArray array] retain];
        
        // Browser toolbar
        [_toolarItemIdentifiers addObjectsFromArray:[SRBrowserController toolbarItemIdentifiers]];
    }
    
    return _toolarItemIdentifiers;
}

//--------------------------------------------------------------//
#pragma mark -- Web history --
//--------------------------------------------------------------//

- (WebView*)openHistory:(WebHistoryItem*)history
{
    // Get current document
    NSDocumentController*   documentController;
    SRBrowserDocument*      document;
    documentController = [NSDocumentController sharedDocumentController];
    document = [documentController currentDocument];
    if (!document) {
        // Open untitled document
        document = [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    }
    
    // Open history
    return [[document browserController] openHistory:history];
}

- (void)loadWebHistory
{
    // Create path ~/Library/Application Support/Shiira/History.plist
    NSString*	historyPath;
    historyPath = [HMApplicationSupportFolder(@"Shiira") 
            stringByAppendingPathComponent:SRHistoryFileName];
    if (!historyPath) {
        return;
    }
    
    // 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)saveWebHistory
{
    // Create path ~/Library/Application Support/Shiira/History.plist
    NSString*	historyPath;
    historyPath = [HMApplicationSupportFolder(@"Shiira") 
            stringByAppendingPathComponent:SRHistoryFileName];
    if (!historyPath) {
        return;
    }
    
    // Check existense
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:historyPath]) {
        // Create file
        HMCreateFile(historyPath);
    }
    
    // Save history
    WebHistory* history;
    NSError*    error;
    history = [WebHistory optionalSharedHistory];
    if (![history saveToURL:[NSURL fileURLWithPath:historyPath] error:&error]) {
        // Error
        NSLog(@"Failed to save history");
        return;
    }
}

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

- (id)rootBookmarkOfBrowser:(NSString*)browser
{
    // Get managed object context
    NSManagedObjectContext* context;
    context = [self managedObjectContext];
    
    // Create request
    NSFetchRequest*     request;
    request = [[NSFetchRequest alloc] init];
    [request autorelease];
    [request setEntity:[NSEntityDescription entityForName:@"Bookmark" 
            inManagedObjectContext:context]];
    [request setPredicate:[NSPredicate predicateWithFormat:@"type == 'Root' AND browser == %@", browser]];
    
    // Get root bookmarks
    NSArray*    bookmarks;
    bookmarks = [context executeFetchRequest:request error:nil];
    if ([bookmarks count] > 0) {
        return [bookmarks objectAtIndex:0];
    }
    
    // Create root bookmark
    SRBookmark* bookmark;
    bookmark = [NSEntityDescription insertNewObjectForEntityForName:@"Bookmark" 
            inManagedObjectContext:context];
    [bookmark setType:SRBookmarkTypeRoot];
    [bookmark setBrowser:browser];
    
    // Assign object
    if ([browser isEqualToString:SRBrowserShiira]) {
        [context assignObject:bookmark toPersistentStore:[self persistentStore]];
    }
    else {
        [context assignObject:bookmark toPersistentStore:[self memoryPersistentStore]];
    }
    
    return bookmark;
}

- (SRBookmark*)bookmarkForBookmarkBar
{
    // Get managed object context
    NSManagedObjectContext* context;
    context = [self managedObjectContext];
    
    // Create request
    NSFetchRequest*     request;
    NSPredicate*        predicate;
    request = [[NSFetchRequest alloc] init];
    [request autorelease];
    [request setEntity:[NSEntityDescription entityForName:@"Bookmark" 
            inManagedObjectContext:context]];
    
    predicate = [NSPredicate predicateWithFormat:@"type == 'BookmarksBar'"];
    [request setPredicate:predicate];
    
    // Get bookmark
    NSArray*    bookmarks;
    bookmarks = [context executeFetchRequest:request error:nil];
    if (!bookmarks || [bookmarks count] == 0) {
        return nil;
    }
    
    return [bookmarks objectAtIndex:0];
}

- (WebView*)openBookmark:(SRBookmark*)bookmark
{
    // Get current document
    NSDocumentController*   documentController;
    SRBrowserDocument*      document;
    documentController = [NSDocumentController sharedDocumentController];
    document = [documentController currentDocument];
    if (!document) {
        // Open untitled document
        document = [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    }
    
    // Open bookmark
    return [[document browserController] openBookmark:bookmark];
}

//--------------------------------------------------------------//
#pragma mark -- Shelf --
//--------------------------------------------------------------//

- (WebView*)openShelf:(NSString*)shelfId
{
    // Get current document
    NSDocumentController*   documentController;
    SRBrowserDocument*      document;
    documentController = [NSDocumentController sharedDocumentController];
    document = [documentController currentDocument];
    if (!document) {
        // Open untitled document
        document = [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    }
    
    // Open shelf
    return [[document browserController] openShelf:shelfId];
}

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

- (NSArray*)searchEngines
{
    // Get context manager
    NSManagedObjectContext* context;
    context = [self managedObjectContext];
    
    // Create fetch request
    NSFetchRequest*         request;
    NSEntityDescription*    entity;
    NSSortDescriptor*       sortDescriptor;
    request = [[NSFetchRequest alloc] init];
    [request autorelease];
    
    entity = [NSEntityDescription entityForName:@"SearchEngine" inManagedObjectContext:context];
    [request setEntity:entity];
    
    sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
    [sortDescriptor autorelease];
    [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
    
    // Fetch search engine
    return [context executeFetchRequest:request error:NULL];
}

//--------------------------------------------------------------//
#pragma mark -- Key bindings --
//--------------------------------------------------------------//

- (void)_applyMenuKeyBindingsWithMenu:(NSMenu*)menu 
        dictionary:(NSDictionary*)dict
{
    // Get menu items
    NSEnumerator*   enumerator;
    NSMenuItem*     menuItem;
    enumerator = [[menu itemArray] objectEnumerator];
    while (menuItem = [enumerator nextObject]) {
        // For separator
        if ([menuItem isSeparatorItem]) {
            continue;
        }
        
        // For submenu
        if ([menuItem hasSubmenu]) {
            // Collect with submenu
            [self _applyMenuKeyBindingsWithMenu:[menuItem submenu] dictionary:dict];
            continue;
        }
        
        // Get tag
        int tag;
        tag = [menuItem tag];
        if (tag == 0) {
            continue;
        }
        
        // Get key binding
        NSDictionary*   keyBinding;
        keyBinding = [dict objectForKey:[NSNumber numberWithInt:tag]];
        if (!keyBinding) {
            continue;
        }
        
        // Get key equivalent and modifier flags
        NSString*       keyEquivalent;
        unsigned int    flags;
        keyEquivalent = [keyBinding objectForKey:@"key"];
        flags = [[keyBinding objectForKey:@"flags"] unsignedIntValue];
        if (!keyEquivalent || flags == 0) {
            continue;
        }
        
        // Set key equivalent and modifier flags
        [menuItem setKeyEquivalent:keyEquivalent];
        [menuItem setKeyEquivalentModifierMask:flags];
    }
}

- (void)loadKeyBindings
{
    // Create path ~/Library/Application Support/Shiira/KeyBindings.dat
    NSString*	keyBindingsPath;
    keyBindingsPath = [HMApplicationSupportFolder(@"Shiira") 
            stringByAppendingPathComponent:SRKeyBindingsFileName];
    if (!keyBindingsPath) {
        return;
    }
    
    // Check existense
    NSFileManager*	fileMgr;
    fileMgr = [NSFileManager defaultManager];
    if (![fileMgr fileExistsAtPath:keyBindingsPath]) {
        return;
    }
    
    // Load key bindings
    NSDictionary*   keyBindings;
    if (!(keyBindings = [NSUnarchiver unarchiveObjectWithFile:keyBindingsPath])) {
        // Error
        NSLog(@"Fialed to load KeyBindings.dat");
        return;
    }
    
    // Apply menu key bindings
    NSDictionary*   menuKeyBindings;
    menuKeyBindings = [keyBindings objectForKey:@"menu"];
    if (menuKeyBindings) {
        [self _applyMenuKeyBindingsWithMenu:[[NSApplication sharedApplication] mainMenu] 
                dictionary:menuKeyBindings];
    }
}

- (void)_collectMenuKeyBindingsWithMenu:(NSMenu*)menu 
        intoDictionary:(NSMutableDictionary*)dict
{
    // Get menu items
    NSEnumerator*   enumerator;
    NSMenuItem*     menuItem;
    enumerator = [[menu itemArray] objectEnumerator];
    while (menuItem = [enumerator nextObject]) {
        // For separator
        if ([menuItem isSeparatorItem]) {
            continue;
        }
        
        // For submenu
        if ([menuItem hasSubmenu]) {
            // Collect with submenu
            [self _collectMenuKeyBindingsWithMenu:[menuItem submenu] intoDictionary:dict];
            continue;
        }
        
        // Check action
        NSString*   actionStr;
        actionStr = NSStringFromSelector([menuItem action]);
        if (actionStr && [SRExceptionActions() containsObject:actionStr]) {
            continue;
        }
        
        // Get key equivalent
        NSString*   keyEquivalent;
        keyEquivalent = [menuItem keyEquivalent];
        if ([keyEquivalent length] == 0) {
            continue;
        }
        
        // Get tag
        int tag;
        tag = [menuItem tag];
        if (tag == 0) {
            continue;
        }
        
        // Get modifier flags
        unsigned int    flags;
        flags = [menuItem keyEquivalentModifierMask];
        
        // Add key binding
        NSDictionary*   keyBinding;
        keyBinding = [NSDictionary dictionaryWithObjectsAndKeys:
                keyEquivalent, @"key", 
                [NSNumber numberWithUnsignedInt:flags], @"flags", 
                nil];
        [dict setObject:keyBinding forKey:[NSNumber numberWithInt:tag]];
    }
}

- (void)saveKeyBindings
{
    // Create path ~/Library/Application Support/Shiira/KeyBindings.dat
    NSString*	keyBindingsPath;
    keyBindingsPath = [HMApplicationSupportFolder(@"Shiira") 
            stringByAppendingPathComponent:SRKeyBindingsFileName];
    if (!keyBindingsPath) {
        return;
    }
    
    // Collect menu key bindings
    NSMutableDictionary*    menuKeyBindings;
    menuKeyBindings = [NSMutableDictionary dictionary];
    [self _collectMenuKeyBindingsWithMenu:[[NSApplication sharedApplication] mainMenu] 
            intoDictionary:menuKeyBindings];
    
    // Save key bindings
    NSDictionary*   keyBindings;
    keyBindings = [NSDictionary dictionaryWithObjectsAndKeys:
            menuKeyBindings, @"menu", 
            nil];
    if (![NSArchiver archiveRootObject:keyBindings toFile:keyBindingsPath]) {
        // Error
        NSLog(@"Failed to save KeyBindings.dat");
        return;
    }
}

//--------------------------------------------------------------//
#pragma mark -- Black utility panel --
//--------------------------------------------------------------//

- (BOOL)isBlackPaelShown
{
    // Get HMBlkPanel class
    Class   blkPanel;
    blkPanel = NSClassFromString(@"HMBlkPanel");
    if (!blkPanel) {
        return NO;
    }
    
    // Get window list
    NSEnumerator*   enumerator;
    NSWindow*       window;
    enumerator = [[NSApp windows] objectEnumerator];
    while (window = [enumerator nextObject]) {
        if ([window isKindOfClass:blkPanel]) {
            return YES;
        }
    }
    
    return NO;
}

- (void)closeAllBlackPanels
{
    // Get HMBlkPanel class
    Class   blkPanel;
    blkPanel = NSClassFromString(@"HMBlkPanel");
    if (!blkPanel) {
        return;
    }
    
    // Get window list
    NSEnumerator*   enumerator;
    NSWindow*       window;
    enumerator = [[NSApp windows] objectEnumerator];
    while (window = [enumerator nextObject]) {
        if ([window isKindOfClass:blkPanel]) {
            // Close window
            [[window windowController] close];
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- Working with browser menu --
//--------------------------------------------------------------//

- (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 -- Opening URL --
//--------------------------------------------------------------//

- (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];
    
#if 0
    // For history
    if ([URL isFileURL] && [[[URL path] pathExtension] isEqualToString:@"history"]) {
        // Get URL
        NSURL*  historyURL;
        historyURL = [[SRSpotlightController sharedInstance] URLWithHistoryFile:[URL path]];
        if (!historyURL) {
            return;
        }
        URL = historyURL;
    }
#endif
    
    // When specified URL is already opened, reload it
    NSArray*                documents;
    NSEnumerator*           enumerator;
    SRBrowserDocument*      document;
    SRBrowserController*    browserController = nil;
    documents = [NSApp orderedDocuments];
    enumerator = [documents objectEnumerator];
    while (document = [enumerator nextObject]) {
        if ([document isKindOfClass:[SRBrowserDocument class]]) {
            SRBrowserController*    controller;
            controller = (SRBrowserController*)[document browserController];
            if ([controller isKindOfClass:[SRBrowserController class]]) {
                if (!browserController) {
                    browserController = controller;
                }
                
                if ([controller reloadURLIfExists:url]) {
                    return;
                }
            }
        }
    }
    
    // Open in existed window
    if (browserController) {
        if (![[[[browserController selectedPageController] webView] mainFrame] dataSource]) {
            [browserController openURL:url];
        }
        else {
            [browserController openInNewTabURL:url select:YES];
        }
        [browserController showWindow:self];
        
        return;
    }
     
    // Create new document and open URL
    document = [[NSDocumentController sharedDocumentController] 
            openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
    browserController = [document browserController];
    [browserController openURL:url];
}

//--------------------------------------------------------------//
#pragma mark -- Persistent stack --
//--------------------------------------------------------------//

- (NSManagedObjectModel*)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
	
    // Collect bundles
    NSArray*    bundles;
    bundles = [NSArray arrayWithObject:[NSBundle mainBundle]];
    
    // Create managed object model
    _managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:bundles] retain];
    
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator*)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    // Decide persistent file path
    NSString*   path;
    NSURL*      url;
    path = [HMApplicationSupportFolder(@"Shiira") 
            stringByAppendingPathComponent:@"Shiira.sql"];
    url = [NSURL fileURLWithPath:path];
    
    // Create persistent coordinator
    NSError*    error;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] 
            initWithManagedObjectModel:[self managedObjectModel]];
    
    // Add persistent store
    _persistentStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType 
            configuration:nil URL:url options:nil error:&error];
    if (!_persistentStore) {
        [[NSApplication sharedApplication] presentError:error];
    }
    
    // Add memory persistent store
    _memoryPersistentStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType 
            configuration:nil URL:nil options:nil error:&error];
    if (!_memoryPersistentStore) {
        [[NSApplication sharedApplication] presentError:error];
    }
    
    return _persistentStoreCoordinator;
}

- (id)persistentStore
{
    if (!_persistentStore) {
        [self persistentStoreCoordinator];
    }
    
    return _persistentStore;
}

- (id)memoryPersistentStore
{
    if (!_memoryPersistentStore) {
        [self persistentStoreCoordinator];
    }
    
    return _memoryPersistentStore;
}

- (NSManagedObjectContext*)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
    
    // Create managed object context
    NSPersistentStoreCoordinator*   coordinator;
    coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    
    return _managedObjectContext;
}

- (void)saveShiiraData
{
    NSError*    error = nil;
    if (![[self managedObjectContext] save:&error]) {
        [[NSApplication sharedApplication] presentError:error];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Application menu actions --
//--------------------------------------------------------------//

- (void)orderFrontStandardAboutPanel:(id)sender
{
    // Show about panel
    [[SRAboutController sharedInstance] showWindow:self];
}

- (void)showPreferencesAction:(id)sender
{
    // Show preferences panel
    [[SRPrefController sharedInstance] showWindow:self];
}

//--------------------------------------------------------------//
#pragma mark -- File menu actions --
//--------------------------------------------------------------//

- (void)closeTabAction:(id)sender
{
    // Dummy action
    // This enables menu validation for 'Close Tab' menu item
}

//--------------------------------------------------------------//
#pragma mark -- Edit menu actions --
//--------------------------------------------------------------//

- (void)findAction:(id)sender
{
    // Show find panel
    [[SRFindController sharedInstance] showWindow:self];
}

- (void)findNextAction:(id)sender
{
    // Pass through to find panel
    [[SRFindController sharedInstance] findNextAction:sender];
}

- (void)findPreviousAction:(id)sender
{
    // Pass through to find panel
    [[SRFindController sharedInstance] findPreviousAction:sender];
}

- (void)useSelectionForFindAction:(id)sender
{
    // Pass through to find panel
    [[SRFindController sharedInstance] useSelectionForFindAction:sender];
}

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

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

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

- (void)toggleHistoryPanelAction:(id)sender
{
    // Toggle history panel
    SRHistoryController*    controller;
    controller = [SRHistoryController sharedInstance];
    if ([[controller window] isVisible]) {
        [controller close];
    }
    else {
        [controller showWindow:sender];
    }
}

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

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

- (void)toggleBookmarkPanelAction:(id)sender
{
    // Toggle bookmark panel
    SRBookmarkController*   controller;
    controller = [SRBookmarkController sharedInstance];
    if ([[controller window] isVisible]) {
        [controller close];
    }
    else {
        [controller showWindow:sender];
    }
}

- (void)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
        SRBrowserDocument*  document;
        document = [documentController currentDocument];
        if (!document) {
            document = [documentController openUntitledDocumentOfType:SRHTMLDocumentType display:YES];
        }
        
        // Open bookmark
        [[document browserController] openBookmark:bookmark];
        
        // Make window key
        NSWindow*   window;
        window = [[document browserController] window];
        if (![window isKeyWindow]) {
            [window makeKeyWindow];
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- Window menu actions --
//--------------------------------------------------------------//

- (void)showDownloadsPanelAction:(id)sender
{
    [[SRDownloadController sharedInstance] showWindow:sender];
}

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

- (void)applicationWillTerminate:(NSNotification*)notification
{
#if 0
    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];
    }
#endif
    
    // Save settings
    [self saveShiiraData];
    [self saveWebHistory];
    [self saveKeyBindings];
    //[[SRBookmarkStorage sharedInstance] saveBookmarks];
#if 0
    [[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);
    }
#endif
}

//--------------------------------------------------------------//
#pragma mark -- Apple menu action --
//--------------------------------------------------------------//

- (void)blockPopUpAction:(id)sender
{
    // Negate javaScriptCanOpenWindowsAutomatically
    BOOL    flag;
    flag = [[SRPreference webPreferences] javaScriptCanOpenWindowsAutomatically];
    [[SRPreference webPreferences] 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?", nil)];
    [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."), nil)];
    [alert addButtonWithTitle:NSLocalizedString(@"Empty", nil)];
    [alert addButtonWithTitle:NSLocalizedString(@"Cancel", nil)];
    
    int result;
    result = [alert runModal];
    if (result != NSAlertFirstButtonReturn) {
        return;
    }
    
    // Empty cache
    [[SRCacheController sharedInstance] removeAllCache];
}

- (void)togglePrivateBrowseAction:(id)sender
{
    // Get WebPreferences
    WebPreferences* preferences;
    preferences = [SRPreference webPreferences];
    
    // 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?", nil)];
        [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.", nil)];
        [alert setAlertStyle:NSInformationalAlertStyle];
        [alert addButtonWithTitle:NSLocalizedString(@"OK", nil)];
        [alert addButtonWithTitle:NSLocalizedString(@"Cancel", nil)];
        
        int result;
        result = [alert runModal];
        [alert release];
        if (result != NSAlertFirstButtonReturn) {
            return;
        }
    }
    
    // Toggle private browse mode
    [preferences setPrivateBrowsingEnabled:!isPrivate];
}

//--------------------------------------------------------------//
#pragma mark -- Window menu action --
//--------------------------------------------------------------//

- (void)showCacheAction:(id)sender
{
}

- (void)showSearchEnginesAction:(id)sender
{
}

- (void)toggleDownloadPanelAction:(id)sender
{
    // Toggle download panel
    SRDownloadController*   controller;
    controller = [SRDownloadController sharedInstance];
    if ([[controller window] isVisible]) {
        [controller close];
    }
    else {
        [controller showWindow:sender];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Help menu action --
//--------------------------------------------------------------//

- (void)showShiiraHomePageAction:(id)sender
{
}

//--------------------------------------------------------------//
#pragma mark -- Menu item validation --
//--------------------------------------------------------------//

- (BOOL)validateMenuItem:(id<NSMenuItem>)menuItem
{
    // Get tag
    int tag;
    tag = [menuItem tag];
    
    // Check full screen mode status
    SRBrowserController*    mainBrowserController;
    mainBrowserController = [SRBrowserController mainBrowserController];
    if (mainBrowserController) {
        // For full screen mode
        if ([mainBrowserController isFullScreenMode]) {
            switch (tag) {
            case SRAboutShiiraTag:
            case SRPreferencesTag:
            case SRBlockPopupWindowTag:
            case SRClearCacheTag:
            case SRPrivateBrowseTag: {
                return NO;
            }
            }
        }
    }
    
    switch (tag) {
    // Application menu
    case SRBlockPopupWindowTag: {
        // Get blokc pop up setting
        BOOL    isBlockedPopup;
        isBlockedPopup = ![[SRPreference webPreferences] javaScriptCanOpenWindowsAutomatically]; 
        [menuItem setState:isBlockedPopup ? NSOnState : NSOffState];
        return YES;
    }
    case SRPrivateBrowseTag: {
        // Get private browse setting
        BOOL    isPrivate;
        isPrivate = [[SRPreference webPreferences] privateBrowsingEnabled];
        [menuItem setState:isPrivate ? NSOnState : NSOffState];
        return YES;
    }
    
    // File menu
    case SRCloseTabTag: {
        [menuItem setKeyEquivalent:@""];
        return NO;
    }
    
    // History menu
    case SRHistoryPanelTag: {
        BOOL    isVisible;
        isVisible = [[[SRHistoryController sharedInstance] window] isVisible];
        [menuItem setState:isVisible ? NSOnState : NSOffState];
        return YES;
    }
    
    // Bookmark menu
    case SRBookmarkPanelTag: {
        BOOL    isVisible;
        isVisible = [[[SRBookmarkController sharedInstance] window] isVisible];
        [menuItem setState:isVisible ? NSOnState : NSOffState];
        return YES;
    }
    
    // Window menu
    case SRDownloadsPanelTag: {
        BOOL    isVisible;
        isVisible = [[[SRDownloadController sharedInstance] window] isVisible];
        [menuItem setState:isVisible ? NSOnState : NSOffState];
        return YES;
    }
    }
    
    return YES;
}

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

- (void)_updateBrowserMenu:(NSMenu*)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 = SRBrowserMenuItems(NO);
    enumerator = [browserMenuItems objectEnumerator];
    while (menuItem = [enumerator nextObject]) {
        [browserMenu addItem:menuItem];
    }
}

- (void)_updateTextEncodingMenu
{
    // Get encoding menu
    NSMenu* encodingMenu;
    encodingMenu = SRTextEncodingMenu();
    
    // Append text encoding menu
    NSArray*        encodingItems;
    NSEnumerator*   enumerator;
    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'
    NSMenuItem* defaultItem;
    defaultItem = [encodingMenu itemAtIndex:0];
    [defaultItem setAction:[[encodingItems objectAtIndex:0] action]];
}

- (void)menuNeedsUpdate:(NSMenu*)menu
{
    // Browser menu
    if (menu == SRBrowserMenu()) {
        static BOOL _browserMenuUpdated = NO;
        if (!_browserMenuUpdated) {
            [self _updateBrowserMenu:menu];
            _browserMenuUpdated = YES;
        }
    }
    
    // Text encoding menu
    if (menu == SRTextEncodingMenu()) {
        static BOOL _textEncodingMenuUpdated = NO;
        if (!_textEncodingMenuUpdated) {
            [self _updateTextEncodingMenu];
            _textEncodingMenuUpdated = YES;
        }
    }
}

- (BOOL)menuHasKeyEquivalent:(NSMenu*)menu 
        forEvent:(NSEvent*)event 
        target:(id*)target 
        action:(SEL*)action
{
    // Get attributes
    unsigned int    modifierFlags;
    BOOL            cmdFlag, optFlag, shiftFlag;
    NSString*       characters;
    modifierFlags = [event modifierFlags];
    cmdFlag = (modifierFlags & NSCommandKeyMask) != 0;
    optFlag = (modifierFlags & NSAlternateKeyMask) != 0;
    shiftFlag = (modifierFlags & NSShiftKeyMask) != 0;
    characters = [event charactersIgnoringModifiers];
    
    // For file menu
    if (menu == SRFileMenu()) {
        // For cmd + 'w'
        if (cmdFlag && !optFlag && !shiftFlag && [characters isEqualToString:@"w"]) {
            // Use close window action
            *target = nil;
            *action = @selector(closeWindowAction:);
            
            return YES;
        }
    }
    
    return NO;
}

//--------------------------------------------------------------//
#pragma mark -- NSToolbarItemValidation informal protocol --
//--------------------------------------------------------------//

- (BOOL)validateToolbarItem:(NSToolbarItem*)item
{
    // Get item identifier
    id  identifier;
    identifier = [item itemIdentifier];
    
    // Bookmark panel
    if ([identifier isEqualToString:SRBrowserBookmarkPanelItem]) {
        // Check image
        BOOL    isVisible;
        isVisible = [[[SRBookmarkController sharedInstance] window] isVisible];
        if (isVisible && [item image] != [NSImage imageNamed:@"BookmarkPanel_on"]) {
            [item setImage:[NSImage imageNamed:@"BookmarkPanel_on"]];
        }
        else if (!isVisible && [item image] != [NSImage imageNamed:@"BookmarkPanel"]) {
            [item setImage:[NSImage imageNamed:@"BookmarkPanel"]];
        }
        
        return YES;
    }
    // History panel
    if ([identifier isEqualToString:SRBrowserHistoryPanelItem]) {
        // Check image
        BOOL    isVisible;
        isVisible = [[[SRHistoryController sharedInstance] window] isVisible];
        if (isVisible && [item image] != [NSImage imageNamed:@"HistoryPanel_on"]) {
            [item setImage:[NSImage imageNamed:@"HistoryPanel_on"]];
        }
        else if (!isVisible && [item image] != [NSImage imageNamed:@"HistoryPanel"]) {
            [item setImage:[NSImage imageNamed:@"HistoryPanel"]];
        }
        
        return YES;
    }
    // Page info panel
    if ([identifier isEqualToString:SRBrowserPageInfoPanelItem]) {
        return YES;
    }
    // Find panel
    if ([identifier isEqualToString:SRBrowserFindPanelItem]) {
        return YES;
    }
    // Download panel
    if ([identifier isEqualToString:SRBrowserDownloadPanelItem]) {
        // Check image
        BOOL    isVisible;
        isVisible = [[[SRDownloadController sharedInstance] window] isVisible];
        if (isVisible && [item image] != [NSImage imageNamed:@"DownloadPanel_on"]) {
            [item setImage:[NSImage imageNamed:@"DownloadPanel_on"]];
        }
        else if (!isVisible && [item image] != [NSImage imageNamed:@"DownloadPanel"]) {
            [item setImage:[NSImage imageNamed:@"DownloadPanel"]];
        }
        
        return YES;
    }
    
    return NO;
}

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

- (void)_saveWebHistoryLater
{
    // Save history after delay
    if (!_willSaveHistory) {
        [self performSelector:@selector(_saveWebHistory) withObject:nil afterDelay:60];
        
        _willSaveHistory = YES;
    }
}

- (void)webHistoryItemsAdded:(NSNotification*)notification
{
    // Save web history later
    [self _saveWebHistoryLater];
}

- (void)webHistoryItemsRemoved:(NSNotification*)notification
{
    // Save web history later
    [self _saveWebHistoryLater];
}

- (void)webHistoryAllItemsRemoved:(NSNotification*)notification
{
    // Save web history later
    [self _saveWebHistoryLater];
}

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

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

- (void)observeValueForKeyPath:(NSString*)keyPath 
        ofObject:(id)object 
        change:(NSDictionary*)change 
        context:(void *)context
{
    // For user defaults
    if (object == [NSUserDefaults standardUserDefaults]) {
        // Bookmark pref
        if ([keyPath isEqualToString:SRBookmarkMenuUsageFlags]) {
        }
    }
}

@end
