/*
SRPageInfoController.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 "SRAppController.h"
#import "SRDefaultKeys.h"
#import "SRPageController.h"
#import "SRPageInfoController.h"

#import "SRPrefDefaultKeys.h"

#import "SRImageInfoCell.h"

// Notifications
NSString*   SRPageInfoLinkSelectedNotification = @"SRPageInfoLinkSelectedNotification";

// Frame auto save name
NSString*   SRPageInfoPanelFrameAutoSaveName = @"SRPageInfoPanelFrameAutoSaveName";

static NSMutableArray*  _instances = nil;
static NSDictionary*    _domAttrDict = nil;

static float            _linksTextViewMinHeight = 100.0f;
static float            _linksImageViewMinHeight = 100.0f;
static float            _imagesImageViewMinHeight = 100.0f;
static float            _otherTextViewMinHeight = 100.0f;
static float            _otherWebViewMinHeight = 100.0f;

static int              _imageCellSizeWidth = 80;
static int              _imageCellSizeHeight = 80;

@interface SRPageInfoController (private)
- (void)_updateLinksView;
- (void)_updateLinksPredicate;
- (void)_updateLinksSearchMenu;
- (void)_updateImagesMatrix;
- (void)_updateImagesView;
- (void)_updateSourceViewBackground;
- (void)_updateOtherView;
- (void)_updateInfoView;

- (void)linksScrollViewBondsChanged:(NSNotification*)notification;
- (void)imagesScrollViewBondsChanged:(NSNotification*)notification;
- (void)otherScrollViewBondsChanged:(NSNotification*)notification;
@end

@implementation SRPageInfoController

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

+ (SRPageInfoController*)pageInfoControllerWithPageController:(SRPageController*)pageController
{
    // Get URL string
    NSString*   URLString;
    URLString = [[[[[[pageController webView] mainFrame] dataSource] request] URL] absoluteString];
    if (URLString) {
        // Find page info controller
        NSEnumerator*           enumerator;
        SRPageInfoController*   pageInfoController;
        enumerator = [_instances objectEnumerator];
        while (pageInfoController = [enumerator nextObject]) {
            if ([[pageInfoController URLString] isEqualToString:URLString]) {
                return pageInfoController;
            }
        }
    }
    
    // Create new page info controller
    SRPageInfoController*   pageInfoController;
    pageInfoController = [[SRPageInfoController alloc] init];
    [pageInfoController loadWindow];
    [pageInfoController setPageController:pageController];
    
    // Make page controller observer
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:pageController selector:@selector(linkSelected:) 
            name:SRPageInfoLinkSelectedNotification object:pageInfoController];
    
    // Read DOM attribute dictionary
    if (!_domAttrDict) {
        NSString*   path;
        path = [[NSBundle mainBundle] pathForResource:@"DOMAttr" ofType:@"plist"];
        if (path) {
            _domAttrDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
            if (!_domAttrDict) {
                NSLog(@"Failed to read DOMAttr.plist");
            }
        }
    }
    
    return pageInfoController;
}

- (void)_setProfileObjects
{
    static NSDictionary*    _attr = nil;
    if (!_attr) {
        NSShadow*   shadow;
        shadow = [[NSShadow alloc] init];
        [shadow setShadowBlurRadius:0.0f];
        [shadow setShadowColor:[NSColor whiteColor]];
        [shadow setShadowOffset:NSMakeSize(0, -1.0f)];
        
        _attr = [[NSDictionary alloc] initWithObjectsAndKeys:
                [NSFont boldSystemFontOfSize:11], NSFontAttributeName, 
                [NSColor grayColor], NSForegroundColorAttributeName, 
                shadow, NSShadowAttributeName, 
                nil];
        [shadow release];
    }
    
    // For HTML page
    NSMutableDictionary*    general;
    general = [NSMutableDictionary dictionary];
    [general setValue:NSLocalizedString(@"General Info", nil) forKey:@"title"];
    [general setValue:@"HTML" forKey:@"type"];
    [_profiles addObject:general];
    
    NSMutableDictionary*    advanced;
    NSAttributedString*     advancedAttrTitle;
    advanced = [NSMutableDictionary dictionary];
    [advanced setValue:NSLocalizedString(@"Advanced Info", nil) forKey:@"title"];
    [advanced setValue:@"category" forKey:@"type"];
    
    advancedAttrTitle = [[NSAttributedString alloc] 
            initWithString:NSLocalizedString(@"Advanced Info", nil) attributes:_attr];
    [advanced setValue:advancedAttrTitle forKey:@"attributedTitle"];
    [advancedAttrTitle release];
    
    [_profiles addObject:advanced];
    
    NSMutableDictionary*    links;
    links = [NSMutableDictionary dictionary];
    [links setValue:NSLocalizedString(@"Links", nil) forKey:@"title"];
    [links setValue:@"HTML" forKey:@"type"];
    [_profiles addObject:links];
    
    NSMutableDictionary*    domTrees;
    domTrees = [NSMutableDictionary dictionary];
    [domTrees setValue:NSLocalizedString(@"DOM", nil) forKey:@"title"];
    [domTrees setValue:@"HTML" forKey:@"type"];
    [_profiles addObject:domTrees];
    
    NSMutableDictionary*    cookies;
    cookies = [NSMutableDictionary dictionary];
    [cookies setValue:NSLocalizedString(@"Cookies", nil) forKey:@"title"];
    [cookies setValue:@"HTML" forKey:@"type"];
    [_profiles addObject:cookies];
    
    NSMutableDictionary*    files;
    NSAttributedString*     filesAttrTitle;
    files = [NSMutableDictionary dictionary];
    [files setValue:NSLocalizedString(@"Files", nil) forKey:@"title"];
    [files setValue:@"category" forKey:@"type"];
    
    filesAttrTitle = [[NSAttributedString alloc] 
            initWithString:NSLocalizedString(@"Files", nil) attributes:_attr];
    [files setValue:filesAttrTitle forKey:@"attributedTitle"];
    [filesAttrTitle release];
    
    [_profiles addObject:files];
    
    NSMutableDictionary*    source;
    source = [NSMutableDictionary dictionary];
    [source setValue:NSLocalizedString(@"HTML", nil) forKey:@"title"];
    [source setValue:@"HTML" forKey:@"type"];
    [_profiles addObject:source];
    
    NSMutableDictionary*    css;
    css = [NSMutableDictionary dictionary];
    [css setValue:NSLocalizedString(@"CSS", nil) forKey:@"title"];
    [css setValue:@"HTML" forKey:@"type"];
    [_profiles addObject:css];
    
    NSMutableDictionary*    javaScript;
    javaScript = [NSMutableDictionary dictionary];
    [javaScript setValue:NSLocalizedString(@"JavaScript", nil) forKey:@"title"];
    [javaScript setValue:@"HTML" forKey:@"type"];
    [_profiles addObject:javaScript];
    
    NSMutableDictionary*    images;
    images = [NSMutableDictionary dictionary];
    [images setValue:NSLocalizedString(@"Images", nil) forKey:@"title"];
    [images setValue:@"HTML" forKey:@"type"];
    [_profiles addObject:images];
    
    NSMutableDictionary*    other;
    other = [NSMutableDictionary dictionary];
    [other setValue:NSLocalizedString(@"Others", nil) forKey:@"title"];
    [other setValue:@"HTML" forKey:@"type"];
    [_profiles addObject:other];
}

- (id)init
{
    self = [super initWithWindowNibName:@"PageInfo"];
    if (!self) {
        return nil;
    }
    
    // Initialize
    _isInitialized = NO;
    _profiles = [[NSMutableArray array] retain];
    _linksSearchType = SRPageInfoSearchLinkAll;
    _images = [[NSMutableArray array] retain];
    _DOMNodes = [[NSMutableSet set] retain];
    
    // Register itself
    if (!_instances) {
        _instances = [[NSMutableArray array] retain];
    }
    [_instances addObject:self];
    
    // Register observer
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(matrixFrameDidChange:) 
            name:NSViewFrameDidChangeNotification object:_imagesMatrix];
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    [defaults addObserver:self forKeyPath:SRSourceFontSize 
            options:NSKeyValueObservingOptionNew context:NULL];
    [defaults addObserver:self forKeyPath:SRSourceDefaultColor 
            options:NSKeyValueObservingOptionNew context:NULL];
    [defaults addObserver:self forKeyPath:SRSourceTagColor 
            options:NSKeyValueObservingOptionNew context:NULL];
    [defaults addObserver:self forKeyPath:SRSourceCommentColor 
            options:NSKeyValueObservingOptionNew context:NULL];
    [defaults addObserver:self forKeyPath:SRSourceBackgroundColor 
            options:NSKeyValueObservingOptionNew context:NULL];
    
    return self;
}

- (void)awakeFromNib
{
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    
    // Configure window
    NSWindow*   window;
    window = [self window];
    [window setFrameAutosaveName:SRPageInfoPanelFrameAutoSaveName];
    
    // Configure table view
    [_profileTableView setWantsLiveResizeToUseCachedImage:NO];
    
    // Set image text cell
    NSTableColumn*          column;
    NSCell*                 oldCell;
    HMImageTextFieldCell*   cell;
    column = [_profileTableView tableColumnWithIdentifier:@"profile"];
    oldCell = [column dataCell];
    cell = [[HMImageTextFieldCell alloc] init];
    [cell setFont:[oldCell font]];
    [cell setUseBoldForHighlight:YES];
    [column setDataCell:cell];
    [cell release];
    
    // Set header cell
    NSCell*             oldHeaderCell;
    HMTableHeaderCell*  headerCell;
    column = [_profileTableView tableColumnWithIdentifier:@"profile"];
    oldHeaderCell = [column headerCell];
    headerCell = [[HMTableHeaderCell alloc] init];
    [headerCell setObjectValue:[oldHeaderCell objectValue]];
    [headerCell setAlignment:[oldHeaderCell alignment]];
    [headerCell setFont:[oldHeaderCell font]];
    [column setHeaderCell:headerCell];
    [headerCell release];
    
    // Set header view
    NSTableHeaderView*  oldHeaderView;
    HMTableHeaderView*  headerView;
    oldHeaderView = [_profileTableView headerView];
    headerView = [[HMTableHeaderView alloc] initWithFrame:[oldHeaderView frame]];
    [headerView setSplitView:_splitView];
    [_profileTableView setHeaderView:headerView];
    
    // Configure split view
    [_splitView setSplitType:HMSplitLine];
    
    float   sideWidth;
    sideWidth = [[NSUserDefaults standardUserDefaults] floatForKey:SRPageInfoSideWidth];
    if ([[_splitView subviews] count] > 0 && sideWidth > 0) {
        // Set subview width
        NSView* subview;
        NSRect  frame;
        float   delta;
        
        subview = [[_splitView subviews] objectAtIndex:0];
        frame = [subview frame];
        delta = frame.size.width - sideWidth;
        frame.size.width = sideWidth;
        [subview setFrame:frame];
        
        subview = [[_splitView subviews] objectAtIndex:1];
        frame = [subview frame];
        frame.size.width += delta;
        [subview setFrame:frame];
        
        [_splitView adjustSubviews];
    }
    
    _isInitialized = YES;
    
    // Configure links view
    _linksTextViewMinHeight = [_linksTextView frame].size.height;
    _linksImageViewMinHeight = [_linksImageView frame].size.height;
    [_linksScrollView setPostsBoundsChangedNotifications:YES];
    [_linksScrollView setPostsFrameChangedNotifications:YES];
    [center addObserver:self selector:@selector(linksScrollViewBondsChanged:) 
            name:NSViewBoundsDidChangeNotification object:_linksScrollView];
    [center addObserver:self selector:@selector(linksScrollViewBondsChanged:) 
            name:NSViewFrameDidChangeNotification object:_linksScrollView];
    
    [_linksSplitView setSplitType:HMSplitWide];
    [_linksTableView setDoubleAction:@selector(linksDoubleClickAction:)];
    
    [self _updateLinksView];
    [self _updateLinksSearchMenu];
    
    // Configure DOM trees view
    [_domTreesSplitView setSplitType:HMSplitWide];
    
    // Configure CSS view
    [_cssTableView setDraggingSourceOperationMask:NSDragOperationAll_Obsolete forLocal:NO];
    [_cssSplitView setSplitType:HMSplitWide];
    
    // Configure JavaScirpt view
    [_javaScriptTableView setDraggingSourceOperationMask:NSDragOperationAll_Obsolete forLocal:NO];
    [_javaScriptSplitView setSplitType:HMSplitWide];
    
    // Configure images view
    _imagesImageViewMinHeight = [_imagesImageView frame].size.height;
    [_imagesImageScrollView setPostsBoundsChangedNotifications:YES];
    [_imagesImageScrollView setPostsFrameChangedNotifications:YES];
    [center addObserver:self selector:@selector(imagesScrollViewBondsChanged:) 
            name:NSViewBoundsDidChangeNotification object:_imagesImageScrollView];
    [center addObserver:self selector:@selector(imagesScrollViewBondsChanged:) 
            name:NSViewFrameDidChangeNotification object:_imagesImageScrollView];
    
    [_imagesSplitView setSplitType:HMSplitWide];
    
    // Configure matrix
    _imagesMatrix = [[NSMatrix alloc] initWithFrame:NSZeroRect 
            mode:NSRadioModeMatrix 
            cellClass:[SRImageInfoCell class] 
            numberOfRows:0 
            numberOfColumns:0];
    [_imagesMatrix autorelease];
    [_imagesMatrix setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
    [_imagesMatrix setPostsFrameChangedNotifications:YES];
    [_imagesMatrix setAllowsEmptySelection:YES];
    [_imagesMatrix setBackgroundColor:[NSColor colorWithDeviceWhite:0.8f alpha:1.0f]];
    [_imagesMatrix setDrawsBackground:YES];
    [_imagesMatrix setTarget:self];
    [_imagesMatrix setAction:@selector(imageSelectedAction:)];
    [_imagesMatrixScrollView setDocumentView:_imagesMatrix];
    
    // Configure source view
    [_sourceOutlineView setDraggingSourceOperationMask:NSDragOperationAll_Obsolete forLocal:NO];
    [_sourceSplitView setSplitType:HMSplitWide];
    [self _updateSourceViewBackground];
    
    // Configure other view
    _otherTextViewMinHeight = [_otherTextView frame].size.height;
    _otherWebViewMinHeight = [_otherWebView frame].size.height;
    [_otherScrollView setPostsBoundsChangedNotifications:YES];
    [_otherScrollView setPostsFrameChangedNotifications:YES];
    [center addObserver:self selector:@selector(otherScrollViewBondsChanged:) 
            name:NSViewBoundsDidChangeNotification object:_otherScrollView];
    [center addObserver:self selector:@selector(otherScrollViewBondsChanged:) 
            name:NSViewFrameDidChangeNotification object:_otherScrollView];
    
    [_otherTableView setDraggingSourceOperationMask:NSDragOperationAll_Obsolete forLocal:NO];
    [_otherSplitView setSplitType:HMSplitWide];
    
    // Set profiles
    [self _setProfileObjects];
    
    // Relaod data
    [_profileTableView reloadData];
}

- (void)dealloc
{
    // Remove observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    [_profiles release], _profiles = nil;
    [_images release], _images = nil;
    [_DOMNodes release], _DOMNodes = nil;
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Page info handling --
//--------------------------------------------------------------//

- (NSString*)URLString
{
    return [[_objectController content] valueForKey:@"generalURL"];
}

- (NSString*)profileTitle
{
    // Get selected profile
    int             row;
    NSDictionary*   profile;
    row = [_profileTableView selectedRow];
    if (row == NSNotFound || row >= [_profiles count]) {
        return nil;
    }
    profile = [_profiles objectAtIndex:row];
    
    // Get title
    return [profile valueForKey:@"title"];
}

- (NSIndexPath*)_indexPathWithTitle:(NSString*)title node:(id)node
{
    // Get observed object
    id  profile;
    profile = [node observedObject];
    
    // Check title
    if ([[profile objectForKey:@"title"] isEqualToString:title]) {
        return [node indexPath];
    }
    
    // Get subnode
    unsigned int    i;
    for (i = 0; i < [node count]; i++) {
        id  indexPath;
        indexPath = [self _indexPathWithTitle:title node:[node subnodeAtIndex:i]];
        if (indexPath) {
            return indexPath;
        }
    }
    
    return nil;
}

- (void)setProfileWithTitle:(NSString*)title
{
    // Find profile with title
    int i, index = NSNotFound;
    for (i = 0; i < [_profiles count]; i++) {
        NSDictionary*   profile;
        profile = [_profiles objectAtIndex:i];
        
        // Check title
        if ([title isEqualToString:[profile objectForKey:@"title"]]) {
            index = i;
            break;
        }
    }
    
    if (index == NSNotFound) {
        return;
    }
    
    // Select row
    [_profileTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
}

- (NSArray*)_createFieldItemsWithHeaderFields:(NSDictionary*)headerFields
{
    NSMutableArray*    items;
    items = [NSMutableArray array];
    
    NSEnumerator*   enumerator;
    NSString*       key;
    enumerator = [headerFields keyEnumerator];
    while (key = [enumerator nextObject]) {
        NSString*   value;
        value = [headerFields objectForKey:key];
        if (!value) {
            continue;
        }
        
        NSDictionary*   item;
        item = [NSDictionary dictionaryWithObjectsAndKeys:
                key, @"key", 
                value, @"value", nil];
        
        [items addObject:item];
    }
    
    return items;
}

- (NSAttributedString*)_colorizedSourceWithString:(NSString*)source
{
    if (!source) {
        return nil;
    }
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Get colors
    static NSColor* _textColor = nil;
    if (!_textColor) {
        _textColor = [[NSColor blackColor] retain];
    }
    
    NSData*     data;
    NSColor*    textColor = nil;
    data = [defaults objectForKey:SRSourceDefaultColor];
    if (data) {
        textColor = [NSUnarchiver unarchiveObjectWithData:data];
    }
    if (!textColor) {
        textColor = _textColor;
    }
    
    // Get font
    NSString*   fontName;
    int         fontSize;
    NSFont*     font;
    fontName = [defaults stringForKey:SRSourceFontName];
    if (!fontName) {
        fontName = @"Monaco";
    }
    fontSize = [defaults integerForKey:SRSourceFontSize];
    if (fontSize < 4) {
        fontSize = 12;
    }
    
    font = [NSFont fontWithName:fontName size:fontSize];
    
    // Create attributed string
    NSMutableAttributedString*  attrString;
    attrString = [[NSMutableAttributedString alloc] initWithString:source];
    [attrString autorelease];
    [attrString addAttribute:NSForegroundColorAttributeName 
            value:textColor range:NSMakeRange(0, [attrString length])];
    
    // Set font
    [attrString addAttribute:NSFontAttributeName
            value:font range:NSMakeRange(0, [attrString length])];
    
    return attrString;
}

- (NSAttributedString*)_colorizedHTMLSourceWithString:(NSString*)source
{
    if (!source) {
        return nil;
    }
    
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Get colors
    static NSColor* _textColor = nil;
    static NSColor* _tagColor = nil;
    static NSColor* _commentColor = nil;
    if (!_textColor) {
        _textColor = [[NSColor blackColor] retain];
        _tagColor = [[NSColor colorWithDeviceRed:0.1 green:0.1 blue:0.648 alpha:1.0] retain];
        _commentColor = [[NSColor colorWithDeviceRed:0.632 green:0 blue:0 alpha:1.0] retain];
    }
    
    NSData*     data;
    NSColor*    textColor = nil;
    NSColor*    tagColor = nil;
    NSColor*    commentColor = nil;
    data = [defaults objectForKey:SRSourceDefaultColor];
    if (data) {
        textColor = [NSUnarchiver unarchiveObjectWithData:data];
    }
    if (!textColor) {
        textColor = _textColor;
    }
    
    data = [defaults objectForKey:SRSourceTagColor];
    if (data) {
        tagColor = [NSUnarchiver unarchiveObjectWithData:data];
    }
    if (!tagColor) {
        tagColor = _tagColor;
    }
    
    data = [defaults objectForKey:SRSourceCommentColor];
    if (data) {
        commentColor = [NSUnarchiver unarchiveObjectWithData:data];
    }
    if (!commentColor) {
        commentColor = _commentColor;
    }
    
    // Get font
    NSString*   fontName;
    int         fontSize;
    NSFont*     font;
    fontName = [defaults stringForKey:SRSourceFontName];
    if (!fontName) {
        fontName = @"Monaco";
    }
    fontSize = [defaults integerForKey:SRSourceFontSize];
    if (fontSize < 4) {
        fontSize = 12;
    }
    
    font = [NSFont fontWithName:fontName size:fontSize];
    
    // Create attributed string
    NSMutableAttributedString*  attrString;
    attrString = [[NSMutableAttributedString alloc] initWithString:source];
    [attrString autorelease];
    [attrString setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
            textColor, NSForegroundColorAttributeName, 
            @"textColor", @"SRColorAttributeName", nil]
            range:NSMakeRange(0, [attrString length])];
    
    // Scan source
    NSScanner*  scanner;
    int         sourceLength;
    scanner = [NSScanner scannerWithString:source];
    sourceLength = [source length];
    while (![scanner isAtEnd]) {
        // Find next '<' character
        int tagStart, tagEnd;
        [scanner scanUpToString:@"<" intoString:nil];
        tagStart = [scanner scanLocation];		
        
        // For '<!--'
        if (sourceLength > tagStart + 6 && 
            [[source substringWithRange:NSMakeRange(tagStart + 1, 3)] isEqualToString:@"!--"])
        {
            // Find '-->'
            if ([scanner scanUpToString:@"-->" intoString:nil]) {
                tagEnd = [scanner scanLocation] + 3;
                if (tagEnd > sourceLength) {
                    tagEnd = sourceLength;
                }
                if (sourceLength > tagEnd) {
                    [attrString setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
                            commentColor, NSForegroundColorAttributeName, 
                            @"commentColor", @"SRColorAttributeName", nil]
                            range:NSMakeRange(tagStart, tagEnd - tagStart)];
                }
            }
        }
        else if (sourceLength > tagStart) {
            // Find '>'
            if ([scanner scanUpToString:@">" intoString:nil]) {
                tagEnd = [scanner scanLocation] + 1;
                if (tagEnd > sourceLength) {
                    tagEnd = sourceLength;
                }
                [attrString setAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
                        tagColor, NSForegroundColorAttributeName, 
                        @"tagColor", @"SRColorAttributeName", nil]
                        range:NSMakeRange(tagStart, tagEnd - tagStart)];
            }
        }
    }
    
    // Set font
    [attrString addAttribute:NSFontAttributeName
            value:font range:NSMakeRange(0, [attrString length])];
    
    return attrString;
}

- (void)_setGeneralPageInfoWithPageController:(SRPageController*)pageController
{
    // Get web view and web data source
    WebView*        webView;
    WebDataSource*  dataSource;
    webView = [pageController webView];
    dataSource = [[webView mainFrame] dataSource];
    
    // Set general info
    id  content;
    content = [_objectController content];
    
    [content setValue:[dataSource pageTitle] forKey:@"generalTitle"];
    [content setValue:[[[dataSource request] URL] absoluteString] forKey:@"generalURL"];
    [content setValue:[dataSource textEncodingName] forKey:@"generalTextEncoding"];
    
    // Get icon
    NSImage*    icon;
    icon = [[WebIconDatabase sharedIconDatabase] 
            iconForURL:[content valueForKey:@"generalURL"] withSize:NSMakeSize(16.0f, 16.0f)];
    if (icon) {
        [content setValue:icon forKey:@"generalIcon"];
    }
    
    // Get loading time
    float       loadingTime;
    NSString*   loadingTimeStr;
    loadingTime = [pageController loadingTime];
    if (loadingTime == -1) {
        loadingTimeStr = NSLocalizedString(UTF8STR("Now loading…"), nil);
    }
    else {
        loadingTimeStr = [NSString stringWithFormat:@"%5.2f %@", 
                loadingTime, NSLocalizedString(@"seconds", nil)];
    }
    [content setValue:loadingTimeStr forKey:@"generalLoadingTime"];
    
    // Clear HTTP header
    content = [_httpHeaderTreeController content];
    [content removeAllObjects];
    
    // Get request header fields
    NSURLRequest*   request;
    NSDictionary*   headerFields;
    request = [dataSource request];
    headerFields = [request allHTTPHeaderFields];
    if (headerFields) {
        [content addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                NSLocalizedString(@"Request", nil), @"key", 
                [self _createFieldItemsWithHeaderFields:headerFields], @"children", 
                nil]];
    }
    
    // Get response header fields
    NSURLResponse*  response;
    response = [dataSource response];
    if ([response respondsToSelector:@selector(allHeaderFields)]) {
        headerFields = [(NSHTTPURLResponse*)response allHeaderFields];
        if (headerFields) {
            [content addObject:[NSDictionary dictionaryWithObjectsAndKeys:
                    NSLocalizedString(@"Response", nil), @"key", 
                    [self _createFieldItemsWithHeaderFields:headerFields], @"children", 
                    nil]];
        }
    }
    
    // Reload data
    [_httpHeaderOutlineView reloadData];
    
    // Expand items
    id  item;
    item = [_httpHeaderOutlineView itemAtRow:1];
    if (item) {
        [_httpHeaderOutlineView expandItem:item expandChildren:YES];
    }
    item = [_httpHeaderOutlineView itemAtRow:0];
    if (item) {
        [_httpHeaderOutlineView expandItem:item expandChildren:YES];
    }
}

- (void)_setLinksPageInfoWithPageController:(SRPageController*)pageController
{
    // Remove old links
    [_linksArrayController removeObjects:[_linksArrayController arrangedObjects]];
    
    // Get DOM document
    WebView*        webView;
    DOMDocument*    domDocument;
    webView = [pageController webView];
    domDocument = [[webView mainFrame] DOMDocument];
    if (!domDocument) {
        return;
    }
    
    // Get anchor tags
    NSMutableArray*     anchorObjects;
    HMAnchorNodeFilter* filter;
    DOMNodeIterator*    iterator;
    DOMNode*            node;
    anchorObjects = [NSMutableArray array];
    filter = [[HMAnchorNodeFilter alloc] init];
    [filter autorelease];
    iterator = [domDocument createNodeIterator:[domDocument documentElement] 
            :DOM_SHOW_ELEMENT :filter :NO];
    while (node = [iterator nextNode]) {
        DOMHTMLAnchorElement*   anchor;
        anchor = (DOMHTMLAnchorElement*)node;
        
        // Check href
        NSString*   href;
        href = [anchor href];
        if (!href || [href length] == 0) {
            continue;
        }
        
        // Create anchor object
        NSMutableDictionary*    anchorObject;
        anchorObject = [NSMutableDictionary dictionary];
        
        if (href) {
            [anchorObject setObject:href forKey:@"href"];
        }
        
        NSString*   name;
        name = [anchor name];
        if (name) {
            [anchorObject setObject:name forKey:@"name"];
        }
        
        NSString*   html;
        html = [anchor outerHTML];
        if (html) {
            [anchorObject setObject:html forKey:@"html"];
        }
        
        NSString*   text;
        text = [anchor innerText];
        if ([text length] > 0) {
            [anchorObject setObject:text forKey:@"text"];
        }
        else {
            // Find img node
            HMImageNodeFilter*      imgFilter;
            DOMNodeIterator*        imgIterator;
            DOMHTMLImageElement*    img;
            imgFilter = [[HMImageNodeFilter alloc] init];
            imgIterator = [domDocument createNodeIterator:anchor :DOM_SHOW_ELEMENT :imgFilter :NO];
            img = (DOMHTMLImageElement*)[imgIterator nextNode];
            if (img) {
                NSURL*      url = nil;
                NSString*   src;
                src = [img src];
                if ([src hasPrefix:@"http://"]) {
                    url = [NSURL URLWithString:src];
                }
                else {
                    // It might be relative URL
                    NSURL*  baseURL;
                    baseURL = [NSURL URLWithString:[(DOMHTMLDocument*)domDocument URL]];
                    if (baseURL) {
                        url = [NSURL URLWithString:src relativeToURL:url];
                    }
                }
                
                if (url) {
                    [anchorObject setObject:url forKey:@"imageURL"];
                }
                [anchorObject setObject:NSLocalizedString(@"(Image link)", nil) forKey:@"text"];
                [anchorObject setObject:[NSColor grayColor] forKey:@"textColor"];
            }
            else {
                [anchorObject setObject:NSLocalizedString(@"(No text)", nil) forKey:@"text"];
                [anchorObject setObject:[NSColor grayColor] forKey:@"textColor"];
            }
            
            [imgFilter release];
        }
        
        [anchorObject setObject:anchor forKey:@"domNode"];
        
        [anchorObjects addObject:anchorObject];
    }
    
    // Set anchor objects
    [_linksArrayController addObjects:anchorObjects];
    [_linksArrayController rearrangeObjects];
}

- (void)_setCookiesPageInfoWithPageController:(SRPageController*)pageController
{
    // Remove old cookies
    [_cookiesArrayController removeObjects:[_cssArrayController arrangedObjects]];
    
    // Get web view
    WebView*    webView;
    webView = [pageController webView];
    
    // Get cookies
    NSEnumerator*   enumerator;
    WebFrame*       frame;
    enumerator = [[webView webFrames] objectEnumerator];
    while (frame = [enumerator nextObject]) {
        NSURL*  url;
        url = [[[frame dataSource] request] URL];
        if (url) {
            NSArray*    cookies;
            cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] 
                    cookiesForURL:url];
            
            [_cookiesArrayController addObjects:cookies];
        }
    }
}

- (void)_setCSSPageInfoWithPageController:(SRPageController*)pageController
{
    // Remove old CSSs
    [_cssArrayController removeObjects:[_cssArrayController arrangedObjects]];
    
    // Get web view
    WebView*    webView;
    webView = [pageController webView];
    
    // Get CSS subresources
    NSArray*        subresources;
    NSArray*        cssResources;
    NSEnumerator*   enumerator;
    WebResource*    resource;
    subresources = [[webView mainFrame] subresources];
    cssResources = [subresources filteredArrayUsingPredicate:
            [NSPredicate predicateWithFormat:@"MIMEType == 'text/css'"]];
    enumerator = [cssResources objectEnumerator];
    while (resource = [enumerator nextObject]) {
        // Get resource info
        NSString*           URLString;
        NSString*           textEncodingName;
        NSStringEncoding    encoding = NSASCIIStringEncoding;
        NSData*             data;
        URLString = [[resource URL] absoluteString];
        if (!URLString) {
            URLString = @"(No URL)";
        }
        
        textEncodingName = [resource textEncodingName];
        if (textEncodingName) {
            CFStringEncoding    cfEncoding;
            cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)textEncodingName);
            encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
        }
        
        data = [resource data];
        
        // Get source
        NSString*           source;
        NSAttributedString* attrSource;
        source = [[NSString alloc] initWithData:data encoding:encoding];
        attrSource = [self _colorizedSourceWithString:source];
        [source release];
        
        // Add CSS info
        NSMutableDictionary*    css;
        css = [NSMutableDictionary dictionary];
        [css setObject:URLString forKey:@"file"];
        if (attrSource) {
            [css setObject:attrSource forKey:@"source"];
        }
        if (data) {
            [css setObject:data forKey:@"data"];
        }
        
        [_cssArrayController addObject:css];
    }
    [_cssArrayController setSelectionIndex:0];
}

- (void)_setJavaScriptPageInfoWithPageController:(SRPageController*)pageController
{
    // Remove old JavaScripts
    [_javaScriptArrayController removeObjects:[_javaScriptArrayController arrangedObjects]];
    
    // Get web view
    WebView*    webView;
    webView = [pageController webView];
    
    // Get JavaScript subresources
    NSArray*        subresources;
    NSArray*        jsResources;
    NSEnumerator*   enumerator;
    WebResource*    resource;
    subresources = [[webView mainFrame] subresources];
    jsResources = [subresources filteredArrayUsingPredicate:
            [NSPredicate predicateWithFormat:
                    @"(MIMEType == 'application/x-javascript') OR (MIMEType == 'text/javascript')"]];
    enumerator = [jsResources objectEnumerator];
    while (resource = [enumerator nextObject]) {
        // Get resource info
        NSString*           URLString;
        NSString*           textEncodingName;
        NSStringEncoding    encoding = NSASCIIStringEncoding;
        NSData*             data;
        URLString = [[resource URL] absoluteString];
        if (!URLString) {
            URLString = @"(No URL)";
        }
        
        textEncodingName = [resource textEncodingName];
        if (textEncodingName) {
            CFStringEncoding    cfEncoding;
            cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)textEncodingName);
            encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
        }
        
        data = [resource data];
        
        // Get source
        NSString*           source;
        NSAttributedString* attrSource;
        source = [[NSString alloc] initWithData:data encoding:encoding];
        attrSource = [self _colorizedSourceWithString:source];
        [source release];
        
        // Add JavaScirpt info
        NSMutableDictionary*    javaScript;
        javaScript = [NSMutableDictionary dictionary];
        [javaScript setObject:URLString forKey:@"file"];
        if (attrSource) {
            [javaScript setObject:attrSource forKey:@"source"];
        }
        if (data) {
            [javaScript setObject:data forKey:@"data"];
        }
        
        [_javaScriptArrayController addObject:javaScript];
    }
    [_javaScriptArrayController setSelectionIndex:0];
}

- (void)_setImagesPageInfoWithPageController:(SRPageController*)pageController
{
    // Remove old images
    [_images removeAllObjects];
    
    // Get web view
    WebView*    webView;
    webView = [pageController webView];
    
    // Get image subresources
    NSArray*        subresources;
    NSMutableArray* imageResources;
    NSEnumerator*   enumerator;
    WebResource*    resource;
    subresources = [[webView mainFrame] subresources];
    imageResources = [NSMutableArray array];
    enumerator = [subresources objectEnumerator];
    while (resource = [enumerator nextObject]) {
        NSString*   MIMEType;
        MIMEType = [resource MIMEType];
        if ([MIMEType hasPrefix:@"image/"]) {
            [imageResources addObject:resource];
        }
    }
    
    enumerator = [imageResources objectEnumerator];
    while (resource = [enumerator nextObject]) {
        // Get resource info
        NSString*   URLString;
        NSString*   MIMEType;
        NSData*     data;
        URLString = [[resource URL] absoluteString];
        if (!URLString) {
            URLString = @"(No URL)";
        }
        MIMEType = [resource MIMEType];
        data = [resource data];
        
        // Add image info
        NSMutableDictionary*    imageInfo;
        imageInfo = [NSMutableDictionary dictionary];
        [imageInfo setObject:URLString forKey:@"urlString"];
        if (MIMEType) {
            [imageInfo setObject:MIMEType forKey:@"mimeType"];
        }
        if (data) {
            [imageInfo setObject:data forKey:@"data"];
            
            NSString*   fileSize;
            fileSize = HMDataSizeString([data length]);
            if (fileSize) {
                [imageInfo setObject:fileSize forKey:@"fileSize"];
            }
            else {
                [imageInfo setObject:@"-" forKey:@"fileSize"];
            }
        }
        else {
            [imageInfo setObject:@"-" forKey:@"fileSize"];
        }
        
        [_images addObject:imageInfo];
    }
    
    // Update images matrix
    [self _updateImagesMatrix];
    [self _updateImagesView];
}

- (void)_addSourceWithWebFrame:(WebFrame*)frame content:(id)content
{
    // Get data source
    WebDataSource*  dataSource;
    dataSource = [frame dataSource];
    
    // Create source info
    NSMutableDictionary*    sourceInfo;
    sourceInfo = [NSMutableDictionary dictionary];
    
    // Set file path
    NSString*   filePath;
    filePath = [[[dataSource request] URL] absoluteString];
    if (filePath) {
        [sourceInfo setObject:filePath forKey:@"file"];
    }
    
    // Set HTML source
    NSString*   source;
    source = [[dataSource representation] documentSource];
    if (source) {
        [sourceInfo setObject:[self _colorizedHTMLSourceWithString:source] forKey:@"source"];
    }
    
    // Set data
    NSData* data;
    data = [dataSource data];
    if (data) {
        [sourceInfo setObject:data forKey:@"data"];
    }
    
    // Set DOM documnet
    DOMDocument*    document;
    document = [frame DOMDocument];
    if (document) {
        [sourceInfo setObject:document forKey:@"domDocument"];
    }
    
    // Set title
    NSString*   title;
    title = [dataSource pageTitle];
    if (title && [title length] > 0) {
        [sourceInfo setObject:title forKey:@"title"];
    }
    
    // Add child frames
    NSArray*    childFrames;
    childFrames = [frame childFrames];
    if ([childFrames count] > 0) {
        NSMutableArray* children;
        children = [NSMutableArray array];
        [sourceInfo setObject:children forKey:@"children"];
        
        NSEnumerator*   enumerator;
        WebFrame*       child;
        enumerator = [[frame childFrames] objectEnumerator];
        while (child = [enumerator nextObject]) {
            [self _addSourceWithWebFrame:child content:children];
        }
    }
    
    // Add source info
    [content addObject:sourceInfo];
}

- (void)_setSourcePageInfoWithPageController:(SRPageController*)pageController
{
    // Remove old source
    [[_sourceTreeController content] removeAllObjects];
    
    // Get main frame
    WebFrame*   mainFrame;
    mainFrame = [[pageController webView] mainFrame];
    
    // Add sources
    [self _addSourceWithWebFrame:mainFrame content:[_sourceTreeController content]];
    
    // When there is no children
    if ([[mainFrame childFrames] count] == 0) {
        [_sourceOutlineView setIndentationPerLevel:0];
        [_domTreesOutlineView setIndentationPerLevel:0];
    }
    else {
        [_sourceOutlineView setIndentationPerLevel:16.0f];
        [_domTreesOutlineView setIndentationPerLevel:16.0f];
    }
    
    // Reload data
    [_sourceOutlineView reloadData];
    [_domTreesOutlineView reloadData];
    
    // Expand all items
    id  item;
    item = [_sourceOutlineView itemAtRow:0];
    if (item) {
        [_sourceOutlineView expandItem:item expandChildren:YES];
    }
    
    item = [_domTreesOutlineView itemAtRow:0];
    if (item) {
        [_domTreesOutlineView expandItem:item expandChildren:YES];
    }
    
    // Select first one
    [_sourceOutlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
    [_domTreesOutlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
}

- (void)_setOtherPageInfoWithPageController:(SRPageController*)pageController
{
    // Remove old other files
    [_otherArrayController removeObjects:[_otherArrayController arrangedObjects]];
    
    // Get web view
    WebView*    webView;
    webView = [pageController webView];
    
    // Get subresources
    NSArray*        subresources;
    NSEnumerator*   enumerator;
    WebResource*    resource;
    subresources = [[webView mainFrame] subresources];
    enumerator = [subresources objectEnumerator];
    while (resource = [enumerator nextObject]) {
        // Check MIME type
        NSString*   mimeType;
        mimeType = [resource MIMEType];
        if ([mimeType isEqualToString:@"text/html"] || 
            [mimeType isEqualToString:@"text/css"] || 
            [mimeType isEqualToString:@"text/javascript"] || 
            [mimeType isEqualToString:@"application/x-javascript"] || 
            [mimeType hasPrefix:@"image/"])
        {
            continue;
        }
        
        // Get resource info
        NSString*   URLString;
        URLString = [[resource URL] absoluteString];
        if (!URLString) {
            URLString = @"(No URL)";
        }
        
        NSData* data;
        data = [resource data];
        
        // Add other info
        NSMutableDictionary*    otherInfo;
        otherInfo = [NSMutableDictionary dictionary];
        [otherInfo setObject:URLString forKey:@"file"];
        [otherInfo setObject:mimeType forKey:@"mimeType"];
        [otherInfo setObject:data forKey:@"data"];
        
        if ([mimeType hasPrefix:@"text/"] || 
            [mimeType isEqualToString:@"application/xml"] || 
            [mimeType isEqualToString:@"application/xhtml+xml"] || 
            [mimeType isEqualToString:@"application/rss+xml"] || 
            [mimeType isEqualToString:@"application/rdf+xml"] || 
            [mimeType isEqualToString:@"application/atom+xml"])
        {
            // Get text
            NSString*           textEncodingName;
            NSStringEncoding    encoding = NSASCIIStringEncoding;
            textEncodingName = [resource textEncodingName];
            if (textEncodingName) {
                CFStringEncoding    cfEncoding;
                cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)textEncodingName);
                encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
            }
            
            NSString*   text;
            text = [[NSString alloc] initWithData:data encoding:encoding];
            [text autorelease];
            
            if (text) {
                [otherInfo setObject:text forKey:@"text"];
            }
        }
        
        [_otherArrayController addObject:otherInfo];
    }
    [_otherArrayController setSelectionIndex:0];
}

- (void)setPageController:(SRPageController*)pageController
{
    _pageController = pageController;
    
    // Set window title
    NSString*   title;
    title = [[[[pageController webView] mainFrame] dataSource] pageTitle];
    if (title) {
        title = [NSString stringWithFormat:NSLocalizedString(@"Page Info of '%@'", nil), title];
        [[self window] setTitle:title];
    }
    
    // Set page info
    [self _setGeneralPageInfoWithPageController:pageController];
    [self _setLinksPageInfoWithPageController:pageController];
    [self _setCookiesPageInfoWithPageController:pageController];
    [self _setCSSPageInfoWithPageController:pageController];
    [self _setJavaScriptPageInfoWithPageController:pageController];
    [self _setImagesPageInfoWithPageController:pageController];
    [self _setSourcePageInfoWithPageController:pageController];
    [self _setOtherPageInfoWithPageController:pageController];
    
    // Select general
    [_profileTableView selectRow:0 byExtendingSelection:NO];
    
    // Set observer
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(pageProgressFinish:) 
            name:SRPageProgressFinish object:pageController];
}

//--------------------------------------------------------------//
#pragma mark -- Appearance --
//--------------------------------------------------------------//

- (void)_updateLinksView
{
    // Get selected objects
    NSArray*        selectedObjects;
    NSDictionary*   selectedObject = nil;
    selectedObjects = [_linksArrayController selectedObjects];
    if ([selectedObjects count] > 0) {
        selectedObject = [selectedObjects objectAtIndex:0];
    }
    if (!selectedObject) {
        return;
    }
    
    // Decide lower view
    NSView* view = nil;
    if ([selectedObject objectForKey:@"imageURL"]) {
        view = _linksImageView;
    }
    else {
        view = _linksTextView;
    }
    
    // Set lower view
    NSView* lowerView = nil;
    if ([[_linksLowerView subviews] count] > 0) {
        lowerView = [[_linksLowerView subviews] objectAtIndex:0];
    }
    if (lowerView != view) {
        [lowerView removeFromSuperview];
        
        NSRect  frame;
        frame = [_linksLowerView bounds];
        [view setFrame:frame];
        [_linksLowerView addSubview:view];
    }
    
    // Update lower scroll view
    [self linksScrollViewBondsChanged:nil];
    
    // Get anchor
    DOMHTMLAnchorElement*   anchor;
    anchor = [selectedObject objectForKey:@"domNode"];
    if (!anchor) {
        return;
    }
    
    // Notify link selection
    [[NSNotificationCenter defaultCenter] postNotificationName:SRPageInfoLinkSelectedNotification 
            object:self 
            userInfo:[NSDictionary dictionaryWithObject:anchor forKey:@"domNode"]];
}

- (void)_updateLinksPredicate
{
    NSPredicate*    predicate = nil;
    
    // Get search string
    NSString*   searchString;
    searchString = [_linksSearchField stringValue];
    if ([searchString length] > 0) {
        // Create predicate
        switch (_linksSearchType) {
        case SRPageInfoSearchLinkAll: {
            predicate = [NSPredicate predicateWithFormat:@"(href CONTAINS[cd] %@) OR (text CONTAINS[cd] %@)", 
                    searchString, searchString];
            break;
        }
        case SRPageInfoSerachLinkURL: {
            predicate = [NSPredicate predicateWithFormat:@"href CONTAINS[cd] %@", 
                    searchString];
            break;
        }
        case SRPageInfoSearchLinkText: {
            predicate = [NSPredicate predicateWithFormat:@"text CONTAINS[cd] %@", 
                    searchString];
            break;
        }
        }
    }
    
    // Set predicate
    [_linksArrayController setFilterPredicate:predicate];
    [_linksTableView reloadData];
}

- (void)_updateLinksSearchMenu
{
    // Set menu item state
    int i;
    for (i = 0; i < [_linksSearchMenu numberOfItems]; i++) {
        NSMenuItem* menuItem;
        menuItem = [_linksSearchMenu itemAtIndex:i];
        [menuItem setState:[menuItem tag] == _linksSearchType ? NSOnState : NSOffState];
    }
    
    // Set search menu
    [[_linksSearchField cell] setSearchMenuTemplate:_linksSearchMenu];
}

- (void)_updateImagesMatrix
{
    // Get content size
    NSSize  contentSize;
    contentSize = [_imagesMatrixScrollView contentSize];
    
    // Decide number of columns and rows
    int numberOfColumns, numberOfRows;
    numberOfColumns = contentSize.width / _imageCellSizeWidth;
    if (numberOfColumns == 0) {
        numberOfColumns = 1;
    }
    numberOfRows = ([_images count] / numberOfColumns) + (([_images count] % numberOfColumns) ? 1 : 0);
    
    if (numberOfRows == [_imagesMatrix numberOfRows] && 
        numberOfColumns == [_imagesMatrix numberOfColumns])
    {
        return;
    }
    
    // Set cell size
    int interCellSpace;
    interCellSpace = [_imagesMatrix intercellSpacing].width * (numberOfColumns - 1);
    [_imagesMatrix setCellSize:NSMakeSize(
            (contentSize.width - interCellSpace) / numberOfColumns, 
            _imageCellSizeHeight)];
    
    // Keep selected cell
    NSArray*    cells;
    cells = [_imagesMatrix selectedCells];
    
    // Renew matrix rows and columns
    [_imagesMatrix renewRows:numberOfRows columns:numberOfColumns];
    
    // Select cells again
    NSEnumerator*   enumerator;
    NSCell*         cell;
    enumerator = [cells objectEnumerator];
    while (cell = [enumerator nextObject]) {
        int row, col;
        if ([_imagesMatrix getRow:&row column:&col ofCell:cell]) {
            // Select cell
            [_imagesMatrix selectCellAtRow:row column:col];
        }
    }
    
    // Set images to cells
    int row, col, index = 0;
    for (row = 0; row < numberOfRows; row++) {
        for (col = 0; col < numberOfColumns; col++) {
            // Get cell
            NSImageCell*    cell;
            cell = [_imagesMatrix cellAtRow:row column:col];
            if (!cell) {
                continue;
            }
            
            // Check index
            if (index >= [_images count]) {
                [cell setImage:nil];
                continue;
            }
            
            // Get image info
            NSDictionary*   imageInfo;
            imageInfo = [_images objectAtIndex:index++];
            
            // Get data
            NSData* data;
            data = [imageInfo objectForKey:@"data"];
            if (!data) {
                [cell setImage:nil];
                continue;
            }
            
            // Create image
            NSImage*    image;
            image = [[NSImage alloc] initWithData:data];
            if (!image) {
                [cell setImage:nil];
                continue;
            }
            
            // Set image
            [cell setImage:image];
            [image release];
        }
    }
    
    // Make matrix size to fit
    [_imagesMatrix sizeToCells];
    [_imagesMatrix setNeedsDisplay:YES];
}

- (void)_updateImagesView
{
    // Set lower view
    NSView* view = _imagesImageView;
    NSView* lowerView = nil;
    if ([[_imagesLowerView subviews] count] > 0) {
        lowerView = [[_imagesLowerView subviews] objectAtIndex:0];
    }
    if (lowerView != view) {
        [lowerView removeFromSuperview];
        
        NSRect  frame;
        frame = [_imagesLowerView bounds];
        [view setFrame:frame];
        [_imagesLowerView addSubview:view];
    }
    
    // Update lower scroll view
    [self imagesScrollViewBondsChanged:nil];
}

- (void)_updateSourceViewBackground
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Get background color
    static NSColor* _backgroundColor = nil;
    if (!_backgroundColor) {
        _backgroundColor = [[NSColor whiteColor] retain];
    }
    
    NSData*     data;
    NSColor*    backgroundColor = nil;
    data = [defaults objectForKey:SRSourceBackgroundColor];
    if (data) {
        backgroundColor = [NSUnarchiver unarchiveObjectWithData:data];
    }
    if (!backgroundColor) {
        backgroundColor = _backgroundColor;
    }
    
    // Set background color
    [_cssTextView setBackgroundColor:backgroundColor];
    [_javaScriptTextView setBackgroundColor:backgroundColor];
    [_sourceTextView setBackgroundColor:backgroundColor];
}

- (void)_updateOtherView
{
    // Get selected objects
    NSArray*        selectedObjects;
    NSDictionary*   selectedObject = nil;
    selectedObjects = [_otherArrayController selectedObjects];
    if ([selectedObjects count] > 0) {
        selectedObject = [selectedObjects objectAtIndex:0];
    }
    if (!selectedObject) {
        return;
    }
    
    // Decide lower view
    NSView*     view = nil;
    NSString*   mimeType;
    mimeType = [selectedObject objectForKey:@"mimeType"];
    if ([selectedObject objectForKey:@"text"]) {
        view = _otherTextView;
    }
    if ([mimeType isEqualToString:@"application/x-shockwave-flash"]) {
        view = _otherWebView;
    }
    
    // Set lower view
    NSView* lowerView = nil;
    if ([[_otherLowerView subviews] count] > 0) {
        lowerView = [[_otherLowerView subviews] objectAtIndex:0];
    }
    if (lowerView != view) {
        [lowerView removeFromSuperview];
        
        NSRect  frame;
        frame = [_otherLowerView bounds];
        [view setFrame:frame];
        [_otherLowerView addSubview:view];
    }
    
    // Update lower scroll view
    [self otherScrollViewBondsChanged:nil];
    
    // For web view
    if (view == _otherWebView) {
        // Set URL
        NSURL*  url;
        url = [NSURL URLWithString:[selectedObject objectForKey:@"file"]];
        if (url) {
            NSURLRequest*   request;
            request = [NSURLRequest requestWithURL:url];
            [[_otherWebWebView mainFrame] loadRequest:request];
        }
    }
}

- (void)_updateInfoView
{
    // Get selected profile
    NSString*   title;
    title = [self profileTitle];
    if (!title) {
        return;
    }
    
    NSView* infoView = nil;
    
    // For general
    if ([title isEqualToString:NSLocalizedString(@"General Info", nil)]) {
        infoView = _generalView;
    }
    // For links
    else if ([title isEqualToString:NSLocalizedString(@"Links", nil)]) {
        infoView = _linksView;
    }
    // For DOM trees
    else if ([title isEqualToString:NSLocalizedString(@"DOM", nil)]) {
        infoView = _domTreesView;
    }
    // For cookies
    else if ([title isEqualToString:NSLocalizedString(@"Cookies", nil)]) {
        infoView = _cookiesView;
    }
    // For CSS
    else if ([title isEqualToString:NSLocalizedString(@"CSS", nil)]) {
        infoView = _cssView;
    }
    // For JavaScript
    else if ([title isEqualToString:NSLocalizedString(@"JavaScript", nil)]) {
        infoView = _javaScriptView;
    }
    // For images
    else if ([title isEqualToString:NSLocalizedString(@"Images", nil)]) {
        infoView = _imagesView;
    }
    // For source
    else if ([title isEqualToString:NSLocalizedString(@"HTML", nil)]) {
        infoView = _sourceView;
    }
    // For other
    else if ([title isEqualToString:NSLocalizedString(@"Others", nil)]) {
        infoView = _otherView;
    }
    
    // Set info view
    if (!infoView) {
        return;
    }
    if (![[_infoView subviews] containsObject:infoView]) {
        [[_infoView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
        
        [infoView setFrame:[_infoView bounds]];
        [_infoView addSubview:infoView];
    }
}

//--------------------------------------------------------------//
#pragma mark -- NSView notification --
//--------------------------------------------------------------//

- (void)linksScrollViewBondsChanged:(NSNotification*)notification
{
    // Decode content size
    NSSize  contentSize;
    contentSize = [_linksScrollView contentSize];
    if ([[_linksLowerView subviews] containsObject:_linksTextView]) {
        if (contentSize.height < _linksTextViewMinHeight) {
            contentSize.height = _linksTextViewMinHeight;
        }
    }
    if ([[_linksLowerView subviews] containsObject:_linksImageView]) {
        if (contentSize.height < _linksImageViewMinHeight) {
            contentSize.height = _linksImageViewMinHeight;
        }
    }
    
    // Set frame
    NSRect  rect;
    rect = [_linksLowerView frame];
    if (!NSEqualSizes(rect.size, contentSize)) {
        rect.size = contentSize;
        [_linksLowerView setFrame:rect];
    }
}

- (void)imagesScrollViewBondsChanged:(NSNotification*)notification
{
    // Decode content size
    NSSize  contentSize;
    contentSize = [_imagesImageScrollView contentSize];
    if (contentSize.height < _imagesImageViewMinHeight) {
        contentSize.height = _imagesImageViewMinHeight;
    }
    
    // Set frame
    NSRect  rect;
    rect = [_imagesLowerView frame];
    if (!NSEqualSizes(rect.size, contentSize)) {
        rect.size = contentSize;
        [_imagesLowerView setFrame:rect];
    }
}

- (void)matrixFrameDidChange:(NSNotification*)notification
{
    // Update images matrix
    [self _updateImagesMatrix];
}

- (void)otherScrollViewBondsChanged:(NSNotification*)notification
{
    // Decode content size
    NSSize  contentSize;
    contentSize = [_otherScrollView contentSize];
    if ([[_otherLowerView subviews] containsObject:_otherTextView]) {
        if (contentSize.height < _otherTextViewMinHeight) {
            contentSize.height = _otherTextViewMinHeight;
        }
    }
    if ([[_otherLowerView subviews] containsObject:_otherWebView]) {
        if (contentSize.height < _otherWebViewMinHeight) {
            contentSize.height = _otherWebViewMinHeight;
        }
    }
    
    // Set frame
    NSRect  rect;
    rect = [_otherLowerView frame];
    if (!NSEqualSizes(rect.size, contentSize)) {
        rect.size = contentSize;
        [_otherLowerView setFrame:rect];
    }
}


//--------------------------------------------------------------//
#pragma mark -- NSTableView data source --
//--------------------------------------------------------------//

- (int)numberOfRowsInTableView:(NSTableView*)tableView
{
    // For profile
    if (tableView == _profileTableView) {
        return [_profiles count];
    }
    
    return 0;
}

- (id)tableView:(NSTableView*)tableView 
        objectValueForTableColumn:(NSTableColumn*)tableColumn 
        row:(int)rowIndex
{
    // For profile
    if (tableView == _profileTableView) {
        // Get profile at row
        NSDictionary*   profile;
        profile = [_profiles objectAtIndex:rowIndex];
        
        // For category
        if ([[profile objectForKey:@"type"] isEqualToString:@"category"]) {
            NSAttributedString* attrTitle;
            attrTitle = [profile objectForKey:@"attributedTitle"];
            if (attrTitle) {
                return attrTitle;
            }
        }
        
        return [profile objectForKey:@"title"];
    }
    
    return nil;
}

- (NSString*)_promisedFilePathWithFileURLString:(NSString*)urlString 
        title:(NSString*)title 
        defaultName:(NSString*)defaultName 
        preferedExtenstion:(NSString*)preferedExtenstion 
        dropDestination:(NSURL*)dropDestination
{
    // Check default name
    if (!defaultName || [defaultName length] == 0) {
        defaultName = NSLocalizedString(@"Untitled", nil);
    }
    
    // Decide file name
    NSString*   fileName;
    fileName = [[[NSURL URLWithString:urlString] path] lastPathComponent];
    if ([[fileName pathExtension] length] == 0) {
        NSString*   name;
        if ([title length] > 0) {
            name = title;
        }
        else {
            name = defaultName;
        }
        
        if (preferedExtenstion) {
            fileName = [NSString stringWithFormat:@"%@.%@", name, preferedExtenstion];
        }
        else {
            fileName = [NSString stringWithFormat:@"%@", name];
        }
    }
    
    // Decide file path
    NSString*   fileExtension;
    NSString*   filePath;
    fileExtension = [fileName pathExtension];
    if (!fileExtension || [fileExtension length] == 0) {
        fileExtension = preferedExtenstion;
    }
    
    filePath = HMMakeFilePathUnique(
            [dropDestination path], [fileName stringByDeletingPathExtension], fileExtension);
    
    return filePath;
}

- (NSArray*)_cssTableView:(NSTableView*)tableView 
        namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination 
        forDraggedRowsWithIndexes:(NSIndexSet*)indexSet
{
    NSMutableArray* fileNames;
    fileNames = [NSMutableArray array];
    
    unsigned    index;
    index = [indexSet firstIndex];
    do {
        // Get css info
        NSDictionary*   cssInfo = nil;
        if (index < [[_cssArrayController arrangedObjects] count]) {
            cssInfo = [[_cssArrayController arrangedObjects] objectAtIndex:index];
        }
        if (!cssInfo) {
            continue;
        }
        
        // Get data
        NSData* data;
        data = [cssInfo objectForKey:@"data"];
        if (!data) {
            continue;
        }
        
        // Decide file path
        NSString*   filePath;
        filePath = [self _promisedFilePathWithFileURLString:[cssInfo objectForKey:@"file"] 
                title:nil defaultName:@"styleSheet" preferedExtenstion:@"css" dropDestination:dropDestination];
        
        // Save data
        [data writeToFile:filePath atomically:YES];
        
        // Add file name
        [fileNames addObject:[filePath lastPathComponent]];
    } while ((index = [indexSet indexGreaterThanIndex:index]) != NSNotFound);
    
    return fileNames;
}

- (NSArray*)_javaScriptTableView:(NSTableView*)tableView 
        namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination 
        forDraggedRowsWithIndexes:(NSIndexSet*)indexSet
{
    NSMutableArray* fileNames;
    fileNames = [NSMutableArray array];
    
    unsigned    index;
    index = [indexSet firstIndex];
    do {
        // Get css info
        NSDictionary*   jsInfo = nil;
        if (index < [[_javaScriptArrayController arrangedObjects] count]) {
            jsInfo = [[_javaScriptArrayController arrangedObjects] objectAtIndex:index];
        }
        if (!jsInfo) {
            continue;
        }
        
        // Get data
        NSData* data;
        data = [jsInfo objectForKey:@"data"];
        if (!data) {
            continue;
        }
        
        // Decide file path
        NSString*   filePath;
        filePath = [self _promisedFilePathWithFileURLString:[jsInfo objectForKey:@"file"] 
                title:nil defaultName:@"javaScript" preferedExtenstion:@"js" dropDestination:dropDestination];
        
        // Save data
        [data writeToFile:filePath atomically:YES];
        
        // Add file name
        [fileNames addObject:[filePath lastPathComponent]];
    } while ((index = [indexSet indexGreaterThanIndex:index]) != NSNotFound);
    
    return fileNames;
}

- (NSArray*)_otherTableView:(NSTableView*)tableView 
        namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination 
        forDraggedRowsWithIndexes:(NSIndexSet*)indexSet
{
    NSMutableArray* fileNames;
    fileNames = [NSMutableArray array];
    
    unsigned    index;
    index = [indexSet firstIndex];
    do {
        // Get css info
        NSDictionary*   otherInfo = nil;
        if (index < [[_otherArrayController arrangedObjects] count]) {
            otherInfo = [[_otherArrayController arrangedObjects] objectAtIndex:index];
        }
        if (!otherInfo) {
            continue;
        }
        
        // Get data
        NSData* data;
        data = [otherInfo objectForKey:@"data"];
        if (!data) {
            continue;
        }
        
        // Decide file path
        NSString*   filePath;
        filePath = [self _promisedFilePathWithFileURLString:[otherInfo objectForKey:@"file"] 
                title:nil defaultName:nil preferedExtenstion:nil dropDestination:dropDestination];
        
        // Save data
        [data writeToFile:filePath atomically:YES];
        
        // Add file name
        [fileNames addObject:[filePath lastPathComponent]];
    } while ((index = [indexSet indexGreaterThanIndex:index]) != NSNotFound);
    
    return fileNames;
}

- (NSArray*)tableView:(NSTableView*)tableView 
        namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination 
        forDraggedRowsWithIndexes:(NSIndexSet*)indexSet
{
    // For CSS
    if (tableView == _cssTableView) {
        return [self _cssTableView:tableView 
                namesOfPromisedFilesDroppedAtDestination:dropDestination 
                forDraggedRowsWithIndexes:indexSet];
    }
    // For JavaScirpt
    if (tableView == _javaScriptTableView) {
        return [self _javaScriptTableView:tableView 
                namesOfPromisedFilesDroppedAtDestination:dropDestination 
                forDraggedRowsWithIndexes:indexSet];
    }
    // For other
    if (tableView == _otherTableView) {
        return [self _otherTableView:tableView 
                namesOfPromisedFilesDroppedAtDestination:dropDestination 
                forDraggedRowsWithIndexes:indexSet];
    }
    
    return nil;
}

- (BOOL)tableView:(NSTableView*)tableView 
        writeRowsWithIndexes:(NSIndexSet*)rowIndexes 
        toPasteboard:(NSPasteboard*)pboard
{
    // For CSS
    if (tableView == _cssTableView) {
        [pboard declareTypes:[NSArray arrayWithObjects:NSFilesPromisePboardType, nil] owner:self];
        [pboard setPropertyList:[NSArray arrayWithObject:@"css"] forType:NSFilesPromisePboardType];
    }
    // For JavaScirpt
    if (tableView == _javaScriptTableView) {
        [pboard declareTypes:[NSArray arrayWithObjects:NSFilesPromisePboardType, nil] owner:self];
        [pboard setPropertyList:[NSArray arrayWithObject:@"js"] forType:NSFilesPromisePboardType];
    }
    // For other
    if (tableView == _otherTableView) {
        [pboard declareTypes:[NSArray arrayWithObjects:NSFilesPromisePboardType, nil] owner:self];
        [pboard setPropertyList:[NSArray arrayWithObject:@"*"] forType:NSFilesPromisePboardType];
    }
    
    return YES;
}

//--------------------------------------------------------------//
#pragma mark -- NSTableView delegate --
//--------------------------------------------------------------//

- (void)tableViewSelectionDidChange:(NSNotification*)notification
{
    // Get table view
    NSTableView*    tableView;
    tableView = [notification object];
    
    // For profile
    if (tableView == _profileTableView) {
        // Update info view
        [self _updateInfoView];
    }
    // For links
    if (tableView == _linksTableView) {
        // Update links view
        [self _updateLinksView];
    }
    
    // For other
    if (tableView == _otherTableView) {
        // Update other view
        [self _updateOtherView];
    }
}

- (BOOL)tableView:(NSTableView*)tableView shouldSelectRow:(int)rowIndex
{
    // For profile
    if (tableView == _profileTableView) {
        // Get profile at row
        NSDictionary*   profile;
        profile = [_profiles objectAtIndex:rowIndex];
        
        // Check category
        if ([[profile objectForKey:@"type"] isEqualToString:@"category"]) {
            return NO;
        }
        
        return YES;
    }
    
    return YES;
}

//--------------------------------------------------------------//
#pragma mark -- NSOutlineView data source --
//--------------------------------------------------------------//

- (int)outlineView:(NSOutlineView*)outlineView 
        numberOfChildrenOfItem:(id)item
{
    // For DOM outline
    if (outlineView == _domTreesDomOutlineView) {
        // For root
        if (!item) {
            // Get selected object
            NSArray*        selectedObjects;
            NSDictionary*   sourceInfo = nil;
            selectedObjects = [_sourceTreeController selectedObjects];
            if ([selectedObjects count] > 0) {
                sourceInfo = [selectedObjects objectAtIndex:0];
            }
            if (!sourceInfo) {
                return 0;
            }
            
            // Use DOM document
            if (![sourceInfo objectForKey:@"domDocument"]) {
                return 0;
            }
            return 1;
        }
        
        // item is DOMNode
        return [[item childNodes] length];
    }
    
    // For source DOM attr
    if (outlineView == _domTreesDomAttrOutlineView) {
        // Get selected DOM node
        id  domNode;
        domNode = [_domTreesDomOutlineView itemAtRow:[_domTreesDomOutlineView selectedRow]];
        if (!domNode) {
            return 0;
        }
        
        // Get attrs
        NSArray*    attrs;
        attrs = [[_domAttrDict objectForKey:NSStringFromClass([domNode class])] objectForKey:@"attrs"];
        
        // For HTML element
        static int  _htmlAttrCount = -1;
        if (_htmlAttrCount == -1) {
            _htmlAttrCount = [[[_domAttrDict objectForKey:@"DOMHTMLElement"] objectForKey:@"attrs"] count];
        }
        if ([domNode isKindOfClass:[DOMHTMLElement class]]) {
            return [attrs count] + _htmlAttrCount;
        }
        
        return [attrs count];
    }
    
    return 0;
}

- (id)outlineView:(NSOutlineView*)outlineView 
        child:(int)index 
        ofItem:(id)item
{
    // For source DOM
    if (outlineView == _domTreesDomOutlineView) {
        // For root
        if (!item) {
            // Get selected object
            NSArray*        selectedObjects;
            NSDictionary*   sourceInfo = nil;
            selectedObjects = [_sourceTreeController selectedObjects];
            if ([selectedObjects count] > 0) {
                sourceInfo = [selectedObjects objectAtIndex:0];
            }
            if (!sourceInfo) {
                return nil;
            }
            
            // Get DOM document
            return [sourceInfo objectForKey:@"domDocument"];
        }
        
        // item is DOMNode
        id  childNode;
        childNode = [[item childNodes] item:index];
        
        // Retain child node
        if (![_DOMNodes containsObject:childNode]) {
            [_DOMNodes addObject:childNode];
        }
        return childNode;
    }
    
    // For source DOM attr
    if (outlineView == _domTreesDomAttrOutlineView) {
        // Get selected DOM node
        id  domNode;
        domNode = [_domTreesDomOutlineView itemAtRow:[_domTreesDomOutlineView selectedRow]];
        if (!domNode) {
            return 0;
        }
        
        // Get attrs
        NSArray*    attrs;
        attrs = [[_domAttrDict objectForKey:NSStringFromClass([domNode class])] objectForKey:@"attrs"];
        
        // For HTML element
        static NSArray* _htmlAttrs = nil;
        if (!_htmlAttrs) {
            _htmlAttrs = [[_domAttrDict objectForKey:@"DOMHTMLElement"] objectForKey:@"attrs"];
        }
        if ([domNode isKindOfClass:[DOMHTMLElement class]]) {
            if (index < [attrs count]) {
                return [attrs objectAtIndex:index];
            }
            
            return [_htmlAttrs objectAtIndex:index - [attrs count]];
        }
        
        return [attrs objectAtIndex:index];
    }
    
    return nil;
}

- (BOOL)outlineView:(NSOutlineView*)outlineView 
        isItemExpandable:(id)item
{
    // For source DOM
    if (outlineView == _domTreesDomOutlineView) {
        // item is DOMNode
        return [item hasChildNodes];
    }
    
    // For source DOM attr
    if (outlineView == _domTreesDomAttrOutlineView) {
        return NO;
    }
    
    return NO;
}

- (id)outlineView:(NSOutlineView*)outlineView 
        objectValueForTableColumn:(NSTableColumn*)tableColumn 
        byItem:(id)item
{
    id  identifier;
    identifier = [tableColumn identifier];
    
    // For source DOM
    if (outlineView == _domTreesDomOutlineView) {
        // For name
        if ([identifier isEqualToString:@"name"]) {
            return [item nodeName];
        }
        // For value
        if ([identifier isEqualToString:@"value"]) {
            return [item nodeValue];
        }
    }
    
    // For source DOM attr
    if (outlineView == _domTreesDomAttrOutlineView) {
        // Get selected DOM node
        id  domNode;
        domNode = [_domTreesDomOutlineView itemAtRow:[_domTreesDomOutlineView selectedRow]];
        if (!domNode) {
            return nil;
        }
        
        // For attr name
        if ([identifier isEqualToString:@"attrName"]) {
            return [item objectForKey:@"name"];
        }
        // For attr value
        if ([identifier isEqualToString:@"attrValue"]) {
            // Get class
            NSString*   className;
            className = [item objectForKey:@"class"];
            
            // For NSString
            if ([className isEqualToString:@"NSString"]) {
                id  value;
                value = [domNode valueForKey:[item objectForKey:@"name"]];
                if (![value isKindOfClass:[NSString class]]) {
                    return nil;
                }
                return value;
            }
            // For DOMHTMLCollection
            if ([className isEqualToString:@"DOMHTMLCollection"]) {
            }
        }
    }
    
    return nil;
}

- (NSArray*)_sourceOutlineView:(NSOutlineView*)outlineView 
        namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination 
        forDraggedItems:(NSArray*)items
{
    NSMutableArray* fileNames;
    fileNames = [NSMutableArray array];
    
    NSEnumerator*   enumerator;
    id              item;
    enumerator = [items objectEnumerator];
    while (item = [enumerator nextObject]) {
        // Get source info
        NSDictionary*   sourceInfo = nil;
        if ([item respondsToSelector:@selector(observedObject)]) {
            sourceInfo = [item observedObject];
        }
        if (!sourceInfo) {
            continue;
        }
        
        // Get data
        NSData* data;
        data = [sourceInfo objectForKey:@"data"];
        if (!data) {
            continue;
        }
        
        // Decide file path
        NSString*   filePath;
        filePath = [self _promisedFilePathWithFileURLString:[sourceInfo objectForKey:@"file"] 
                title:[sourceInfo objectForKey:@"title"] 
                defaultName:@"source" 
                preferedExtenstion:@"html" 
                dropDestination:dropDestination];
        
        // Save data
        [data writeToFile:filePath atomically:YES];
        
        // Add file name
        [fileNames addObject:[filePath lastPathComponent]];
    }
    
    return fileNames;
}

- (NSArray*)outlineView:(NSOutlineView*)outlineView 
        namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination 
        forDraggedItems:(NSArray*)items
{
    // For source
    if (outlineView == _sourceOutlineView) {
        return [self _sourceOutlineView:outlineView 
                namesOfPromisedFilesDroppedAtDestination:dropDestination 
                forDraggedItems:items];
    }
    
    return nil;
}

- (BOOL)outlineView:(NSOutlineView*)outlineView 
        writeItems:(NSArray*)items 
        toPasteboard:(NSPasteboard*)pboard
{
    // For source
    if (outlineView == _sourceOutlineView) {
        [pboard declareTypes:[NSArray arrayWithObjects:NSFilesPromisePboardType, nil] owner:self];
        [pboard setPropertyList:[NSArray arrayWithObject:@"html"] forType:NSFilesPromisePboardType];
    }
    
    return YES;
}

//--------------------------------------------------------------//
#pragma mark -- NSOutlineView delegate --
//--------------------------------------------------------------//

- (BOOL)outlineView:(NSOutlineView*)outlineView shouldSelectItem:(id)item
{
    // For general
    if (outlineView == _httpHeaderOutlineView) {
        // Can't select first level item
        if ([outlineView levelForItem:item] == 0) {
            return NO;
        }
        
        return YES;
    }
    
    return YES;
}

- (void)outlineViewSelectionDidChange:(NSNotification*)notification
{
    // For source
    if ([notification object] == _domTreesOutlineView) {
        // Reload dta
        [_DOMNodes removeAllObjects];
        [_domTreesDomOutlineView reloadData];
    }
    
    // For source DOM
    if ([notification object] == _domTreesDomOutlineView) {
        // Reload dta
        [_domTreesDomAttrOutlineView reloadData];
    }
}

//--------------------------------------------------------------//
#pragma mark -- HMTableView delegate --
//--------------------------------------------------------------//

- (BOOL)tableView:(NSTableView*)tableView 
        frameOfCellAtColumn:(int)column 
        row:(int)row 
        proposedFrame:(NSRect*)proposedFrame
{
    // For profile
    if (tableView == _profileTableView) {
        // Get profile at row
        NSDictionary*   profile;
        profile = [_profiles objectAtIndex:row];
        
        // Check category
        if ([[profile objectForKey:@"type"] isEqualToString:@"category"]) {
            return NO;
        }
        
        // Shrink frame
        proposedFrame->origin.x += 11.0f;
        proposedFrame->size.width -= 11.0f;
        
        return YES;
    }
    
    return NO;
}

//--------------------------------------------------------------//
#pragma mark -- Action --
//--------------------------------------------------------------//

- (void)setLinksSearchTextAction:(id)sender
{
    [self _updateLinksPredicate];
}

- (void)setLinksSearchTypeAction:(id)sender
{
    _linksSearchType = [sender tag];
    
    [self _updateLinksSearchMenu];
    [self _updateLinksPredicate];
}

- (void)linksDoubleClickAction:(id)sender
{
    // Get selected objects
    NSArray*        selectedObjects;
    NSDictionary*   selectedObject = nil;
    selectedObjects = [_linksArrayController selectedObjects];
    if ([selectedObjects count] > 0) {
        selectedObject = [selectedObjects objectAtIndex:0];
    }
    if (!selectedObject) {
        return;
    }
    
    // Get anchor
    DOMHTMLAnchorElement*   anchor;
    anchor = [selectedObject objectForKey:@"domNode"];
    if (!anchor) {
        return;
    }
    
    // Create URL
    NSURL*      url = nil;
    NSString*   href;
    href = [anchor href];
    if ([href hasPrefix:@"http://"]) {
        url = [NSURL URLWithString:href];
    }
    else {
        // It might be relative URL
        NSURL*  baseURL;
        baseURL = [NSURL URLWithString:[(DOMHTMLDocument*)[anchor ownerDocument] URL]];
        if (baseURL) {
            url = [NSURL URLWithString:href relativeToURL:url];
        }
    }
    
    if (!url) {
        return;
    }
    
    // Open URL
    [[SRAppController sharedInstance] 
            openURL:url pageControllerIfAvailable:_pageController];
}

- (void)imageSelectedAction:(id)sender
{
    // Get selected image
    int row, column, index = NSNotFound;
    row = [_imagesMatrix selectedRow];
    column = [_imagesMatrix selectedColumn];
    if (row != -1 && column != -1) {
        index = row * [_imagesMatrix numberOfColumns] + column;
    }
    
    if (index == NSNotFound || index >= [_images count]) {
        [_imagesContent setValue:@"" forKey:@"url"];
        [_imagesContent setValue:@"" forKey:@"mimeType"];
        [_imagesContent setValue:nil forKey:@"data"];
        return;
    }
    
    NSMutableDictionary*    imageInfo;
    imageInfo = [_images objectAtIndex:index];
    
    // Set image info
    [_imagesContent setValue:[imageInfo objectForKey:@"urlString"] forKey:@"url"];
    [_imagesContent setValue:[imageInfo objectForKey:@"mimeType"] forKey:@"mimeType"];
    [_imagesContent setValue:[imageInfo objectForKey:@"data"] forKey:@"data"];
    
    if ([imageInfo objectForKey:@"fileSize"]) {
        [_imagesContent setValue:[imageInfo objectForKey:@"fileSize"] forKey:@"fileSize"];
    }
    
    if ([imageInfo objectForKey:@"imageSize"]) {
        [_imagesContent setValue:[imageInfo objectForKey:@"imageSize"] forKey:@"imageSize"];
    }
    else {
        // Get image size
        NSImage*    image;
        image = [[NSImage alloc] initWithData:[imageInfo objectForKey:@"data"]];
        if (image) {
            NSSize  size;
            size = [image size];
            if (!NSEqualSizes(size, NSZeroSize)) {
                NSString*   sizeString;
                sizeString = [NSString stringWithFormat:@"%d x %d", (int)size.width, (int)size.height];
                
                [_imagesContent setValue:sizeString forKey:@"imageSize"];
                [imageInfo setObject:sizeString forKey:@"imageSize"];
            }
            
            [image release];
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- NSWindow delegate --
//--------------------------------------------------------------//

- (void)windowWillClose:(NSNotification*)notification
{
    // Unregister itself
    if ([_instances containsObject:self]) {
        [_instances removeObject:self];
    }
}

//--------------------------------------------------------------//
#pragma mark -- NSSplitView delegate --
//--------------------------------------------------------------//

- (float)splitView:(NSSplitView*)splitView 
        constrainMinCoordinate:(float)proposedMin 
        ofSubviewAt:(int)offset
{
    return 100.0f;
}

- (float)splitView:(NSSplitView*)splitView 
        constrainMaxCoordinate:(float)proposedMax 
        ofSubviewAt:(int)offset
{
    return 150.0f;
}

- (void)splitViewDidResizeSubviews:(NSNotification*)notification
{
    // Set shelf table width
    if (_isInitialized) {
        [[NSUserDefaults standardUserDefaults] 
                setFloat:[_profileTableView frame].size.width forKey:SRPageInfoSideWidth];
    }
}

//--------------------------------------------------------------//
#pragma mark -- SRPageController notification --
//--------------------------------------------------------------//

- (void)pageProgressFinish:(NSNotification*)notification
{
    if ([notification object] != _pageController) {
        return;
    }
    
    // Set window title
    NSString*   title;
    title = [[[[_pageController webView] mainFrame] dataSource] pageTitle];
    if (title) {
        title = [NSString stringWithFormat:NSLocalizedString(@"Page Info of '%@'", nil), title];
        if (![title isEqualToString:[[self window] title]]) {
            [[self window] setTitle:title];
        }
    }
    
    // Set page info
    [self _setGeneralPageInfoWithPageController:_pageController];
    [self _setLinksPageInfoWithPageController:_pageController];
    [self _setCSSPageInfoWithPageController:_pageController];
    [self _setJavaScriptPageInfoWithPageController:_pageController];
    [self _setImagesPageInfoWithPageController:_pageController];
    [self _setSourcePageInfoWithPageController:_pageController];
    [self _setOtherPageInfoWithPageController:_pageController];
}

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

- (void)_updateSourceWithFont:(NSFont*)font objects:(NSArray*)objects
{
    NSEnumerator*           enumerator;
    NSMutableDictionary*    object;
    enumerator = [objects objectEnumerator];
    while (object = [enumerator nextObject]) {
        // Get source
        NSMutableAttributedString*  source;
        source = [object objectForKey:@"source"];
        if (!source) {
            continue;
        }
        
        // Set font
        [source addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [source length])];
    }
}

- (void)_updateSourceWithFont:(NSFont*)font objects:(id)objects childrenKeyPath:(NSString*)childrenKey
{
    [self _updateSourceWithFont:font objects:objects];
}

- (void)observeValueForKeyPath:(NSString*)keyPath 
        ofObject:(id)object 
        change:(NSDictionary*)change 
        context:(void *)context
{
    // For user defaults
    if (object == [NSUserDefaults standardUserDefaults]) {
        // Source pref
        if ([keyPath isEqualToString:SRSourceFontSize]) {
            // Update font
            NSString*   fontName;
            int         fontSize;
            NSFont*     font;
            fontName = [object stringForKey:SRSourceFontName];
            fontSize = [object integerForKey:SRSourceFontSize];
            font = [NSFont fontWithName:fontName size:fontSize];
            
            [self _updateSourceWithFont:font objects:[_sourceTreeController content] 
                    childrenKeyPath:[_sourceTreeController childrenKeyPath]];
            [self _updateSourceWithFont:font objects:[_cssArrayController content]];
            [self _updateSourceWithFont:font objects:[_javaScriptArrayController content]];
        }
        if ([keyPath isEqualToString:SRSourceBackgroundColor]) {
            [self _updateSourceViewBackground];
        }
    }
}

@end
