/*
SRSBSRRController.m

Author: Makoto Kinoshita

Copyright 2004 The Shiira Project. All rights reserved.

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

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

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

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

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

#import "SRAppDelegate.h"
#import "SRMainWindowController.h"
#import "SRSourceWindowController.h"
#import "SRSideBarController.h"
#import "SRBookmark.h"
#import "SRBookmarkStorage.h"
#import "SRRSSManager.h"

#import "SRSBBookmarksController.h"
#import "SRSBRSSController.h"

#import "SRImageTextCell.h"

#import "SRSDSyndication.h"

#import "FoundationEx.h"
#import "AppKitEx.h"
#import "WebKitEx.h"

#import "SRSDRSSDocument.h"

static NSString*    _SRRSSTabIdentifier = @"RSS";
static NSString*    _SRFindResultsTabIdentifier = @"findResult";

static NSFont*      _smallSystemFont = nil;
static NSFont*      _smallBoldSystemFont = nil;

NSString* SRSerializePlistForURLFormat(id plist)
{
    NSData*     data;
    NSString*   error;
    data = [NSPropertyListSerialization dataFromPropertyList:plist 
            format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
    if (!data) {
        NSLog(error);
        return nil;
    }
    
    NSString*   string;
    string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    [string autorelease];
    return [string stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}

id SRDeserializePlistFromURLFormat(NSString* string)
{
    NSData*     data;
    id          plist;
    NSString*   error;
    string = [string stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    data = [string dataUsingEncoding:NSUTF8StringEncoding];
    plist = [NSPropertyListSerialization propertyListFromData:data 
            mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:&error];
    if (!plist) {
        NSLog(error);
        return nil;
    }
    
    return plist;
}

@interface SRSBRSSController (private)
- (void)_updateHistoryKey;
@end

@implementation SRSBRSSController

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

- (id)initWithMainWindowController:(SRMainWindowController*)mainWindowController 
        sideBarController:(SRSideBarController*)sideBarController
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // Initialize instance variables
    _mainWindowController = mainWindowController;
    _sideBarController = sideBarController;
    _findResults = nil;
    _channels = nil;
    _items = nil;
    _history = [[NSMutableArray array] retain];
    _currentURLString = nil;
    _style = SRRSSWebSiteStyle;
    
    // Get fonts
    if (!_smallSystemFont) {
        _smallSystemFont = [[NSFont systemFontOfSize:[NSFont smallSystemFontSize]] retain];
        _smallBoldSystemFont = [[NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]] retain];
    }
    
    // Register notifications
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self 
            selector:@selector(RSSWillStartRefresh:) name:SRRSSWillStartRefresh object:nil];
    [center addObserver:self 
            selector:@selector(RSSProgressRefresh:) name:SRRSSProgressRefresh object:nil];
    [center addObserver:self 
            selector:@selector(RSSDidEndRefresh:) name:SRRSSDidEndRefresh object:nil];
    [center addObserver:self 
            selector:@selector(RSSItemsRemovedAll:) name:SRRSSItemsRemovedAll object:nil];
    [center addObserver:self 
            selector:@selector(RSSItemsPreviewed:) name:SRRSSItemsPreviewed object:nil];
    [center addObserver:self 
            selector:@selector(RSSFeedsDidChanged:) name:SRRSSFeedsDidChanged object:nil];
    
    [center addObserver:self 
            selector:@selector(faviconAdded:) name:WebIconDatabaseDidAddIconNotification object:nil];
    
    return self;
}

- (void)awakeFromNib
{
    NSTableColumn*      column;
    NSCell*             oldCell;
    SRImageTextCell*    cell;
    
    // Configure RSS outline
    column = [_RSSOutline tableColumnWithIdentifier:@"title"];
    oldCell = [column dataCell];
    cell = [[SRImageTextCell alloc] init];
    [cell autorelease];
    [cell setFont:[oldCell font]];
    [column setDataCell:cell];
    
    [_RSSOutline setTarget:self];
    [_RSSOutline setAction:@selector(RSSSelectedAction:)];
    [_RSSOutline setDoubleAction:@selector(openRSSAction:)];
    [_RSSOutline setAutoresizesOutlineColumn:NO];
    [_RSSOutline setIndentationPerLevel:0.0f];
    
    column = [_RSSOutline tableColumnWithIdentifier:@"date"];
    if (column) {
        [_RSSOutline setSortDescriptors:[NSArray arrayWithObject:[column sortDescriptorPrototype]]];
    }
    
    // Configure RSS find table
    column = [_findTable tableColumnWithIdentifier:@"results"];
    oldCell = [column dataCell];
    cell = [[SRImageTextCell alloc] init];
    [cell autorelease];
    [cell setFont:[oldCell font]];
    [column setDataCell:cell];
    
    [_findTable setTarget:self];
    [_findTable setAction:@selector(RSSSelectedAction:)];
    [_findTable setDoubleAction:@selector(openRSSAction:)];
    
    column = [_findTable tableColumnWithIdentifier:@"date"];
    if (column) {
        [_findTable setSortDescriptors:[NSArray arrayWithObject:[column sortDescriptorPrototype]]];
    }
    
    // Configure web view
    // Dummy
    WebPreferences* pref;
    int             defaultFontSize;
    pref = [_channelWebView preferences];
    defaultFontSize = [pref defaultFontSize];
    [pref setDefaultFontSize:defaultFontSize + 1];
    [pref setDefaultFontSize:defaultFontSize];
    [pref setDefaultTextEncodingName:@"UTF-8"];
    
    // Select tab item
    [_RSSTab selectTabViewItemWithIdentifier:_SRRSSTabIdentifier];
    
    // Set channel view as default
    [self setDetailView:SRRSSChannelDetail];
    
    // Update history key
    [self _updateHistoryKey];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [_findResults release];
    [_channels release];
    [_items release];
    [_history release];
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Controller --
//--------------------------------------------------------------//

- (SRMainWindowController*)mainWindowController
{
    return _mainWindowController;
}

- (void)setMainWindowController:(SRMainWindowController*)mainWindowController
{
    _mainWindowController = mainWindowController;
}

- (SRSideBarController*)sideBarController
{
    return _sideBarController;
}

- (void)setSidebarController:(SRSideBarController*)sideBarController
{
    _sideBarController = sideBarController;
}

//--------------------------------------------------------------//
#pragma mark -- First responder --
//--------------------------------------------------------------//

#if 0
- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (void)updateNextResponder
{
    // Get tab item identifier
    id  identifier;
    identifier = [[_RSSTab selectedTabViewItem] identifier];
    
    if ([identifier isEqualToString:_SRRSSTabIdentifier]) {
        if ([_RSSOutline nextResponder] != self) {
            [self setNextResponder:[_RSSOutline nextResponder]];
            [_RSSOutline setNextResponder:self];
        }
    }
    if ([identifier isEqualToString:_SRFindResultsTabIdentifier]) {
        if ([_findTable nextResponder] != self) {
            [self setNextResponder:[_findTable nextResponder]];
            [_findTable setNextResponder:self];
        }
    }
}
#endif

//--------------------------------------------------------------//
#pragma mark -- View --
//--------------------------------------------------------------//

- (NSView*)view
{
    return _RSSView;
}

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

- (void)_updateHistoryKey
{
    // Check history
    if (!_currentURLString || [_history count] == 0) {
        [_content setValue:[NSNumber numberWithBool:NO] forKey:@"RSSCanGoBack"];
        [_content setValue:[NSNumber numberWithBool:NO] forKey:@"RSSCanGoForward"];
        return;
    }
    
    // For RSSCanGoBack
    [_content setValue:[NSNumber numberWithBool:_currentURLString != [_history objectAtIndex:0]] 
            forKey:@"RSSCanGoBack"];
    
    // For RSSCanGoForward
    [_content setValue:[NSNumber numberWithBool:_currentURLString != [_history lastObject]] 
            forKey:@"RSSCanGoForward"];
}

- (void)addHistoryWithDocumentId:(NSString*)documentId itemId:(NSString*)itemId
{
    // Create escaped string
    NSString*   escapedDocumentId = nil;
    NSString*   escapedItemId = nil;
    if (documentId) {
        escapedDocumentId = (NSString*)CFXMLCreateStringByEscapingEntities(NULL, (CFStringRef)documentId, NULL);
        [escapedDocumentId autorelease];
    }
    if (itemId) {
        escapedItemId = (NSString*)CFXMLCreateStringByEscapingEntities(NULL, (CFStringRef)itemId, NULL);
        [escapedItemId autorelease];
    }
    
    // Create URL string
    NSString*   URLString = nil;
    if (escapedDocumentId && !escapedItemId) {
        URLString = [NSString stringWithFormat:@"channel://%@", 
                SRSerializePlistForURLFormat([NSDictionary dictionaryWithObjectsAndKeys:
                        escapedDocumentId, @"documentId", nil])];
    }
    if (escapedDocumentId && escapedItemId) {
        URLString = [NSString stringWithFormat:@"item://%@", 
                SRSerializePlistForURLFormat([NSDictionary dictionaryWithObjectsAndKeys:
                        escapedDocumentId, @"documentId", 
                        escapedItemId, @"itemId", nil])];
    }
    
    // Check URL string
    if (!URLString) {
        return;
    }
    if ([_currentURLString isEqualToString:URLString]) {
        return;
    }
    
    // Remove objects
    int index;
    index = [_history indexOfObject:_currentURLString];
    if (index != NSNotFound && index < [_history count] - 1) {
        [_history removeObjectsInRange:NSMakeRange(index + 1, [_history count] - index - 1)];
    }
    
    // Add URL string
    [_history addObject:URLString];
    
    // Set current URL string
    _currentURLString = URLString;
    
    // Update history key
    [self _updateHistoryKey];
}

//--------------------------------------------------------------//
#pragma mark -- Detail view --
//--------------------------------------------------------------//

- (void)setDetailView:(SRRSSDetail)type
{
    // Get detail view
    NSArray*    subviews;
    NSView*     detailView = nil;
    subviews = [_detailView subviews];
    if (subviews && [subviews count] > 0) {
        detailView = [subviews objectAtIndex:0];
    }
    
    // Decide new detail view
    NSView*     newDetailView = nil;
    switch (type) {
    case SRRSSChannelDetail: {
        newDetailView = _channelView;
        break;
    }
    }
    
    // Swap detail view
    if (detailView != newDetailView) {
        // Remove current subview
        [detailView removeFromSuperview];
        
        // Add new view
        [newDetailView setFrameOrigin:NSZeroPoint];
        [newDetailView setFrameSize:[_detailView frame].size];
        [_detailView addSubview:newDetailView];
    }
}

- (NSString*)_channelHTMLWithChannel:(NSDictionary*)channel
{
    NSMutableString*    html;
    html = [NSMutableString string];
    
    // Create header
    [html appendString:@"<html><head><style>tr { line-height: 130%; } a { text-decoration: none; } a:hover { text-decoration: underline; } tr.alt { background: #edf3fe; } span.notPreviewed { font-weight: bold; }</style></head>"];
    
    // Create body
    [html appendString:@"<body style='margin: 0;'>"];
    
    // Get channel info
    NSString*   channelTitle;
    NSString*   channelLink;
    NSString*   documentId;
    NSString*   escapedDocumentId = nil;
    NSDate*     channelDate;
    channelTitle = [channel objectForKey:@"title"];
    channelLink = [channel objectForKey:@"link"];
    documentId = [channel objectForKey:@"documentId"];
    if (documentId) {
        escapedDocumentId = (NSString*)CFXMLCreateStringByEscapingEntities(NULL, (CFStringRef)documentId, NULL);
        [escapedDocumentId autorelease];
    }
    channelDate = [channel objectForKey:@"date"];
    
    NSString*   rssitemLink = nil;
    if (escapedDocumentId) {
        rssitemLink = [NSString stringWithFormat:@"rssitem://%@", 
                SRSerializePlistForURLFormat([NSDictionary dictionaryWithObjectsAndKeys:
                        escapedDocumentId, @"documentId", nil])];
    }
    if (!rssitemLink) {
        rssitemLink = [NSString stringWithFormat:@"http://%@", documentId];
    }
    
    [html appendString:@"<div style='padding: 4px; background: #e9e9e9; border-bottom: 1px solid #919699;'>"];
    [html appendFormat:@"<span style='font-size: large;'><b>%@</b></span>", channelTitle];
    [html appendFormat:@" <span>[<a href='%@'>Web</a>] [<a href='%@'>RSS</a>]<span>", channelLink , rssitemLink];
    [html appendFormat:@"<br/><span style='color: #666666;'>%@%@</span>", NSLocalizedString(@"Updated: ", nil), [channelDate descriptionWithCalendarFormat:NSLocalizedString(@"RSSCalendarFormat", @"%B %e, %Y %I:%M %p") timeZone:nil locale:nil]];
    
#if 0
    // Get image URL
    NSString*   imageURL;
    imageURL = [channel objectForKey:@"imageURL"];
    if (imageURL) {
        [html appendFormat:@"<p><img src='%@'></p>", imageURL];
    }
#endif
    
    [html appendFormat:@"</div>"];
    
    // Add items
    NSArray*        items;
    items = [channel objectForKey:@"items"];
    if (items && [items count] > 0) {
        [html appendString:@"<table cellspacing='0' cellpadding='2' style='width: 100%;'>"];
        
        int index = 0;
        NSEnumerator*   enumerator;
        NSDictionary*   item;
        enumerator = [items objectEnumerator];
        while (item = [enumerator nextObject]) {
            NSString*   itemTitle;
            NSString*   itemLink;
            itemTitle = [item objectForKey:@"title"];
            itemLink = [item objectForKey:@"link"];
            
            if (itemTitle && itemLink) {
                // For item scheme
                NSString*   itemId;
                NSString*   escapedItemId;
                NSString*   plistString = @"";
                itemId = [SRSDRSSDocument itemIdentifierWithItem:item];
                if (itemId) {
                    escapedItemId = (NSString*)CFXMLCreateStringByEscapingEntities(NULL, (CFStringRef)itemId, NULL);
                    [escapedItemId autorelease];
                }
                if (escapedDocumentId && escapedItemId) {
                    plistString = SRSerializePlistForURLFormat([NSDictionary dictionaryWithObjectsAndKeys:
                            escapedDocumentId, @"documentId", 
                            escapedItemId, @"itemId", nil]);
                }
                
                if (index % 2 == 0) {
                    [html appendFormat:@"<tr><td>"];
                }
                else {
                    [html appendFormat:@"<tr class='alt'><td>"];
                }
                
                [html appendFormat:@"<a href='item://%@'>", plistString];
                
                NSNumber*   isPreviewed;
                isPreviewed = [item objectForKey:@"isPreviewed"];
                [html appendFormat:@"<span class='%@' id='%@'>%@</span>", 
                        isPreviewed && [isPreviewed boolValue] ? @"previewed" : @"notPreviewed", 
                        escapedItemId ? escapedItemId : @"", 
                        itemTitle];
                [html appendFormat:@"</a>"];
                
                // For webitem scheme
                NSString*   escapedItemLink;
                escapedItemLink = (NSString*)CFXMLCreateStringByEscapingEntities(NULL, (CFStringRef)itemLink, NULL);
                plistString = @"";
                if (escapedDocumentId && escapedItemId && escapedItemLink) {
                    plistString = SRSerializePlistForURLFormat([NSDictionary dictionaryWithObjectsAndKeys:
                            escapedDocumentId, @"documentId", 
                            escapedItemId, @"itemId", 
                            escapedItemLink, @"itemLink", nil]);
                }
                [html appendFormat:@" [<a href='webitem://%@'>Web</a>]", plistString];
                
                [html appendFormat:@"</td></tr>"];
                
                index++;
            }
        }
        
        [html appendString:@"</table>"];
    }
    else {
        [html appendString:@"<div style='padding: 4px'>"];
        [html appendFormat:@"<p style='color: #666666'>%@</p>", NSLocalizedString(@"There are no articles", nil)];
        [html appendString:@"</div>"];
    }
    
    // End body
    [html appendString:@"</body></html>"];
    
    return html;
}

- (NSString*)_itemHTMLWithChannel:(NSDictionary*)channel item:(NSDictionary*)item
{
    NSMutableString*    html;
    html = [NSMutableString string];
    
    // Create header
    [html appendString:@"<html><head><style>p { line-height: 130%; } a { text-decoration: none; } a:hover { text-decoration: underline; } span.notPreviewed { font-weight: bold; }</style></head>"];
    
    // Create body
    [html appendString:@"<body style='margin:0;'>"];
    
    // Get channel info
    NSString*   channelTitle;
    NSString*   channelLink;
    NSString*   documentId;
    NSString*   escapedDocumentId = nil;
    NSString*   plistString = @"";
    channelTitle = [channel objectForKey:@"title"];
    channelLink = [channel objectForKey:@"link"];
    documentId = [channel objectForKey:@"documentId"];
    if (documentId) {
        escapedDocumentId = (NSString*)CFXMLCreateStringByEscapingEntities(NULL, (CFStringRef)documentId, NULL);
        [escapedDocumentId autorelease];
        plistString = SRSerializePlistForURLFormat([NSDictionary dictionaryWithObjectsAndKeys:
                escapedDocumentId, @"documentId", nil]);
    }
    
    // Get item info
    NSString*   itemTitle;
    NSString*   itemLink;
    NSString*   itemCategory;
    NSDate*     itemDate;
    itemTitle = [item objectForKey:@"title"];
    itemLink = [item objectForKey:@"link"];
    itemCategory = [item objectForKey:@"category"];
    itemDate = [item objectForKey:@"date"];
    
    [html appendString:@"<div style='padding: 4px; background: #e9e9e9; border-bottom: 1px solid #919699;'>"];
    [html appendFormat:@"<span style='font-size: small;'><a href='channel://%@' style='color: black;'><b>%@</b></a></span><br/>", plistString, channelTitle];
    [html appendFormat:@"<span style='font-size: large;'><b>%@</b></span>", itemTitle];
    [html appendFormat:@" <span>[<a href='%@'>Web</a>]<span>", itemLink];
    [html appendFormat:@"<br/><span style='color: #666666;'>%@%@</span>", NSLocalizedString(@"PubDate: ", nil), [itemDate descriptionWithCalendarFormat:NSLocalizedString(@"RSSCalendarFormat", @"%B %e, %Y %I:%M %p") timeZone:nil locale:nil]];
    
    // Get prev and next item
    NSArray*        items;
    NSEnumerator*   enumerator;
    NSDictionary*   tmpItem;
    int             index = 0, itemIndex = NSNotFound;
    items = [channel objectForKey:@"items"];
    enumerator = [items objectEnumerator];
    while (tmpItem = [enumerator nextObject]) {
        if ([itemTitle isEqualToString:[tmpItem objectForKey:@"title"]] && 
            [itemLink isEqualToString:[tmpItem objectForKey:@"link"]])
        {
            itemIndex = index;
            break;
        }
        index++;
    }
    if (itemIndex != NSNotFound) {
        NSDictionary*   prevItem = nil;
        NSDictionary*   nextItem = nil;
        if (itemIndex > 0) {
            prevItem = [items objectAtIndex:itemIndex - 1];
        }
        if (itemIndex < [items count] - 1) {
            nextItem = [items objectAtIndex:itemIndex + 1];
        }
        
        if (prevItem || nextItem) {
            NSNumber*   isPreviewed;
            NSString*   title;
            NSString*   itemId;
            NSString*   escapedItemId;
            [html appendFormat:@"<div style='font-size: small;'>"];
            if (prevItem) {
                itemId = [SRSDRSSDocument itemIdentifierWithItem:prevItem];
                escapedItemId = nil;
                if (itemId) {
                    escapedItemId = (NSString*)CFXMLCreateStringByEscapingEntities(NULL, (CFStringRef)itemId, NULL);
                    [escapedItemId autorelease];
                }
                
                plistString = @"";
                if (escapedItemId && escapedDocumentId) {
                    plistString = SRSerializePlistForURLFormat([NSDictionary dictionaryWithObjectsAndKeys:
                            escapedDocumentId, @"documentId", 
                            escapedItemId, @"itemId", nil]);
                }
                
                isPreviewed = [prevItem objectForKey:@"isPreviewed"];
                title = [prevItem objectForKey:@"title"];
                if (!isPreviewed || ![isPreviewed boolValue]) {
                    [html appendFormat:@"<span class='notPreviewed'>"];
                }
                [html appendFormat:@"<a href='item://%@' style='color: black;'>&lt;&lt; %@</a>", plistString, title];
                if (!isPreviewed || ![isPreviewed boolValue]) {
                    [html appendFormat:@"</span>"];
                }
                [html appendFormat:@"<br/>"];
            }
            if (nextItem) {
                itemId = [SRSDRSSDocument itemIdentifierWithItem:nextItem];
                escapedItemId = nil;
                if (itemId) {
                    escapedItemId = (NSString*)CFXMLCreateStringByEscapingEntities(NULL, (CFStringRef)itemId, NULL);
                    [escapedItemId autorelease];
                }
                
                plistString = @"";
                if (escapedItemId && escapedDocumentId) {
                    plistString = SRSerializePlistForURLFormat([NSDictionary dictionaryWithObjectsAndKeys:
                            escapedDocumentId, @"documentId", 
                            escapedItemId, @"itemId", nil]);
                }
                
                isPreviewed = [nextItem objectForKey:@"isPreviewed"];
                title = [nextItem objectForKey:@"title"];
                if (!isPreviewed || ![isPreviewed boolValue]) {
                    [html appendFormat:@"<span class='notPreviewed'>"];
                }
                [html appendFormat:@"<a href='item://%@' style='color: black;'>&gt;&gt; %@</a>", plistString, title];
                if (!isPreviewed || ![isPreviewed boolValue]) {
                    [html appendFormat:@"</span>"];
                }
                [html appendFormat:@"<br/>"];
            }
            [html appendFormat:@"</div>"];
        }
    }
    
    [html appendFormat:@"</div>"];
    
    [html appendFormat:@"<div style='padding: 4px;'>"];
    
    // Get image
    
    // Get description
    NSString*   description;
    description = [item objectForKey:@"description"];
    if (description && [description length] > 0) {
        [html appendFormat:@"<p>%@</p>", description];
    }
    else {
        [html appendFormat:@"<p style='color: #666666'>%@</p>", NSLocalizedString(@"There is no description", nil)];
    }
    [html appendFormat:@"</div>"];
    
    // End body
    [html appendString:@"</body></html>"];
    
    return html;
}

- (void)_updateDetailView
{
    // Get selected items
    NSArray*    items;
    items = [self selectedItems];
    
    // Clear HTML
    if (!items || [items count] == 0) {
        [[_channelWebView mainFrame] loadHTMLString:@"" baseURL:nil];
        return;
    }
    
    // Multiple selection
    if ([items count] > 1) {
        return;
    }
    
    // Get item
    NSMutableDictionary*    item;
    item = [items objectAtIndex:0];
    
    // Get type
    NSString*   type;
    type = [item objectForKey:@"type"];
    
    // For RSS channel
    if ([type isEqualToString:@"channel"]) {
        // Create HTML string
        NSString*   HTMLString;
        HTMLString = [self _channelHTMLWithChannel:item];
        [[_channelWebView mainFrame] loadHTMLString:HTMLString baseURL:nil];
        
        // Add history
        [self addHistoryWithDocumentId:[item objectForKey:@"documentId"] itemId:nil];
        return;
    }
    
    // For RSS item
    if ([type isEqualToString:@"item"]) {
        // Get channel
        NSString*               documentId;
        NSMutableDictionary*    channel;
        documentId = [item objectForKey:@"documentId"];
        channel = [[SRRSSManager sharedInstance] channelWithDocumentId:documentId];
        
        // Get item dict
        NSString*               itemId;
        NSMutableDictionary*    itemDict;
        itemId = [SRSDRSSDocument itemIdentifierWithItem:item];
        itemDict = [[SRRSSManager sharedInstance] itemWithItemId:itemId documentId:documentId];
        if (!itemDict) {
            return;
        }
        
        // Create HTML string
        NSString*   HTMLString;
        HTMLString = [self _itemHTMLWithChannel:channel item:itemDict];
        [[_channelWebView mainFrame] loadHTMLString:HTMLString baseURL:nil];
        
        // Add history
        [self addHistoryWithDocumentId:[item objectForKey:@"documentId"] itemId:itemId];
        
        // Make item previewed
        [[SRRSSManager sharedInstance] makeItemsPreviewed:[NSArray arrayWithObject:item]];
        
        return;
    }
}

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

- (NSArray*)selectedItems
{
    // Get tab item identifier
    id  identifier;
    identifier = [[_RSSTab selectedTabViewItem] identifier];
    
    // Get selected RSS
    // For RSS outlie
    if ([identifier isEqualToString:_SRRSSTabIdentifier]) {
        // Get index set
        NSIndexSet* indexSet;
        indexSet = [_RSSOutline selectedRowIndexes];
        if (!indexSet || [indexSet count] == 0) {
            return nil;
        }
        
        // Get selected rows
        NSMutableArray* items;
        unsigned int    index;
        items = [NSMutableArray array];
        index = [indexSet firstIndex];
        do {
            // Get selected item
            id  item;
            item = [_RSSOutline itemAtRow:index];
            [items addObject:item];
        } while ((index = [indexSet indexGreaterThanIndex:index]) != NSNotFound);
        
        return items;
    }
    // For find table
    if ([identifier isEqualToString:_SRFindResultsTabIdentifier]) {
        // Get selected rows
        NSIndexSet* indexSet;
        indexSet = [_findTable selectedRowIndexes];
        if (!indexSet || [indexSet count] == 0) {
            return nil;
        }
        
        // Get selected rows
        NSMutableArray* items;
        unsigned int    index;
        items = [NSMutableArray array];
        index = [indexSet firstIndex];
        do {
            // Get selected item
            id  item;
            item = [_findResults objectAtIndex:index];
            [items addObject:item];
        } while ((index = [indexSet indexGreaterThanIndex:index]) != NSNotFound);
        
        return items;
    }
    
    return nil;
}

- (NSDictionary*)_selectedItem
{
    // Get selected RSS
    NSArray*        items;
    NSDictionary*   item;
    items = [self selectedItems];
    if (!items || [items count] == 0) {
        return nil;
    }
    
    // Get first object
    return [items objectAtIndex:0];
    
    // Get link
    return [item objectForKey:@"link"];
}

- (NSString*)_selectedLink
{
    // Get selected item
    NSDictionary*   item;
    item = [self _selectedItem];
    if (!item) {
        return nil;
    }
    
    // Get link
    return [item objectForKey:@"link"];
}

- (NSString*)_selectedDocumentId
{
    // Get selected item
    NSDictionary*   item;
    item = [self _selectedItem];
    if (!item) {
        return nil;
    }
    
    // Get document ID
    return [item objectForKey:@"documentId"];
}

- (void)reloadRSSFindTable
{
    // Get find string
    NSString*   findString;
    findString = [_searchField stringValue];
    
    // Find RSS
    NSMutableArray* results;
    NSEnumerator*   enumerator;
    NSDictionary*   channel;
    results = [NSMutableArray array];
    enumerator = [_channels objectEnumerator];
    while (channel = [enumerator nextObject]) {
        NSEnumerator*   itemEnumerator;
        NSDictionary*   item;
        itemEnumerator = [[channel objectForKey:@"items"] objectEnumerator];
        while (item = [itemEnumerator nextObject]) {
            // Get title
            NSString*   title;
            title = [item objectForKey:@"title"];
            if (title) {
                if ([title rangeOfString:findString].location != NSNotFound) {
                    [results addObject:item];
                    continue;
                }
            }
            
            // Get link
            NSString*   link;
            link = [item objectForKey:@"link"];
            if (link) {
                if ([link rangeOfString:findString].location != NSNotFound) {
                    [results addObject:item];
                    continue;
                }
            }
        }
    }
    
    // Reload table
    [_findResults release];
    _findResults = [results retain];
    [_findTable reloadData];
}

- (NSMenu*)_contextMenuForView:(id)view 
        event:(NSEvent*)event
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Get modifier key flag
    unsigned int    modifierFlags;
    unsigned int    cmdFlag, optionFlag, shiftFlag;
    modifierFlags = [event modifierFlags];
    cmdFlag = modifierFlags & NSCommandKeyMask;
    optionFlag = modifierFlags & NSAlternateKeyMask;
    shiftFlag = modifierFlags & NSShiftKeyMask;
    
    // Create array for tags
    NSMutableArray* tags;
    tags = [NSMutableArray array];
    
    // Select RSS under the cursor
    if ([view isKindOfClass:[NSTableView class]]) {
        NSPoint point;
        int     rowUnderPoint;
        point = [view convertPoint:[event locationInWindow] 
                fromView:nil];
        rowUnderPoint = [view rowAtPoint:point];
        if (![[view selectedRowIndexes] containsIndex:rowUnderPoint]) {
            [view selectRowIndexes:[NSIndexSet indexSetWithIndex:rowUnderPoint] 
                    byExtendingSelection:NO];
        }
    }
    
    // Get selected items
    NSArray*    items;
    items = [self selectedItems];
    
    NSDictionary*   item = nil;
    
    // When item is selected
    if ([items count] > 0) {
        // Check tab availability
        BOOL    enableTabbedBrowsing, selectNewTabs;
        enableTabbedBrowsing = [defaults boolForKey:SRTabEnableTabbedBrowsing];
        selectNewTabs = [defaults boolForKey:SRTabSelectNewTabs];
        
        // Just one item is selected
        if ([items count] == 1) {
            // Get item
            item = [items objectAtIndex:0];
            
            // Open RSS
            [tags addObject:[NSNumber numberWithInt:SROpenRSSTag]];
            
            // For tab
            if (enableTabbedBrowsing) {
                if ((selectNewTabs && !shiftFlag) || 
                    (!selectNewTabs && shiftFlag))
                {
                    [tags addObject:[NSNumber numberWithInt:SROpenRSSInNewWindowTag]];
                    [tags addObject:[NSNumber numberWithInt:SROpenRSSInNewTabTag]];
                }
                else {
                    [tags addObject:[NSNumber numberWithInt:SROpenRSSInNewBackgroundWindowTag]];
                    [tags addObject:[NSNumber numberWithInt:SROpenRSSInNewBackgroundTabTag]];
                }
            }
            else {
                [tags addObject:[NSNumber numberWithInt:SROpenRSSInNewWindowTag]];
            }
            
            // For channel view
            if (view == _RSSOutline) {
                // Get link
                NSString*   link;
                link = [item objectForKey:@"link"];
                if (link && [link length] > 0) {
                    // Open Web site
                    [tags addObject:[NSNumber numberWithInt:SROpenRSSWebSiteTag]];
                }
                
                // Reload RSS
                [tags addObject:[NSNumber numberWithInt:SRReloadRSSAction]];
                
                // Not update automatically
                [tags addObject:[NSNumber numberWithInt:SRNotUpdateAutioAction]];
                
                // Reveal in Bookmark
                [tags addObject:[NSNumber numberWithInt:SRRevealInBookmarkAction]];
                
                // Mark as read
                [tags addObject:[NSNumber numberWithInt:SRMarkAsReadTag]];
                
                // View source
                [tags addObject:[NSNumber numberWithInt:SRViewSourceRSSTag]];
            }
        }
        // For multi selection
        else {
            // For tab
            if (enableTabbedBrowsing) {
                [tags addObject:[NSNumber numberWithInt:SROpenRSSInTabsTag]];
            }
        }
    }
    
    // For channel view
    if (view == _RSSOutline) {
        // Delete all RSS
        [tags addObject:[NSNumber numberWithInt:SRDeleteAllRSSTag]];
        
        // Mark all articles as read
        [tags addObject:[NSNumber numberWithInt:SRMarkAllArticlesAsReadTag]];
    }
    
    if ([tags count] > 0) {
        // Copy menu
        NSMenu* menu;
        menu = [SRContextMenu copyMenuFrom:[SRContextMenu RSSContextMenu] 
                ofTags:tags 
                target:self];
        
        // Set represented object
        if (item) {
            [[menu itemArray] makeObjectsPerformSelector:@selector(setRepresentedObject:) 
                    withObject:item];
        }
        else {
            [[menu itemArray] makeObjectsPerformSelector:@selector(setRepresentedObject:) 
                    withObject:items];
        }
        
        // Set target
        [[menu itemArray] makeObjectsPerformSelector:@selector(setTarget:) withObject:self];
        
        return menu;
    }
    
    return nil;
}

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

- (void)sortRSSChannels
{
    // Get sort descriptors
    NSArray*    descriptors;
    descriptors = [_RSSOutline sortDescriptors];
    
    // Sort channels
    NSArray*    sortedArray;
    sortedArray = [_channels sortedArrayUsingDescriptors:descriptors];
    [_channels release];
    _channels = [[NSMutableArray arrayWithArray:sortedArray] retain];
    
    // Sort items in channels
    NSEnumerator*   enumerator;
    NSDictionary*   channel;
    enumerator = [_channels objectEnumerator];
    while (channel = [enumerator nextObject]) {
        NSMutableArray* items;
        items = [channel objectForKey:@"items"];
        [items sortUsingDescriptors:descriptors];
    }
    
    // Sort items
    sortedArray = [_items sortedArrayUsingDescriptors:descriptors];
    [_items release];
    _items = [[NSMutableArray arrayWithArray:sortedArray] retain];
    
    // Realod outline
    [_RSSOutline reloadData];
}

- (void)reloadRSSChannels
{
    // Get selected item info
    NSString*   selectedDocumentId = nil;
    NSString*   selectedItemLink = nil;
    BOOL        isExpanded = NO;
    
    int selectedRow;
    selectedRow = [_RSSOutline selectedRow];
    if (selectedRow != -1) {
        NSDictionary*   item = nil;
        item = [_RSSOutline itemAtRow:selectedRow];
        
        // Get type
        NSString*   type;
        type = [item objectForKey:@"type"];
        
        // For channel
        if ([type isEqualToString:@"channel"]) {
            selectedDocumentId = [[[item objectForKey:@"documentId"] copy] autorelease];
            isExpanded = [_RSSOutline isItemExpanded:item];
        }
        // For item
        if ([type isEqualToString:@"item"]) {
            selectedDocumentId = [[[item objectForKey:@"documentId"] copy] autorelease];
            selectedItemLink = [[[item objectForKey:@"link"] copy] autorelease];
        }
    }
    
    // Reload outline
    [_channels release];
    _channels = [[[SRRSSManager sharedInstance] channels] retain];
    [_items release];
    _items = [[[SRRSSManager sharedInstance] items] retain];
    [self sortRSSChannels];
    
    // Select previous selected item
    if (selectedDocumentId) {
        NSDictionary*   channel;
        channel = [SRRSSManager channelWithDocumentIdentifier:selectedDocumentId channels:_channels];
        if (channel) {
            if (selectedItemLink) {
                // Expand channel
                [_RSSOutline expandItem:channel];
                
                // Find item
                NSEnumerator*   enumerator;
                NSDictionary*   item;
                enumerator = [[channel objectForKey:@"items"] objectEnumerator];
                while (item = [enumerator nextObject]) {
                    if ([selectedItemLink isEqualToString:[item objectForKey:@"link"]]) {
                        // Select itme
                        selectedRow = [_RSSOutline rowForItem:item];
                        if (selectedRow > 0) {
                            [_RSSOutline selectRow:selectedRow byExtendingSelection:NO];
                            [_RSSOutline scrollRowToVisible:selectedRow];
                        }
                        break;
                    }
                }
            }
            else {
                // Select channel
                selectedRow = [_RSSOutline rowForItem:channel];
                if (selectedRow > 0) {
                    [_RSSOutline selectRow:selectedRow byExtendingSelection:NO];
                    [_RSSOutline scrollRowToVisible:selectedRow];
                }
                
                // Expand channel
                if (isExpanded) {
                    [_RSSOutline expandItem:channel];
                }
            }
        }
    }
}

- (void)RSSWillStartRefresh:(NSNotification*)notification
{
    // Update RSS status
    [_content setValue:[NSNumber numberWithBool:YES] forKey:@"RSSIsLoading"];
//    [_content setValue:[proxy loadingStatusDescription] forKey:@"RSSStatusMessage"];
}

- (void)RSSProgressRefresh:(NSNotification*)notification
{
    // Update RSS status
    [_content setValue:[NSNumber numberWithBool:YES] forKey:@"RSSIsLoading"];
//    [_content setValue:[proxy loadingStatusDescription] forKey:@"RSSStatusMessage"];
    
    // Realod data
    [self reloadRSSChannels];
}

- (void)RSSDidEndRefresh:(NSNotification*)notification
{
    // Update RSS status
    [_content setValue:[NSNumber numberWithBool:NO] forKey:@"RSSIsLoading"];
//    [_content setValue:[proxy loadingStatusDescription] forKey:@"RSSStatusMessage"];
    
    // Realod data
    [self reloadRSSChannels];
    
    // Update appearance
    [self _updateDetailView];
}

- (void)RSSItemsRemovedAll:(NSNotification*)notification
{
    // Realod data
    [self reloadRSSChannels];
}

- (void)RSSItemsPreviewed:(NSNotification*)notification
{
    // Reload items and channels
    NSEnumerator*   enumerator;
    NSDictionary*   item;
    enumerator = [[notification object] objectEnumerator];
    while (item = [enumerator nextObject]) {
        // Reload item
        [_RSSOutline reloadItem:item];
        
        // Reload channel
        NSDictionary*   channel;
        channel = [SRRSSManager channelWithDocumentIdentifier:[item objectForKey:@"documentId"] channels:_channels];
        if (channel) {
            [_RSSOutline reloadItem:channel];
        }
        
        // Get item ID
        NSString*   itemId;
        NSString*   escapedItemId = nil;
        itemId = SRSDRSSItemIdentifier(item);
        if (itemId) {
            escapedItemId = (NSString*)CFXMLCreateStringByEscapingEntities(NULL, (CFStringRef)itemId, NULL);
        }
        if (escapedItemId) {
            // Get DOM document
            DOMDocument*    document;
            document = [[_channelWebView mainFrame] DOMDocument];
            
            // Get element by ID
            DOMElement* element;
            element = [document getElementById:escapedItemId];
            if (element) {
                // Make element previewed
                NSString*   klass;
                klass = [element getAttribute:@"class"];
                if (klass && [klass isEqualToString:@"notPreviewed"]) {
                    [element setAttribute:@"class" :@"previewed"];
                }
            }
        }
    }
}

- (void)RSSFeedsDidChanged:(NSNotification*)notification
{
    // Realod data
    [self reloadRSSChannels];
}

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

- (void)goBackAction:(id)sender
{
    // Check history
    if (!_currentURLString || [_history count] == 0 || _currentURLString == [_history objectAtIndex:0]) {
        return;
    }
    
    // Get index
    int index;
    index = [_history indexOfObject:_currentURLString];
    if (index == NSNotFound || index == 0) {
        return;
    }
    
    // Go back
    NSString*   URLString;
    URLString = [_history objectAtIndex:index - 1];
    _currentURLString = URLString;
    [[_channelWebView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:URLString]]];
    
    // Update history key
    [self _updateHistoryKey];
}

- (void)goForwardAction:(id)sender
{
    // Check history
    if (!_currentURLString || [_history count] == 0 || _currentURLString == [_history lastObject]) {
        return;
    }
    
    // Get index
    int index;
    index = [_history indexOfObject:_currentURLString];
    if (index == NSNotFound || index == [_history count] - 1) {
        return;
    }
    
    // Go back
    NSString*   URLString;
    URLString = [_history objectAtIndex:index + 1];
    _currentURLString = URLString;
    [[_channelWebView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:URLString]]];
    
    // Update history key
    [self _updateHistoryKey];
}

- (void)reloadAction:(id)sender
{
    if (![[SRRSSManager sharedInstance] isRSSSyndicationWorking]) {
        [[SRRSSManager sharedInstance] refreshAllFeeds];
    }
}

- (void)stopAction:(id)sender
{
    if ([[SRRSSManager sharedInstance] isRSSSyndicationWorking]) {
        [[SRRSSManager sharedInstance] stopRefresh];
    }
}

- (void)goChannelLinkAction:(id)sender
{
    // Get channel link
    NSString*   URLString;
    URLString = [_content valueForKey:@"channelLink"];
    if (!URLString) {
        return;
    }
    
    // Open in main window controller
    [_mainWindowController openURLString:URLString];
}

- (void)RSSSelectedAction:(id)sender
{
    // Update appearance
    [self _updateDetailView];
}

- (NSString*)_URLStringOfItem:(NSDictionary*)item
{
    if (!item) {
        return nil;
    }
    
    // Get type
    NSString*   type;
    type = [item objectForKey:@"type"];
    
    // For channel
    if ([type isEqualToString:@"channel"]) {
        // Get document ID
        return [item objectForKey:@"documentId"];
    }
    
    // For item
    if ([type isEqualToString:@"item"]) {
        // Get link
        return [item objectForKey:@"link"];
    }
    
    return nil;
}

- (void)_makeItemPreviewed:(NSDictionary*)item
{
    if (!item) {
        return;
    }
    
    // Get type
    NSString*   type;
    type = [item objectForKey:@"type"];
    
    // For channel
    if ([type isEqualToString:@"channel"]) {
        // Make all items previewed
        [[SRRSSManager sharedInstance] makeItemsPreviewed:[item objectForKey:@"items"]];
    }
    
    // For item
    if ([type isEqualToString:@"item"]) {
        // Make item previewed
        [[SRRSSManager sharedInstance] makeItemsPreviewed:[NSArray arrayWithObject:item]];
    }
}

- (void)openRSSAction:(id)sender
{
    // Get URL string og selected item
    NSDictionary*   item;
    NSString*       URLString;
    item = [self _selectedItem];
    URLString = [self _URLStringOfItem:item];
    if (!URLString) {
        return;
    }
    
    // Open URL
    [_mainWindowController openURLString:URLString];
    
    // Make item previewd
    [self _makeItemPreviewed:item];
}

- (void)openRSSInNewWindowAction:(id)sender
{
    // Get URL string og selected item
    NSDictionary*   item;
    NSString*       URLString;
    item = [self _selectedItem];
    URLString = [self _URLStringOfItem:item];
    if (!URLString) {
        return;
    }
    
    // Open URL in new window
    [_mainWindowController openInNewWindowURLString:URLString];
    
    // Make item previewd
    [self _makeItemPreviewed:item];
}

- (void)openRSSInNewTabAction:(id)sender
{
    // Get URL string og selected item
    NSDictionary*   item;
    NSString*       URLString;
    item = [self _selectedItem];
    URLString = [self _URLStringOfItem:item];
    if (!URLString) {
        return;
    }
    
    // Open URL in new window
    [_mainWindowController openInNewTabURLString:URLString select:YES];
    
    // Make item previewd
    [self _makeItemPreviewed:item];
}

- (void)openRSSInNewBackgroundWindowAction:(id)sender
{
    // Get URL string og selected item
    NSDictionary*   item;
    NSString*       URLString;
    item = [self _selectedItem];
    URLString = [self _URLStringOfItem:item];
    if (!URLString) {
        return;
    }
    
    // Open URL in new bakcground window
    [_mainWindowController openInNewBackgroundWindowURLString:URLString];
    
    // Make item previewd
    [self _makeItemPreviewed:item];
}

- (void)openRSSInNewBackgroundTabAction:(id)sender
{
    // Get URL string of selected item
    NSDictionary*   item;
    NSString*       URLString;
    item = [self _selectedItem];
    URLString = [self _URLStringOfItem:item];
    if (!URLString) {
        return;
    }
    
    // Open URL in new bakcground window
    [_mainWindowController openInNewTabURLString:URLString select:NO];
    
    // Make item previewd
    [self _makeItemPreviewed:item];
}

- (void)openRSSInTabsAction:(id)sender
{
    // Get selected RSS
    NSArray*        items;
    items = [self selectedItems];
    if (!items) {
        return;
    }
    
    // Collect URL strings
    NSMutableArray* URLStrings;
    NSEnumerator*   enumerator;
    NSDictionary*   item;
    URLStrings = [NSMutableArray array];
    enumerator = [items objectEnumerator];
    while (item = [enumerator nextObject]) {
        // Get URL string of item
        NSString*   URLString;
        URLString = [self _URLStringOfItem:item];
        if (!URLString || [URLString length] == 0) {
            continue;
        }
        
        [URLStrings addObject:URLString];
    }
    
    // Open RSS
    [_mainWindowController openInNewTabsURLStrings:URLStrings select:YES];
}

- (void)openRSSWebSiteAction:(id)sender
{
    // Get selected link
    NSString*   link;
    link = [self _selectedLink];
    if (!link || [link length] == 0) {
        return;
    }
    
    // Open link with action
    SROpenActionType    openAction;
    openAction = SROpenActionTypeFromModifierFlags([[NSApp currentEvent] modifierFlags]);
    [_mainWindowController openURLString:link withOpenAction:openAction];
}

- (void)deleteAllRSSAction:(id)sender
{
    // Delete all items
    [[SRRSSManager sharedInstance] deleteAllItems];
}

- (void)markAsReadAction:(id)sender
{
    // Get selected items
    NSArray*    selectedItems;
    selectedItems = [self selectedItems];
    if (!selectedItems || [selectedItems count] == 0) {
        return;
    }
    
    // Collect items
    NSMutableArray* items;
    NSEnumerator*   enumerator;
    NSDictionary*   item;
    items = [NSMutableArray array];
    enumerator = [selectedItems objectEnumerator];
    while (item = [enumerator nextObject]) {
        // Get type
        NSString*   type;
        type = [item objectForKey:@"type"];
        
        // For channel
        if ([type isEqualToString:@"channel"]) {
            NSArray*        children;
            NSEnumerator*   childEnumerator;
            NSDictionary*   child;
            children = [item objectForKey:@"items"];
            childEnumerator = [children objectEnumerator];
            while (child = [childEnumerator nextObject]) {
                if (![items containsObject:child]) {
                    [items addObject:child];
                }
            }
        }
        // For item
        if ([type isEqualToString:@"item"]) {
            if (![items containsObject:item]) {
                [items addObject:item];
            }
        }
    }
    
    // Make items previewed
    [[SRRSSManager sharedInstance] makeItemsPreviewed:items];
}

- (void)markAllArticlesAsReadAction:(id)sender
{
    // Make items previewed
    [[SRRSSManager sharedInstance] makeItemsPreviewed:_items];
}

- (void)reloadRSSAction:(id)sender
{
    // Get selected item
    NSArray*        items;
    NSDictionary*   item;
    items = [self selectedItems];
    if (!items || [items count] == 0) {
        return;
    }
    item = [items objectAtIndex:0];
    
    // Get feed URL
    NSString*   feedURLString;
    feedURLString = [item objectForKey:@"documentId"];
    if (!feedURLString) {
        return;
    }
    
    // Refresh feed
    [[SRRSSManager sharedInstance] refreshFeeds:[NSArray arrayWithObject:feedURLString]];
}

- (void)notUpdateAutoAction:(id)sender
{
    // Get selected items
    NSArray*    items;
    items = [self selectedItems];
    if (!items || [items count] == 0) {
        return;
    }
    
    // Enumerate item
    NSEnumerator*   enumerator;
    NSDictionary*   item;
    enumerator = [items objectEnumerator];
    while (item = [enumerator nextObject]) {
        // Get feed URL
        NSString*   feedURLString;
        feedURLString = [item objectForKey:@"documentId"];
        if (!feedURLString) {
            continue;
        }
        
        // Get bookmark
        NSArray*        bookmarks;
        NSEnumerator*   bookmarkEnumerator;
        SRBookmark*     bookmark;
        bookmarks = [[SRBookmarkStorage sharedInstance] findBookmarkWithURLString:feedURLString];
        bookmarkEnumerator = [bookmarks objectEnumerator];
        while (bookmark = [bookmarkEnumerator nextObject]) {
            if ([bookmark type] == SRBookmarkTypeRSS && [bookmark isAutoUpdate]) {
                [bookmark setAutoUpdate:NO];
            }
        }
    }
}

- (void)revealInBookmarkAction:(id)sender
{
    // Get selected item
    NSArray*        items;
    NSDictionary*   item;
    items = [self selectedItems];
    if (!items || [items count] == 0) {
        return;
    }
    item = [items objectAtIndex:0];
    
    // Get feed URL
    NSString*   feedURLString;
    feedURLString = [item objectForKey:@"documentId"];
    if (!feedURLString) {
        return;
    }
    
    // Reveal in bookmark
    [[_sideBarController bookmarksController] 
            revealBookmarkWithURLString:feedURLString];
    [_sideBarController showTab:SRSidebarBookmarkTab];
}

- (void)findRSSAction:(id)sender
{
    // Get find string
    NSString*   findString;
    findString = [_searchField stringValue];
    
    // Switch tab item
    if ([findString length] == 0) {
        [_RSSTab selectTabViewItemWithIdentifier:_SRRSSTabIdentifier];
        return;
    }
    else {
        [_RSSTab selectTabViewItemWithIdentifier:_SRFindResultsTabIdentifier];
    }
    
    // Reload table
    [self reloadRSSFindTable];
    
    // Update detail view
    [self _updateDetailView];
}

//--------------------------------------------------------------//
#pragma mark -- WebIconDatabase notification --
//--------------------------------------------------------------//

- (void)faviconAdded:(NSNotification*)notification
{
    // Realod data
    [self reloadRSSChannels];
}

//--------------------------------------------------------------//
#pragma mark -- SRSideBarController delegate --
//--------------------------------------------------------------//

- (void)sideBarWillShow
{
    // Reload outline
    [self reloadRSSChannels];
}

//--------------------------------------------------------------//
#pragma mark -- NSTabView delegate --
//--------------------------------------------------------------//

- (void)tabView:(NSTabView*)tabView 
        didSelectTabViewItem:(NSTabViewItem*)tabViewItem
{
    // Update detail view
    [self _updateDetailView];
}

//--------------------------------------------------------------//
#pragma mark -- NSTableDataSource --
//--------------------------------------------------------------//

- (int)numberOfRowsInTableView:(NSTableView*)tableView
{
    // Find table
    if (tableView == _findTable) {
        return [_findResults count];
    }
    
    return 0;
}

- (id)tableView:(NSTableView*)tableView 
        objectValueForTableColumn:(NSTableColumn*)tableColumn 
        row:(int)rowIndex
{
    // Get identifier
    id  identifier;
    identifier = [tableColumn identifier];
    
    // Find table
    if (tableView == _findTable) {
        if ([identifier isEqualToString:@"results"]) {
            return [[_findResults objectAtIndex:rowIndex] objectForKey:@"title"];
        }
        if ([identifier isEqualToString:@"date"]) {
            NSDate* date;
            date = [[_findResults objectAtIndex:rowIndex] objectForKey:@"date"];
            if (!date) {
                return @"--/-- --:--";
            }
            return [date descriptionWithCalendarFormat:@"%m/%d %H:%M" timeZone:[NSTimeZone systemTimeZone] locale:nil];
        }
    }
    
    return nil;
}

- (void)tableView:(NSTableView*)tableView 
        sortDescriptorsDidChange:(NSArray*)oldDescriptors
{
    // Get sort descriptors
    NSArray*    descriptors;
    descriptors = [_findTable sortDescriptors];
    
    // Sort find results
    NSArray*    sortedArray;
    sortedArray = [_findResults sortedArrayUsingDescriptors:descriptors];
    [_findResults release];
    _findResults = [[NSMutableArray arrayWithArray:sortedArray] retain];
    
    // Realod table
    [_findTable reloadData];
}

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

- (void)tableView:(NSTableView*)tableView 
        willDisplayCell:(id)cell 
        forTableColumn:(NSTableColumn*)tableColumn 
        row:(int)rowIndex
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Check favicon availability
    BOOL    isFaviconUsed;
    isFaviconUsed = [defaults boolForKey:SRIconUseFavicon] && [defaults boolForKey:SRIconUseFaviconSideBar];
    
    // Get column identifier
    id  identifier;
    identifier = [tableColumn identifier];
    
    if (tableView == _findTable) {
        // For results column
        if ([identifier isEqualToString:@"results"]) {
            if (isFaviconUsed) {
                // Get item
                NSDictionary*   item;
                item = [_findResults objectAtIndex:rowIndex];
                
                // Get link
                NSString*   link;
                link = [item objectForKey:@"link"];
                if (!link) {
                    link = @"";
                }
                
                // Set font
                NSNumber*   number;
                BOOL        isPreviewed = YES;
                number = [item objectForKey:@"isPreviewed"];
                if (number) {
                    isPreviewed = [number boolValue];
                }
                
                if (isPreviewed) {
                    [cell setFont:_smallSystemFont];
                }
                else {
                    [cell setFont:_smallBoldSystemFont];
                }
                
                // Set image
                [cell setImage:[[WebIconDatabase sharedIconDatabase] 
                        iconForURL:link withSize:NSMakeSize(16, 16)]];
            }
            else {
                [cell setImage:nil];
            }
            return;
        }
    }
}

- (void)tableViewSelectionDidChange:(NSNotification*)notification
{
    // Update detail view
    [self _updateDetailView];
}

// Extention method
- (NSMenu*)tableView:(NSTableView*)tableView 
        menuForEvent:(NSEvent*)event
{
    return [self _contextMenuForView:tableView event:event];
}

//--------------------------------------------------------------//
#pragma mark -- NSOutlineViewDataSource --
//--------------------------------------------------------------//

- (id)outlineView:(NSOutlineView*)outlineView 
        child:(int)index 
        ofItem:(id)item
{
    // For root
    if (!item) {
        if (_style == SRRSSFlatStyle) {
            return [_items objectAtIndex:index];
        }
        if (_style == SRRSSWebSiteStyle) {
            return [_channels objectAtIndex:index];
        }
    }
    
#if 0
    // Get type
    NSString*   type;
    type = [item objectForKey:@"type"];
    
    // For RSS channel
    if ([type isEqualToString:@"channel"]) {
        return [[item objectForKey:@"items"] objectAtIndex:index];
    }
#endif
    
    return nil;
}

- (BOOL)outlineView:(NSOutlineView*)outlineView 
        isItemExpandable:(id)item
{
#if 0
    // Get type
    NSString*   type;
    type = [item objectForKey:@"type"];
    
    // For RSS channel
    if ([type isEqualToString:@"channel"]) {
        return YES;
    }
#endif
    
    return NO;
}

- (int)outlineView:(NSOutlineView*)outlineView 
        numberOfChildrenOfItem:(id)item
{
    // For root
    if (!item) {
        if (_style == SRRSSFlatStyle) {
            return [_items count];
        }
        if (_style == SRRSSWebSiteStyle) {
            return [_channels count];
        }
    }
    
#if 0
    // Get type
    NSString*   type;
    type = [item objectForKey:@"type"];
    
    // For RSS channel
    if ([type isEqualToString:@"channel"]) {
        return [[item objectForKey:@"items"] count];
    }
#endif
    
    return 0;
}

- (id)outlineView:(NSOutlineView*)outlineView 
        objectValueForTableColumn:(NSTableColumn*)column 
        byItem:(id)item
{
    // Get identifier
    NSString*   identifier;
    identifier = [column identifier];
    
    // Get type
    NSString*   type;
    type = [item objectForKey:@"type"];
    
    // For RSS channel
    if ([type isEqualToString:@"channel"]) {
        // Count not previewed items
        NSEnumerator*   enumerator;
        NSDictionary*   childItem;
        int count = 0;
        enumerator = [[item objectForKey:@"items"] objectEnumerator];
        while (childItem = [enumerator nextObject]) {
            NSNumber*   number;
            number = [childItem objectForKey:@"isPreviewed"];
            if (number && ![number boolValue]) {
                count++;
            }
        }
        
        // For title
        if ([identifier isEqualToString:@"title"]) {
            // Return title
            NSString*   title;
            title = [item objectForKey:@"title"];
            if (count > 0) {
                title = [NSString stringWithFormat:@"%@ (%d)", title, count];
            }
            return title;
        }
        
        // For date
        if ([identifier isEqualToString:@"date"]) {
            // Return date
            NSDate* date;
            date = [item objectForKey:@"date"];
            if (!date) {
                return @"--/-- --:--";
            }
            return [date descriptionWithCalendarFormat:@"%m/%d %H:%M" timeZone:[NSTimeZone systemTimeZone] locale:nil];
        }
    }
    
    // For RSS item
    if ([type isEqualToString:@"item"]) {
        // For title
        if ([identifier isEqualToString:@"title"]) {
            // Return title
            return [item objectForKey:@"title"];
        }
        // For date
        if ([identifier isEqualToString:@"date"]) {
            // Return date
            NSDate* date;
            date = [item objectForKey:@"date"];
            if (!date) {
                return nil;
            }
            return [date descriptionWithCalendarFormat:@"%m/%d %H:%M" timeZone:[NSTimeZone systemTimeZone] locale:nil];
        }
    }
    
    return nil;
}

- (void)outlineView:(NSOutlineView*)outlineView 
        sortDescriptorsDidChange:(NSArray*)oldDescriptors
{
    // Sort RSS channels
    [self sortRSSChannels];
}

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

- (void)outlineView:(NSOutlineView*)outlineView 
        willDisplayCell:(id)cell 
        forTableColumn:(NSTableColumn*)column 
        item:(id)item
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    // Check favicon availability
    BOOL    isFaviconUsed;
    isFaviconUsed = [defaults boolForKey:SRIconUseFavicon] && [defaults boolForKey:SRIconUseFaviconSideBar];
    
    // Get column identifier
    id  identifier;
    identifier = [column identifier];
    
    // For RSS column
    if ([identifier isEqualToString:@"title"]) {
        if (isFaviconUsed) {
            // Get type
            NSString*   type;
            type = [item objectForKey:@"type"];
            
            // Get link
            NSString*   link = nil;
            
            // For channel
            if ([type isEqualToString:@"channel"]) {
                // Get link
                link = [item objectForKey:@"link"];
                
                // Set font
                NSEnumerator*   enumerator;
                NSDictionary*   childItem;
                enumerator = [[item objectForKey:@"items"] objectEnumerator];
                while (childItem = [enumerator nextObject]) {
                    NSNumber*   number;
                    number = [childItem objectForKey:@"isPreviewed"];
                    if (number && ![number boolValue]) {
                        [cell setFont:_smallBoldSystemFont];
                        break;
                    }
                }
                if (!childItem) {
                    [cell setFont:_smallSystemFont];
                }
            }
            // For item
            if ([type isEqualToString:@"item"]) {
                // Get channel
                NSString*       documentId;
                NSDictionary*   channel;
                documentId = [item objectForKey:@"documentId"];
                channel = [SRRSSManager channelWithDocumentIdentifier:documentId channels:_channels];
                
                // Get link
                link = [channel objectForKey:@"link"];
                
                // Set font
                NSNumber*   number;
                BOOL        isPreviewed = YES;
                number = [item objectForKey:@"isPreviewed"];
                if (number) {
                    isPreviewed = [number boolValue];
                }
                
                if (isPreviewed) {
                    [cell setFont:_smallSystemFont];
                }
                else {
                    [cell setFont:_smallBoldSystemFont];
                }
            }
            
            if (!link) {
                link = @"";
            }
            
            // Set image
            [cell setImage:[[WebIconDatabase sharedIconDatabase] 
                    iconForURL:link withSize:NSMakeSize(16, 16)]];
        }
        else {
            [cell setImage:nil];
        }
        return;
    }
}

- (void)outlineViewSelectionDidChange:(NSNotification*)notification
{
    [self RSSSelectedAction:self];
}

// Extention method
- (NSMenu*)outlineView:(NSOutlineView*)outlineView 
        menuForEvent:(NSEvent*)event
{
    return [self _contextMenuForView:outlineView event:event];
}

//--------------------------------------------------------------//
#pragma mark -- WebPolicyDelegate delegate --
//--------------------------------------------------------------//

- (void)webView:(WebView*)webView 
        decidePolicyForNavigationAction:(NSDictionary*)info 
        request:(NSURLRequest*)request 
        frame:(WebFrame*)frame 
        decisionListener:(id<WebPolicyDecisionListener>)listener
{
    // Get URL string
    NSURL*      URL;
    NSString*   URLString;
    URL = [request URL];
    URLString = [URL absoluteString];
    
    // Get navigation type
    int navigationType;
    navigationType = [[info objectForKey:WebActionNavigationTypeKey] intValue];
    
    // For channel scheme
    if ([URLString hasPrefix:@"channel://"]) {
        // Get document ID
        NSDictionary*   dictionary;
        NSString*       escapedDocumentId;
        NSString*       documentId = nil;
        dictionary = SRDeserializePlistFromURLFormat([URLString substringFromIndex:10]);
        escapedDocumentId = [dictionary objectForKey:@"documentId"];
        if (escapedDocumentId) {
            documentId = (NSString*)CFXMLCreateStringByUnescapingEntities(NULL, (CFStringRef)escapedDocumentId, NULL);
            [documentId autorelease];
            
            if (documentId) {
                // Get channel
                NSMutableDictionary*    channel;
                channel = [SRRSSManager channelWithDocumentIdentifier:documentId channels:_channels];
                if (channel) {
                    // Create HTML string
                    NSString*   HTMLString;
                    HTMLString = [self _channelHTMLWithChannel:channel];
                    [[_channelWebView mainFrame] loadHTMLString:HTMLString baseURL:nil];
                    
                    // Add history
                    [self addHistoryWithDocumentId:documentId itemId:nil];
                }
            }
        }
        
        return;
    }
    
    // For item scheme
    if ([URLString hasPrefix:@"item://"]) {
        // Get dcument ID and item ID
        NSDictionary*   dictionary;
        NSString*       escapedDocumentId;
        NSString*       documentId = nil;
        NSString*       escapedItemId;
        NSString*       itemId = nil;
        dictionary = SRDeserializePlistFromURLFormat([URLString substringFromIndex:7]);
        escapedDocumentId = [dictionary objectForKey:@"documentId"];
        if (escapedDocumentId) {
            documentId = (NSString*)CFXMLCreateStringByUnescapingEntities(NULL, (CFStringRef)escapedDocumentId, NULL);
            [documentId autorelease];
            escapedItemId = [dictionary objectForKey:@"itemId"];
            if (escapedItemId) {
                itemId = (NSString*)CFXMLCreateStringByUnescapingEntities(NULL, (CFStringRef)escapedItemId, NULL);
                [itemId autorelease];
            }
        }
        if (!documentId || !itemId) {
            return;
        }
        
        // Get channel and item
        NSMutableDictionary*    channel;
        NSMutableDictionary*    item;
        channel = [SRRSSManager channelWithDocumentIdentifier:documentId channels:_channels];
        item = [[SRRSSManager sharedInstance] itemWithItemId:itemId documentId:documentId];
        if (channel && item) {
            // Create HTML string
            NSString*   HTMLString;
            HTMLString = [self _itemHTMLWithChannel:channel item:item];
            [[_channelWebView mainFrame] loadHTMLString:HTMLString baseURL:nil];
            
            // Add history
            [self addHistoryWithDocumentId:documentId itemId:itemId];
            
            // Make item previewed
            NSMutableDictionary*    itemForPreviewed;
            itemForPreviewed = [SRRSSManager itemWithItemIdentifier:itemId channel:channel];
            [[SRRSSManager sharedInstance] makeItemsPreviewed:[NSArray arrayWithObject:itemForPreviewed]];
        }
        
        return;
    }
    
    // For rssitem scheme
    if ([URLString hasPrefix:@"rssitem://"]) {
        // Get dcument ID
        NSDictionary*   dictionary;
        NSString*       escapedDocumentId;
        NSString*       documentId = nil;
        dictionary = SRDeserializePlistFromURLFormat([URLString substringFromIndex:10]);
        escapedDocumentId = [dictionary objectForKey:@"documentId"];
        if (escapedDocumentId) {
            documentId = (NSString*)CFXMLCreateStringByUnescapingEntities(NULL, (CFStringRef)escapedDocumentId, NULL);
            [documentId autorelease];
        }
        if (!documentId) {
            return;
        }
        
        // Get channel
        NSMutableDictionary*    channel;
        channel = [SRRSSManager channelWithDocumentIdentifier:documentId channels:_channels];
        if (channel) {
            // Make all items previewed
            [[SRRSSManager sharedInstance] makeItemsPreviewed:[channel objectForKey:@"items"]];
        }
        
        // Open itemLink
        [_mainWindowController openURLString:documentId];
        
        return;
    }
    
    // For webitem scheme
    if ([URLString hasPrefix:@"webitem://"]) {
        // Get dcument ID, item ID and itemLink
        NSDictionary*   dictionary;
        NSString*       escapedDocumentId;
        NSString*       documentId = nil;
        NSString*       escapedItemId;
        NSString*       itemId = nil;
        NSString*       escapedItemLink;
        NSString*       itemLink = nil;
        dictionary = SRDeserializePlistFromURLFormat([URLString substringFromIndex:10]);
        escapedDocumentId = [dictionary objectForKey:@"documentId"];
        if (escapedDocumentId) {
            documentId = (NSString*)CFXMLCreateStringByUnescapingEntities(NULL, (CFStringRef)escapedDocumentId, NULL);
            [documentId autorelease];
        }
        escapedItemId = [dictionary objectForKey:@"itemId"];
        if (escapedItemId) {
            itemId = (NSString*)CFXMLCreateStringByUnescapingEntities(NULL, (CFStringRef)escapedItemId, NULL);
            [itemId autorelease];
        }
        escapedItemLink = [dictionary objectForKey:@"itemLink"];
        if (escapedItemLink) {
            itemLink = (NSString*)CFXMLCreateStringByUnescapingEntities(NULL, (CFStringRef)escapedItemLink, NULL);
            [itemLink autorelease];
        }
        if (!documentId || !itemId || !itemLink) {
            return;
        }
        
        // Get channel and item
        NSMutableDictionary*    channel;
        NSMutableDictionary*    item;
        channel = [SRRSSManager channelWithDocumentIdentifier:documentId channels:_channels];
        item = [[SRRSSManager sharedInstance] itemWithItemId:itemId documentId:documentId];
        if (channel && item) {
            // Make item previewed
            NSMutableDictionary*    itemForPreviewed;
            itemForPreviewed = [SRRSSManager itemWithItemIdentifier:itemId channel:channel];
            [[SRRSSManager sharedInstance] makeItemsPreviewed:[NSArray arrayWithObject:itemForPreviewed]];
        }
        
        // Open itemLink
        [_mainWindowController openURLString:itemLink];
        
        return;
    }
    
    // For link click
    if (navigationType == WebNavigationTypeLinkClicked) {
        // Cancel this reqeust
        [listener ignore];
        
        // Open in main window controller
        [_mainWindowController openURL:URL];
        
        return;
    }
    
    [listener use];
}

- (void)webView:(WebView*)webView 
        decidePolicyForNewWindowAction:(NSDictionary*)info 
        request:(NSURLRequest*)request 
        newFrameName:(NSString*)frameName 
        decisionListener:(id<WebPolicyDecisionListener>)listener
{
    // Get navigation type
    int navigationType;
    navigationType = [[info objectForKey:WebActionNavigationTypeKey] intValue];
    
    // For link click
    if (navigationType == WebNavigationTypeLinkClicked) {
        // Open in main window controller
        [_mainWindowController openURL:[request URL]];
        
        // Cancel this reqeust
        [listener ignore];
        
        return;
    }
    
    // Ignore anyway
    [listener ignore];
}

//--------------------------------------------------------------//
#pragma mark -- WebUIDelegate delegate --
//--------------------------------------------------------------//

- (NSArray*)webView:(WebView*)webView 
        contextMenuItemsForElement:(NSDictionary*)element 
        defaultMenuItems:(NSArray*)defaultMenuItems
{
    return nil;
}

@end
