/*
AppKitEx.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 "SRAppDelegate.h"
#import "SRMainWindowController.h"
#import "SRBookmarkStorage.h"
#import "AppKitEx.h"

@implementation NSAttributedString (Truncate)

- (NSAttributedString*)truncateForWidth:(int)inWidth
{
    static NSString*    _horizontalEllipsis = nil;
    if (!_horizontalEllipsis) {
        unichar uni[1];
        uni[0] = 0x2026;
        _horizontalEllipsis = [[NSString stringWithCharacters:uni length:1] retain];
    }
    
    NSAttributedString *result = self;
    if ([self size].width > inWidth) {
        NSMutableAttributedString *newString;
        newString = [[NSMutableAttributedString alloc] initWithAttributedString:self];
        [newString autorelease];
        
        int curLength = [self length] - 1; // start by chopping off at least one
        
        while ([newString size].width > inWidth) {
            NSRange range;
            range = NSMakeRange(curLength - 1, 2); // replace 2 characters with '…'
            [newString replaceCharactersInRange:range withString:_horizontalEllipsis];
            curLength--;
        }
        result = newString;
    }
    return result;
}

@end

@implementation NSAttributedString (Attachement)

- (void)_removeAttachmentFrom:(NSMutableAttributedString*)attrStr
{
    // Get range of attacment
    id      attr;
    NSRange range;
    attr = [attrStr attribute:NSAttachmentAttributeName 
            atIndex:0 
            effectiveRange:&range];
    if (range.length <= 0) {
        return;
    }
    
    // Get range again
    if (!attr) {
        attr = [attrStr attribute:NSAttachmentAttributeName 
                atIndex:range.location 
                effectiveRange:&range];
        if (range.length <= 0 || !attr) {
            return;
        }
    }
    
    // Delete attachment
    [attrStr deleteCharactersInRange:range];
}

- (NSString*)stringWithoutAttachment
{
    NSMutableAttributedString*  attrStr;
    attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:self];
    
    while ([attrStr containsAttachments]) {
        [self _removeAttachmentFrom:attrStr];
    }
    
    return [attrStr string];
}

@end

#pragma mark -

@implementation NSOutlineView (ContextMenu)

- (NSMenu*)menuForEvent:(NSEvent*)event
{
    if ([[self delegate] respondsToSelector:@selector(outlineView:menuForEvent:)]) {
        return [[self delegate] outlineView:self menuForEvent:event];
    }
    
    return nil;
}

- (void)draggedImage:(NSImage*)image 
        endedAt:(NSPoint)point 
        operation:(NSDragOperation)operation
{
    if ([[self delegate] respondsToSelector:@selector(draggedImage:endedAt:operation:)]) {
        [[self delegate] draggedImage:image endedAt:point operation:operation];
    }
}

@end

#pragma mark -

@implementation NSTableView (ContextMenu)

- (NSMenu*)menuForEvent:(NSEvent*)event
{
    if ([[self delegate] respondsToSelector:@selector(tableView:menuForEvent:)]) {
        return [[self delegate] tableView:self menuForEvent:event];
    }
    
    return nil;
}

@end

#pragma mark -

@implementation NSTextViewEx

+ (void)load
{
    [self poseAsClass:[NSTextView class]];
}

- (void)keyDown:(NSEvent*)event
{
    // For Java text area
    if (![NSStringFromClass([event class]) isEqualToString:@"SyntheticEvent"]) {
        // Get modifier flags and key code
        unsigned    modifierFlags;
        unsigned    optFlag;
        short       keyCode;
        modifierFlags = [event modifierFlags];
        optFlag = modifierFlags & NSAlternateKeyMask;
        keyCode = [event keyCode];
        
        // For opt + enter
        if (optFlag && keyCode == 0x24 /* enter */) {
            event = [NSEvent keyEventWithType:[event type] 
                    location:[event locationInWindow] 
                    modifierFlags:0 
                    timestamp:[event timestamp] 
                    windowNumber:[event windowNumber] 
                    context:[event context] 
                    characters:[event charactersIgnoringModifiers] 
                    charactersIgnoringModifiers:[event charactersIgnoringModifiers] 
                    isARepeat:[event isARepeat] 
                    keyCode:[event keyCode]];
        }
    }
    
    [super keyDown:event];
}

@end

#pragma mark -

@implementation NSToolbar (ToolbarItem)

- (NSToolbarItem*)toolbarItemWithIdentifier:(id)identifier
{
    NSArray*        items;
    NSEnumerator*   enumerator;
    NSToolbarItem*  item;
    items = [self items];
    enumerator = [items objectEnumerator];
    while (item = [enumerator nextObject]) {
        if ([[item itemIdentifier] isEqual:identifier]) {
            return item;
        }
    }
    
    return nil;
}

@end

#pragma mark -

@implementation NSToolbarItemViewerEx

+ (void)load
{
    [self poseAsClass:[NSToolbarItemViewer class]];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    [super dealloc];
}

- (BOOL)_hasMenu
{
    // Create array of identifier which have menu
    static NSArray* _menuItemIdentifiers = nil;
    if (!_menuItemIdentifiers) {
        _menuItemIdentifiers = [[NSArray arrayWithObjects:
            SRGoBackIdentifier, SRGoForwardIdentifier, nil] retain];
    }
    
    // Check identifier
    id  item = nil;
    id  itemIdentifier = nil;
    if ([self respondsToSelector:@selector(item)]) {
        item = [self item];
        if ([item respondsToSelector:@selector(itemIdentifier)]) {
            itemIdentifier = [item itemIdentifier];
        }
    }
    if (![_menuItemIdentifiers containsObject:itemIdentifier]) {
        return NO;
    }
    
    // Check is enabled
    if (![item isEnabled]) {
        return NO;
    }
    
    return YES;
}

- (NSMenu*)_menu
{
    // Get identifier
    id  itemIdentifier = nil;
    if ([self respondsToSelector:@selector(item)]) {
        id  item;
        item = [self item];
        if ([item respondsToSelector:@selector(itemIdentifier)]) {
            itemIdentifier = [item itemIdentifier];
        }
    }
    if (!itemIdentifier) {
        return nil;
    }
    
    // Get web view
    id          windowController;
    WebView*    webView = nil;
    windowController = [[self window] windowController];
    if ([windowController respondsToSelector:@selector(selectedWebView)]) {
        webView = [windowController selectedWebView];
    }
    if (!webView) {
        return nil;
    }
    
    // Create menu
    NSMenu* menu = nil;
    // For back menu
    if ([itemIdentifier isEqualToString:SRGoBackIdentifier]) {
        // Get back list
        NSArray*    list;
        list = [[webView backForwardList] backListWithLimit:50];
        
        // Make back menu
        menu = SRCreateHistoryMenu(list, YES);
        if ([menu numberOfItems] == 0) {
            menu = nil;
        }
    }
    // For forward menu
    if ([itemIdentifier isEqualToString:SRGoForwardIdentifier]) {
        // Get forward list
        NSArray*    list;
        list = [[webView backForwardList] forwardListWithLimit:50];
        
        // Make forward menu
        menu = SRCreateHistoryMenu(list, NO);
        if ([menu numberOfItems] == 0) {
            menu = nil;
        }
    }
    
    return menu;
}

- (NSMenu*)menuForEvent:(NSEvent*)event
{
    if (![self _hasMenu]) {
        return [super menuForEvent:event];
    }
    
    return [self _menu];
}

- (void)mouseDown:(NSEvent*)event
{
    // Check has menu
    if (![self _hasMenu]) {
        [super mouseDown:event];
        return;
    }
    
    // Highlight itself
    if ([self respondsToSelector:@selector(_setHighlighted:displayNow:)]) {
        [self _setHighlighted:YES displayNow:YES];
    }
    
    // Get mouse location
    NSPoint mouseLoc;
    mouseLoc = [self convertPoint:[event locationInWindow] fromView:nil];
    
    // Wait next event a moment
    NSEvent*    waitingEvent;
    NSRect      offsetRect;
    NSDate*     expireDate;
    offsetRect = NSMakeRect(mouseLoc.x - 2.0, mouseLoc.y - 2.0, 5.0, 5.0);
    expireDate = [NSDate dateWithTimeIntervalSinceNow:0.3];
    
    while (YES) {
        waitingEvent = [NSApp nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask) 
                untilDate:expireDate 
                inMode:NSEventTrackingRunLoopMode 
                dequeue:YES];
        
        // If timer is expired, show context menu
        if (!waitingEvent) {
            break;
        }
        // If mouse up is detected, do mouse up immediately
        else if ([waitingEvent type] == NSLeftMouseUp) {
            [NSApp postEvent:waitingEvent atStart:YES];
            [super mouseDown:event];
            
            // Make highlight off
            if ([self respondsToSelector:@selector(_setHighlighted:displayNow:)]) {
                [self _setHighlighted:NO displayNow:YES];
            }
            
            return;
        }
        // Permit mouse movement up to 3 pixels
        else if ([waitingEvent type] == NSLeftMouseDragged) {
            NSPoint currentMouseLoc;
            currentMouseLoc = [self convertPoint:[waitingEvent locationInWindow] fromView:nil];
            if (!NSPointInRect(currentMouseLoc, offsetRect)) {
                [NSApp postEvent:waitingEvent atStart:YES];
                [super mouseDown:event];
                
                // Make highlight off
                if ([self respondsToSelector:@selector(_setHighlighted:displayNow:)]) {
                    [self _setHighlighted:NO displayNow:YES];
                }
                
                return;
            }
        }
    }
    
    //
    // Show poup menu
    //
    
    // Get menu
    NSMenu* menu;
    menu = [self _menu];
    if (!menu) {
        [super mouseDown:event];
        return;
    }
    
    // Move event location
    NSPoint     popupPoint;
    NSEvent*    popupEvent;
    popupPoint = [self convertPoint:NSMakePoint(0, 0) toView:nil];
    popupEvent = [NSEvent mouseEventWithType:[event type] 
            location:popupPoint 
            modifierFlags:[event modifierFlags] 
            timestamp:[event timestamp] 
            windowNumber:[event windowNumber] 
            context:[event context] 
            eventNumber:[event eventNumber] 
            clickCount:[event clickCount] 
            pressure:[event pressure]];
    
    // Register notification for menu
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self 
            selector:@selector(_menuDidEndTracking:) 
            name:NSMenuDidEndTrackingNotification 
            object:menu];
    
    // Popup context menu
    [NSMenu popUpContextMenu:menu withEvent:popupEvent forView:self];
}

- (void)_menuDidEndTracking:(NSNotification*)notification
{
    NSNotificationCenter*   center;
    center = [NSNotificationCenter defaultCenter];
    
    // Remove notification
    [center removeObserver:self 
            name:NSMenuDidEndTrackingNotification 
            object:[notification object]];
    
    // Make highlight off
    if ([self respondsToSelector:@selector(_setHighlighted:displayNow:)]) {
        [self _setHighlighted:NO displayNow:YES];
    }
}

@end

#pragma mark -

@implementation NSToolbarViewEx

+ (void)load
{
    [self poseAsClass:[NSToolbarView class]];
}

- (NSMenu*)menuForEvent:(NSEvent*)event
{
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    NSMenu* menu;
    menu = [super menuForEvent:event];
    
    id<NSMenuItem>  item;
    
    // Check toolbar display mode
    NSToolbarDisplayMode    displayMode;
    displayMode = [_toolbar displayMode];
    if (displayMode != 0) {
        item = [menu itemWithTag:displayMode];
        if (item && [item state] != NSOnState) {
            [item setState:NSOnState];
        }
    }
    
    // Check toolbar size mode
    NSToolbarSizeMode   sizeMode;
    sizeMode = [_toolbar sizeMode];
    if (sizeMode == NSToolbarSizeModeSmall) {
        item = [menu itemAtIndex:4]; // Menu item for size mode
        if (item && [item state] != NSOnState) {
            [item setState:NSOnState];
        }
    }
    
#if 0
    // For main window toolbar
    if ([[_toolbar identifier] isEqualToString:@"SRMainWindowToolbarIdentifier"]) {
        // Add 'Use small URL and search field'
        [menu addItem:[NSMenuItem separatorItem]];
        
        item = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Use small URL and search field", nil) 
                action:@selector(toggleSmallURLAndSearchFieldAction:) 
                keyEquivalent:@""];
        [item autorelease];
        [item setTarget:[_toolbar delegate]];
        
        if ([defaults boolForKey:SRToolbarUseSmallURLAndSearchField]) {
            [item setState:NSOnState];
        }
        
        [menu addItem:item];
    }
#endif
    
    return menu;
}

@end

#pragma mark -

@implementation NSView (Image)

- (NSImage*)viewImage
{
    // Get frame
    NSRect  frame;
    frame = [self frame];
    
    // Create image rep
    NSBitmapImageRep*   imageRep = nil;
    
    // For 10.4
    if ([self respondsToSelector:@selector(bitmapImageRepForCachingDisplayInRect:)]) {
        imageRep = [self bitmapImageRepForCachingDisplayInRect:frame];
        [self cacheDisplayInRect:frame toBitmapImageRep:imageRep];
    }
    else {
        if ([self window]) {
            [self lockFocus];
            imageRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:frame];
            [self unlockFocus];
        }
    }
    
    if (!imageRep) {
        return nil;
    }
    
    // Create iamge
    NSImage*    image;
    image = [[NSImage alloc] initWithSize:frame.size];
    [image autorelease];
    [image lockFocus];
    [imageRep drawInRect:NSMakeRect(0, 0, frame.size.width, frame.size.height)];
    [image unlockFocus];
    
    return image;
}

- (NSImageView*)viewImageView
{
    // Get image
    NSImage*    image;
    image = [self viewImage];
    if (!image) {
        return nil;
    }
    
    // Get frame
    NSRect  frame;
    frame = [self frame];
    
    // Create image view
    NSImageView*    imageView;
    imageView = [[NSImageView alloc] initWithFrame:NSMakeRect(0, 0, frame.size.width, frame.size.height)];
    [imageView autorelease];
    [imageView setImage:image];
    
    return imageView;
}

@end

#pragma mark -

@implementation NSWindowEx : NSWindow

+ (void)load
{
    [self poseAsClass:[NSWindow class]];
}

- (void)closeWindowAction:(id)sender
{
    // Pass to delegate
    id  delegate;
    delegate = [self delegate];
    if (delegate && [delegate respondsToSelector:_cmd]) {
        [delegate performSelector:_cmd withObject:sender];
        return;
    }
    
    // Close window
    [self performClose:self];
}

- (void)closeTabAction:(id)sender
{
    // Pass to delegate
    id  delegate;
    delegate = [self delegate];
    if (delegate && [delegate respondsToSelector:_cmd]) {
        [delegate performSelector:_cmd withObject:sender];
        return;
    }
    
    // Do nothing in NSWindow
}

- (BOOL)validateMenuItem:(id<NSMenuItem>)menuItem
{
    // Get tag
    int tag;
    tag = [menuItem tag];
    
    // User defaults
    NSUserDefaults* defaults;
    defaults = [NSUserDefaults standardUserDefaults];
    
    switch (tag) {
    // File menu
    case SRCloseWindowTag: {
        // Prioritize SRMainWindowController
        id  windowController;
        windowController = [self windowController];
        if (windowController && [windowController isKindOfClass:[SRMainWindowController class]]) {
            return [windowController validateMenuItem:menuItem];
        }
        
        [menuItem setKeyEquivalent:@"w"];
        [menuItem setKeyEquivalentModifierMask:NSCommandKeyMask];
        return YES;
    }
    case SRCloseTabTag: {
        // Prioritize SRMainWindowController
        id  windowController;
        windowController = [self windowController];
        if (windowController && [windowController isKindOfClass:[SRMainWindowController class]]) {
            return [windowController validateMenuItem:menuItem];
        }
        
        [menuItem setKeyEquivalent:@""];
        return NO;
    }
    }
    
    return YES;
}

@end
