/*
SRBookmarkShelf.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 "SRBookmarkProxy.h"
#import "SRAppControllerProxy.h"
#import "SRBrowserControllerProxy.h"

#import "SRBookmarkContextMenu.h"

#import "SRPrefDefaultKeys.h"

#import "SRBookmarkShelf.h"

#import "SRConstants.h"

static float    _javaScriptViewMinHeight = 100.0f;

@interface SRBookmarkShelf (private)

// Display style
- (void)_updateLowerView;

// NSView notification
- (void)scrollViewBondsChanged:(NSNotification*)notification;
@end

@implementation SRBookmarkShelf

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

- (id)initWithBundle:(NSBundle*)bundle
{
    self = [super initWithBundle:bundle];
    if (!self) {
        return nil;
    }
    
    // Initialize instance variables
    
    // Register notifications
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(bookmarkUpdated:) 
            name:SRBookmarkUpdated object:nil];
    [center addObserver:self selector:@selector(treeControllerAcceptedDrop:) 
            name:HMTreeControllerAcceptedDrop object:nil];
    
    return self;
}

- (void)didLoad
{
    // Create buttons
    _tableButton = [[NSClassFromString(@"HMButton") alloc] initWithFrame:NSMakeRect(0, 0, 25, 19)];
    [_tableButton setButtonType:NSMomentaryChangeButton];
    [_tableButton setBezelStyle:NSRegularSquareBezelStyle];
    [_tableButton setBordered:NO];
    [_tableButton setImage:[NSImage imageNamed:@"shelfTableView"]];
    [_tableButton setSelectedImage:[NSImage imageNamed:@"shelfPressedBackground"]];
    [_tableButton setTarget:self];
    [_tableButton setAction:@selector(selectDisplayAction:)];
    
    _browserButton = [[NSClassFromString(@"HMButton") alloc] initWithFrame:NSMakeRect(0, 0, 25, 19)];
    [_browserButton setButtonType:NSMomentaryChangeButton];
    [_browserButton setBezelStyle:NSRegularSquareBezelStyle];
    [_browserButton setBordered:NO];
    [_browserButton setImage:[NSImage imageNamed:@"shelfNodeBrowser"]];
    [_browserButton setSelectedImage:[NSImage imageNamed:@"shelfPressedBackground"]];
    [_browserButton setTarget:self];
    [_browserButton setAction:@selector(selectDisplayAction:)];
    
    _dividerViewView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, 2, 19)];
    [_dividerViewView setImage:[NSImage imageNamed:@"shelfBarDivider"]];
    
    _contextButton = [[NSClassFromString(@"HMMenuButton") alloc] initWithFrame:NSMakeRect(0, 0, 31, 19)];
    [_contextButton setButtonType:NSMomentaryChangeButton];
    [_contextButton setBezelStyle:NSRegularSquareBezelStyle];
    [_contextButton setBordered:NO];
    [_contextButton setImage:[NSImage imageNamed:@"shelfContext"]];
    [_contextButton setSelectedImage:[NSImage imageNamed:@"shelfPressedBackgroundL"]];
    [_contextButton setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
    [_contextButton setTarget:self];
    [_contextButton setDelegate:self];
    
    // Configure outline
    [_outlineView setDoubleAction:@selector(openBookmarkAction:)];
    [_outlineView setTarget:self];
    [_outlineView registerForDraggedTypes:[NSArray arrayWithObjects:
            WebURLsWithTitlesPboardType, HMTreeControllerObjectIdPboardType, nil]];
    //[_outlineView setAutosaveExpandedItems:YES];
    [_outlineView setAutoresizesOutlineColumn:NO];
    
    // Configure browser
    //[_browser _setBorderType:NSNoBorder];
    [_browser setDoubleAction:@selector(openBookmarkAction:)];
    [_browser setTarget:self];
    
    // Set image text cell
    NSTableColumn*      column;
    NSCell*             oldCell;
    id                  cell;
    column = [_outlineView tableColumnWithIdentifier:@"title"];
    oldCell = [column dataCell];
    cell = [[NSClassFromString(@"HMImageTextFieldCell") alloc] init];
    if (cell) {
        [cell setFont:[oldCell font]];
        [column setDataCell:cell];
        [cell release];
    }
    
    // Configure split view
    [_splitView setSplitType:HMSplitWide];
    
    // Configure tree controller
    NSSortDescriptor*   sortDescriptor;
    sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
    [_bookmarkTreeController setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
    [sortDescriptor release];
    [_bookmarkTreeController setIndexKeyPath:@"index"];
    [_bookmarkTreeController setDelegate:self];
    
    // Prepare content
    [_bookmarkTreeController prepareContent];
    
    // Set display style
    [self setDisplayStyle:SRShelfOutlineStyle];
    
    // Configure scroll view
    [_scrollView setPostsBoundsChangedNotifications:YES];
    [_scrollView setPostsFrameChangedNotifications:YES];
    [[NSNotificationCenter defaultCenter] 
            addObserver:self selector:@selector(scrollViewBondsChanged:) 
            name:NSViewBoundsDidChangeNotification object:_scrollView];
    [[NSNotificationCenter defaultCenter] 
            addObserver:self selector:@selector(scrollViewBondsChanged:) 
            name:NSViewFrameDidChangeNotification object:_scrollView];
    
    // Configure JavaScript view
    _javaScriptViewMinHeight = [_javaScriptView frame].size.height;
    
    // Update appearance
    [self _updateLowerView];
}

- (void)dealloc
{
    // Remove observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    [_tableButton release], _tableButton = nil;
    [_browserButton release], _browserButton = nil;
    [_dividerViewView release], _dividerViewView = nil;
    [_contextButton release], _contextButton = nil;
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- Bar buttons --
//--------------------------------------------------------------//

- (NSArray*)rightBarButtons
{
    return [NSArray arrayWithObjects:
            _tableButton, _browserButton, _dividerViewView, _contextButton, nil];
}

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

- (NSArray*)selectedBookmarks
{
    // Get selected objects
    return [_bookmarkTreeController selectedObjects];
}

- (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 bookmark under the cursor
    NSPoint point;
    int     rowUnderPoint;
    point = [view convertPoint:[event locationInWindow] 
            fromView:nil];
    rowUnderPoint = [view rowAtPoint:point];
    if (rowUnderPoint >=0) {
        if (![[view selectedRowIndexes] containsIndex:rowUnderPoint]) {
            [view selectRowIndexes:[NSIndexSet indexSetWithIndex:rowUnderPoint] 
                    byExtendingSelection:NO];
        }
    }
    
    // Get selected rows
    NSIndexSet* indexSet;
    indexSet = [view selectedRowIndexes];
    
    id<SRBookmark>  bookmark = nil;
    NSArray*        bookmarks = nil;
    
    // No bookmark is selected
    if ([indexSet count] == 0) {
        if ([view isKindOfClass:[NSOutlineView class]]) {
            [tags addObject:[NSNumber numberWithInt:SRCreateNewBookmarkFolderTag]];
        }
    }
    else {
        // Just one bookmark is selected
        if ([indexSet count] == 1) {
            // Get bookmark
            bookmark = [[self selectedBookmarks] objectAtIndex:0];
        }
        else {
            // Get bookmarks
            bookmarks = [self selectedBookmarks];
        }
        
        // Check tab availability
        BOOL    selectNewTabs;
        selectNewTabs = [defaults boolForKey:SRTabSelectNewTabs];
        
        // Case of one HTML bookmark
        if (bookmark && ![[bookmark isFolder] boolValue]) {
            // Create tag array
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkTag]];
            
            if ((selectNewTabs && !shiftFlag) || 
                (!selectNewTabs && shiftFlag))
            {
                [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewWindowTag]];
                [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewTabTag]];
            }
            else {
                [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewBackgroundWindowTag]];
                [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewBackgroundTabTag]];
            }
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInNewWindowTag]];
            
            if ([bookmark isMutable]) {
                [tags addObject:[NSNumber numberWithInt:SRDeleteBookmarkTag]];
            }
            if ([view isKindOfClass:[NSOutlineView class]]) {
                [tags addObject:[NSNumber numberWithInt:SRCreateNewBookmarkFolderTag]];
            }
        }
        // Case of one bookmark folder or multiple bookmarks
        if ((bookmark && [[bookmark isFolder] boolValue]) || bookmarks) {
            // Check deletable
            BOOL    isDeletable = NO;
            if (bookmark && [bookmark isMutable]) {
                isDeletable = YES;
            }
            if (bookmarks) {
                NSEnumerator*   enumerator;
                id<SRBookmark>  tmp;
                enumerator = [bookmarks objectEnumerator];
                while (tmp = [enumerator nextObject]) {
                    if ([tmp isMutable]) {
                        isDeletable = YES;
                        break;
                    }
                }
            }
            
            // Create tag array
            [tags addObject:[NSNumber numberWithInt:SROpenBookmarkInTabsTag]];
            if (isDeletable) {
                [tags addObject:[NSNumber numberWithInt:SRDeleteBookmarkTag]];
            }
            if ([view isKindOfClass:[NSOutlineView class]]) {
                [tags addObject:[NSNumber numberWithInt:SRCreateNewBookmarkFolderTag]];
            }
        }
    }
    
    if ([tags count] > 0) {
        // Copy menu
        NSMenu* menu;
        menu = HMCopyMenuWithTags([SRBookmarkContextMenu contextMenu], tags, self);
        
        // Set represented object
        if (bookmark) {
            [[menu itemArray] makeObjectsPerformSelector:@selector(setRepresentedObject:) 
                    withObject:bookmark];
        }
        else {
            [[menu itemArray] makeObjectsPerformSelector:@selector(setRepresentedObject:) 
                    withObject:bookmarks];
        }
        
        return menu;
    }
    
    return nil;
}

//--------------------------------------------------------------//
#pragma mark -- Display style --
//--------------------------------------------------------------//

- (void)_updateDisplayStyle
{
    // Get view frame
    NSRect  frame;
    frame = [_tabView bounds];
    
    // Decide display style
    int displayStyle = SRShelfOutlineStyle;
    if ([_tableButton isSelected]) {
        displayStyle = SRShelfOutlineStyle;
    }
    else if ([_browserButton isSelected]) {
        displayStyle = SRShelfBrowserStyle;
    }
    
    // For outline
    if (displayStyle == SRShelfOutlineStyle) {
        [_tabView selectTabViewItemWithIdentifier:@"outline"];
        [_outlineView setFrame:frame];
    }
    // For browser
    if (displayStyle == SRShelfBrowserStyle) {
        [_tabView selectTabViewItemWithIdentifier:@"browser"];
        
        frame.origin.x -= 1.0f;
        frame.origin.y -= 1.0f;
        frame.size.width += 2.0f;
        frame.size.height += 1.0f;
        [_browser setFrame:frame];
    }
}

- (void)setDisplayStyle:(int)displayStyle
{
    // Select button
    switch (displayStyle) {
    case SRShelfOutlineStyle: {
        [_tableButton setSelected:YES];
        [_browserButton setSelected:NO];
        break;
    }
    case SRShelfBrowserStyle: {
        [_tableButton setSelected:NO];
        [_browserButton setSelected:YES];
        break;
    }
    }
    
    // Update display style
    [self _updateDisplayStyle];
}

- (void)_updateLowerView
{
    // Get content size
    NSSize  contentSize;
    contentSize = [_scrollView contentSize];
    
    // Get selected bookmarks
    NSArray*    bookmarks;
    bookmarks = [self selectedBookmarks];
    if ([bookmarks count] == 0) {
        // Clear document view
        [_scrollView setDocumentView:nil];
        return;
    }
    
    // Get old view
    NSView* oldView;
    oldView = [_scrollView documentView];
    
    // For multiple view
    if ([bookmarks count] > 1) {
        return;
    }
    
    // Get selected bookmark
    id<SRBookmark>  bookmark;
    NSString*       type;
    bookmark = [bookmarks objectAtIndex:0];
    type = [bookmark type];
    
    // For HTML
    id  newView = nil;
    if ([type isEqualToString:SRBookmarkTypeHTML]) {
        newView = _bookmarkView;
    }
    
    // For folder
    if ([type isEqualToString:SRBookmarkTypeFolder] || 
        [type isEqualToString:SRBookmarkTypeBookmarkBar] || 
        [type isEqualToString:SRBookmarkTypeBookmarkMenu])
    {
        newView = _folderView;
    }
    
    // For JavaScript
    if ([type isEqualToString:SRBookmarkTypeJavaScript]) {
        newView = _javaScriptView;
    }
    
    // For smart
    if ([type isEqualToString:SRBookmarkTypeSmart]) {
        newView = _smartView;
    }
    
    // Swap view
    if (newView != oldView) {
        NSRect  frame;
        frame = [newView frame];
        frame.origin.x = 0;
        frame.origin.y = 0;
        frame.size.width = contentSize.width;
        [newView setFrame:frame];
        
        [_scrollView setDocumentView:newView];
    }
    
    // Update folder view
    if (newView == _folderView) {
        // For bookmark bar or menu
        if ([type isEqualToString:SRBookmarkTypeBookmarkBar] || 
            [type isEqualToString:SRBookmarkTypeBookmarkMenu])
        {
            // Disable editing
            [_folderTitleTextField setEditable:NO];
            [_folderAutoTabCheckBox setHidden:YES];
        }
        else {
            // Enable editing
            [_folderTitleTextField setEditable:YES];
            
            // Show auto tab checkbox
            if ([[(id<SRBookmark>)[bookmark valueForKey:@"parent"] type] 
                    isEqualToString:SRBookmarkTypeBookmarkBar])
            {
                [_folderAutoTabCheckBox setHidden:NO];
            }
            else {
                [_folderAutoTabCheckBox setHidden:YES];
            }
        }
    }
    
    // Update JavaScript view
    if (newView == _javaScriptView) {
        [self scrollViewBondsChanged:nil];
    }
}

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

- (void)bookmarkSelectedAction:(id)sender
{
    // Clear clicked row and column
    _clickedRow = NSNotFound;
    _clickedColumn = NSNotFound;
    
    // Check bookmark type
    NSArray*        bookmarks;
    id<SRBookmark>  bookmark = nil;
    NSString*       type = nil;
    bookmarks = [self selectedBookmarks];
    if ([bookmarks count] == 1) {
        bookmark = [bookmarks objectAtIndex:0];
    }
    if (bookmark) {
        type = [bookmark type];
    }
    
    if (!type || 
        [type isEqualToString:SRBookmarkTypeBookmarkBar] || 
        [type isEqualToString:SRBookmarkTypeBookmarkMenu])
    {
        return;
    }
    
    // Get previous selected row indexes
    NSIndexSet* prevRowIndexes;
    prevRowIndexes = [_outlineView previousSelectedRowIndexes];
    
    // Get selected row
    NSIndexSet* rowIndexes;
    int         row;
    rowIndexes = [_outlineView selectedRowIndexes];
    if ([rowIndexes count] != 1) {
        return;
    }
    row = [rowIndexes firstIndex];
    
    // Prepare for editing
    if ([prevRowIndexes containsIndex:row]) {
        // Keep clicked row and column
        _clickedRow = [_outlineView clickedRow];
        _clickedColumn = [_outlineView clickedColumn];
        
        // Start timer for editing
        _editingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f 
                target:self selector:@selector(_editingTimerFired:) userInfo:nil repeats:NO];
    }
}

- (void)_editingTimerFired:(NSTimer*)timer
{
    // Edit clicked cell
    if (_clickedColumn != NSNotFound || _clickedRow != NSNotFound) {
        [_outlineView editColumn:_clickedColumn row:_clickedRow withEvent:nil select:YES];
    }
    
    // Clear clicked row, column and timer
    _clickedRow = NSNotFound;
    _clickedColumn = NSNotFound;
    _editingTimer = nil;
}

- (void)openBookmarkAction:(id)sender
{
    // Get selected bookmarks
    NSArray*        bookmarks;
    id<SRBookmark>  bookmark;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        // Get selected row
        int row;
        row = [_outlineView selectedRow];
        if (row == -1) {
            return;
        }
        
        // Get item
        id  item;
        item = [_outlineView itemAtRow:row];
        
        // Toogle expansion
        if ([_outlineView isItemExpanded:item]) {
            [_outlineView collapseItem:item];
        }
        else {
            [_outlineView expandItem:item];
        }
    }
    // Other types
    else {
        // Open bookmark
        [[self browserController] openInNewTabBookmark:bookmark];
    }
}

- (void)selectDisplayAction:(id)sender
{
    // Set display style
    if (sender == _tableButton) {
        [self setDisplayStyle:SRShelfOutlineStyle];
    }
    if (sender == _browserButton) {
        [self setDisplayStyle:SRShelfBrowserStyle];
    }
}

- (void)openBookmarkInNewWindowAction:(id)sender
{
    // Get selected bookmarks
    NSArray*        bookmarks;
    id<SRBookmark>  bookmark;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        return;
    }
    
    // Open bookmark
    [[self browserController] openInNewWindowBookmark:bookmark];
}

- (void)openBookmarkInNewBackgroundTabAction:(id)sender
{
    // Get selected bookmarks
    NSArray*        bookmarks;
    id<SRBookmark>  bookmark;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        return;
    }
    
    // Open bookmark
    [[self browserController] openInNewTabBookmark:bookmark select:NO];
}

- (void)openBookmarkInNewTabAction:(id)sender
{
    // Get selected bookmarks
    NSArray*        bookmarks;
    id<SRBookmark>  bookmark;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        return;
    }
    
    // Open bookmark
    [[self browserController] openInNewTabBookmark:bookmark select:YES];
}

- (void)openBookmarkInTabsAction:(id)sender
{
    // Get selected bookmarks
    NSArray*    bookmarks;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    
    // Open bookmark
    [[self browserController] openInTabsBookmarks:bookmarks];
}

- (void)openBookmarkInNewBackgroundWindowAction:(id)sender
{
    // Get selected bookmarks
    NSArray*        bookmarks;
    id<SRBookmark>  bookmark;
    bookmarks = [self selectedBookmarks];
    if (!bookmarks || [bookmarks count] == 0) {
        return;
    }
    bookmark = [bookmarks objectAtIndex:0];
    
    // For folder
    if ([[bookmark isFolder] boolValue]) {
        return;
    }
    
    // Open bookmark
    [[self browserController] openInNewBackgroundWindowBookmark:bookmark];
}

- (void)deleteBookmarkAction:(id)sender
{
#if 0
    NSIndexSet* indexSet;
    int         row;
    indexSet = [_outlineView selectedRowIndexes];
    row = [indexSet firstIndex];
    if (row == NSNotFound) {
        return;
    }
    
    NSMutableSet*   parentNodes;
    parentNodes = [NSMutableSet set];
    do {
        id  node, parentNode;
        node = [_outlineView itemAtRow:row];
        parentNode = [node parentNode];
        
        id<SRBookmark>  bookmark, parent;
        bookmark = [node observedObject];
        parent = [parentNode observedObject];
        [parent removeBookmarks:[NSArray arrayWithObject:bookmark]];
        
        [parentNodes addObject:parentNode];
    } while ((row = [indexSet indexGreaterThanIndex:row]) != NSNotFound);
    
    [parentNodes makeObjectsPerformSelector:@selector(updateSubnodes)];
#else
    // Get selected bookmarks
    NSArray*        bookmarks;
    bookmarks = [self selectedBookmarks];
    if ([bookmarks count] == 0) {
        return;
    }
    
    // Delete bookmarks
#if 1
    id<SRBookmark>  bookmark, parent;
    bookmark = [bookmarks objectAtIndex:0];
    parent = [bookmark valueForKey:@"parent"];
    [parent removeBookmarks:[NSArray arrayWithObject:bookmark]];
#else
    NSEnumerator*   enumerator;
    id              bookmark;
    enumerator = [bookmarks objectEnumerator];
    while (bookmark = [enumerator nextObject]) {
        id<SRBookmark>  parent;
        parent = [bookmark valueForKey:@"parent"];
        [parent removeBookmarks:[NSArray arrayWithObject:bookmark]];
    }
#endif
#endif
}

- (void)createNewBookmarkFolderAction:(id)sender
{
    // Get selected bookmark
    NSArray*        bookmarks;
    id<SRBookmark>  selectedBookmark = nil;
    id<SRBookmark>  parent = nil;
    bookmarks = [self selectedBookmarks];
    if ([bookmarks count] > 0) {
        selectedBookmark = [bookmarks objectAtIndex:0];
        parent = [selectedBookmark valueForKey:@"parent"];
    }
    else {
        parent = [_appController rootBookmarkOfBrowser:SRBrowserShiira];
    }
    
    // Create folder
    id<SRBookmark>  bookmark;
    bookmark = [_bookmarkFactory folderWithTitle:NSLocalizedString(@"Untitled", nil)];
    
    // Insert bookmark
    int index;
    if (selectedBookmark) {
        index = [[parent sortedChildren] indexOfObject:selectedBookmark] + 1;
    }
    else {
        index = [[parent valueForKey:@"children"] count];
    }
    
    [parent insertBookmarks:[NSArray arrayWithObject:bookmark] atIndex:index];
}

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

- (void)scrollViewBondsChanged:(NSNotification*)notification
{
    // Get lower view
    NSView* lowerView;
    lowerView = [_scrollView documentView];
    
    // For JavaScript view
    if (lowerView == _javaScriptView) {
        NSSize  contentSize;
        contentSize = [_scrollView contentSize];
        if (contentSize.height < _javaScriptViewMinHeight) {
            contentSize.height = _javaScriptViewMinHeight;
        }
        
        NSRect  rect;
        rect = [_javaScriptView frame];
        rect.size = contentSize;
        [_javaScriptView setFrame:rect];
    }
}

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

- (void)outlineViewSelectionDidChange:(NSNotification*)notification
{
    // Update lower view
    [self _updateLowerView];
}

- (void)outlineView:(NSOutlineView*)outlineView 
        willDisplayCell:(id)cell 
        forTableColumn:(NSTableColumn*)column 
        item:(id)item
{
    // Bookmark outline
    if (outlineView == _outlineView) {
        // Get column identifier
        id  identifier;
        identifier = [column identifier];
        
        // For title column
        if ([identifier isEqualToString:@"title"]) {
            // Set image
            NSImage*    image = nil;
            if ([item respondsToSelector:@selector(observedObject)]) {
                image = [[item performSelector:@selector(observedObject)] valueForKey:@"icon"];
            }
            if (image) {
                [cell setImage:image];
            }
            
            return;
        }
    }
}

//--------------------------------------------------------------//
#pragma mark -- NSOutlineViewEx delegate --
//--------------------------------------------------------------//

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

//--------------------------------------------------------------//
#pragma mark -- HMOutlineView delegate --
//--------------------------------------------------------------//

- (void)outlineViewDeleteSelectedItem:(NSOutlineView*)outlineView
{
    [self deleteBookmarkAction:outlineView];
}

//--------------------------------------------------------------//
#pragma mark -- NSBrowser delegate --
//--------------------------------------------------------------//

- (int)browser:(NSBrowser*)browser numberOfRowsInColumn:(int)column
{
    // Browser data source is provided by Core Data
    return 0;
}

- (void)browser:(NSBrowser*)browser willDisplayCell:(id)cell atRow:(int)row column:(int)column
{
    // Get bookmark at row and column
    NSArray*    sortDescriptors;
    NSArray*    bookmarks;
    sortDescriptors = [_bookmarkTreeController sortDescriptors];
    bookmarks = [[_bookmarkTreeController content] sortedArrayUsingDescriptors:sortDescriptors];
    
    int i;
    for (i = 0; i < column; i++) {
        int selectedRow;
        selectedRow = [browser selectedRowInColumn:i];
        
        if (selectedRow > [bookmarks count]) {
            bookmarks = nil;
            break;
        }
        
        bookmarks = [[[bookmarks objectAtIndex:selectedRow] valueForKey:@"children"] allObjects];
        bookmarks = [bookmarks sortedArrayUsingDescriptors:sortDescriptors];
        if (!bookmarks) {
            break;
        }
    }
    
    if (!bookmarks || [bookmarks count] < row) {
        return;
    }
    
    id  bookmark;
    bookmark = [bookmarks objectAtIndex:row];
    
    // Set image
    [cell setImage:[bookmark valueForKey:@"icon"]];
}

//--------------------------------------------------------------//
#pragma mark -- HMMenuButton delegate --
//--------------------------------------------------------------//

- (NSMenu*)menuButton:(HMMenuButton*)menuButton menuForEvent:(NSEvent*)event
{
    return [self _contextMenuForView:_outlineView event:event];
}

//--------------------------------------------------------------//
#pragma mark -- HMSplitView delegate --
//--------------------------------------------------------------//

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

//--------------------------------------------------------------//
#pragma mark -- SRBookmark notification --
//--------------------------------------------------------------//

- (void)bookmarkUpdated:(NSNotification*)notification
{
    // Refresh tree controller
    [_bookmarkTreeController refresh];
}

//--------------------------------------------------------------//
#pragma mark -- HMTreeController delegate --
//--------------------------------------------------------------//

- (BOOL)hmTreeController:(HMTreeController*)treeController 
        outlineView:(NSOutlineView*)outlineView 
        writeItems:(NSArray*)items 
        toPasteboard:(NSPasteboard*)pboard
{
    // Get URLs and titles
    NSMutableArray* URLs;
    NSMutableArray* titles;
    NSEnumerator*   enumerator;
    id              item;
    URLs = [NSMutableArray array];
    titles = [NSMutableArray array];
    enumerator = [items objectEnumerator];
    while (item = [enumerator nextObject]) {
        // Get bookmark
        id<SRBookmark>  bookmark;
        bookmark = [item observedObject];
        if (!bookmark) {
            continue;
        }
        
        // Check bookmark bar
        if ([[bookmark type] isEqualToString:SRBookmarkTypeBookmarkBar]) {
            return NO;
        }
        
        // Get URL and title
        NSString*   URLString;
        NSURL*      URL = nil;
        NSString*   title;
        URLString = [bookmark urlString];
        if (URLString) {
            URL = [NSURL URLWithString:URLString];
        }
        title = [bookmark title];
        
        if (URL && title) {
            [URLs addObject:URL];
            [titles addObject:title];
        }
    }
    
    // Write URLs and titles
    if ([URLs count] > 0) {
        [pboard addTypes:[NSArray arrayWithObject:WebURLsWithTitlesPboardType] owner:nil];
        [WebURLsWithTitles writeURLs:URLs andTitles:titles toPasteboard:pboard];
        
        return YES;
    }
    
    return [items count] > 0;
}

- (NSDragOperation)hmTreeController:(HMTreeController*)treeController 
        outlineView:(NSOutlineView*)outlineView 
        validateDrop:(id <NSDraggingInfo>)info 
        proposedItem:(id)item 
        proposedChildIndex:(int)index
{
    // For root
    if (!item) {
        // Do not allow above bookmarks bar
        if (index == 0) {
            return NSDragOperationNone;
        }
    }
    
    return NSDragOperationMove;
}

//--------------------------------------------------------------//
#pragma mark -- HMTreeController notification --
//--------------------------------------------------------------//

- (void)treeControllerAcceptedDrop:(NSNotification*)notification
{
    // Get tree controller
    HMTreeController*   treeController;
    treeController = [notification object];
    if (treeController == _bookmarkTreeController) {
        return;
    }
    
    // Check entity name
    if (![[treeController entityName] isEqual:[_bookmarkTreeController entityName]]) {
        return;
    }
    
    // Refresh tree controller
    [_bookmarkTreeController refresh];
}

- (id)hmTreeControllerRootObject:(HMTreeController*)treeController 
{
    return [_appController rootBookmarkOfBrowser:SRBrowserShiira];
}

@end
