// Copyright 1997-2000 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OHHTMLPageView.h"

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>
#import <OmniAppKit/OmniAppKit.h>
#import <OWF/OWF.h>

#import "OHColorPalette.h"
#import "OHDownloader.h"
#import "OHHTMLAnchor.h"
#import "OHHTMLDocument.h"
#import "OHViewCell.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/View.subproj/OHHTMLPageView.m,v 1.26 2000/01/20 00:28:04 kc Exp $")

@interface OHHTMLPageView (Private)
// - (void)resize;
- (void)updateLinkColors;
- (void)deactivate;
- (void)saveAllLinkedDocumentsWithContentTypePrefix:(NSString *)contentTypePrefix excludeContentTypePrefix:(NSString *)excludeContentTypePrefix;
@end

@implementation OHHTMLPageView

- (id)initWithHTMLOwner:(OHHTMLOwner *)anHTMLOwner;
{
    if (![super initWithHTMLOwner:anHTMLOwner])
	return nil;

    nonretainedFrameView = nil;
    links = [[NSMutableArray alloc] init];
    namedAnchors = [[NSMutableArray alloc] init];
    selectedAnchor = nil;
    backgroundImageAddress = nil;
    backgroundOmniImage = nil;
    backgroundImage = nil;
    flags.usesOwnBackgroundColor = YES;
    textContainerInset = NSMakeSize(8.0, 8.0); // Netscape 4.0 Macintosh compatibility. Change this and I kill you. -wjs

    [self setBackgroundColor:[[OHColorPalette defaultPalette] backgroundColor]];

    return self;
}

- (void)dealloc;
{
    [links release];
    [namedAnchors release];
    [selectedAnchor release];
    [[NSNotificationCenter defaultCenter] removeObserver:self];

    [super dealloc];
}


// Actions

- (IBAction)saveAllLinkedDocuments:(id)sender;
{
    [self saveAllLinkedDocumentsWithContentTypePrefix:nil excludeContentTypePrefix:@"image/"];
}

- (IBAction)saveAllLinkedImages:(id)sender;
{
    [self saveAllLinkedDocumentsWithContentTypePrefix:@"image/" excludeContentTypePrefix:nil];
}

- (IBAction)startAnimations:(id)sender;
{
    [[(OHHTMLDocument *)nonretainedHTMLOwner images] makeObjectsPerformSelector:@selector(startAnimation)];
}

- (IBAction)stopAnimations:(id)sender;
{
    [[(OHHTMLDocument *)nonretainedHTMLOwner images] makeObjectsPerformSelector:@selector(stopAnimation)];
}

// Selecting anchors

- (void)selectAnchor:(OHHTMLAnchor *)anAnchor;
{
    [self selectAnchor:anAnchor displayInAddressWell:YES];
}

- (void)selectAnchor:(OHHTMLAnchor *)anAnchor displayInAddressWell:(BOOL)shouldDisplay;
{
    if (anAnchor != selectedAnchor) {
        if (selectedAnchor) {
            OHHTMLAnchor *oldSelectedAnchor = selectedAnchor;

            selectedAnchor = nil;
            [[oldSelectedAnchor htmlView] setNeedsDisplayInRect:NSInsetRect([oldSelectedAnchor containingRect], -5.0, -5.0)];
            [oldSelectedAnchor release];
        }

        selectedAnchor = [anAnchor retain];
        if (selectedAnchor) {
            [[selectedAnchor htmlView] setNeedsDisplayInRect:NSInsetRect([selectedAnchor containingRect], -5.0, -5.0)];
        }
    }

    if (shouldDisplay)
        [nonretainedFrameView documentViewSelectionDidChange];
}

- (OHHTMLAnchor *)selectedAnchor;
{
    return selectedAnchor;
}

- (void)followAnchor:(OHHTMLAnchor *)anchor withModifierFlags:(unsigned int)eventFlags;
{
    [anchor setColorToIndex:OHColorPaletteActiveLinkIndex];

    if (eventFlags & NSCommandKeyMask) {
        // The user wants to initiate a download
        [OHDownloader downloadAddressAndPromptForFilename:[anchor address]];
    } else if (eventFlags & NSAlternateKeyMask) {
        // The user wants to select the anchor but not follow it
        [self selectAnchor:anchor];
    } else {
        // The user wants to follow the link
        [self displayDocumentAtAddress:[anchor address]];
    }
}


// Adding links and named anchors

- (void)addLink:(OHHTMLAnchor *)aLink;
{
    [links addObject:aLink];
}

- (void)addNamedAnchor:(OHHTMLAnchor *)aNamedAnchor;
{
    [namedAnchors addObject:aNamedAnchor];
}

- (NSArray *)links
{
    return links;
}

- (NSArray *)namedAnchors
{
    return namedAnchors;
}

// NSResponder subclass

- (void)keyDown:(NSEvent *)theEvent;
{
    [nonretainedFrameView documentViewKeyDown:theEvent];
}


// NSView subclass

- (void)drawRect:(NSRect)rect;
{
    // Draw the background
    [self drawBackgroundForRect:rect backgroundOrigin:NSZeroPoint];
    [super drawRect:rect];
}

- (BOOL)knowsPagesFirst:(int *)firstPageNum last:(int *)lastPageNum
{
    // Override NSTextView's behavior
    return NO;
}

/*
- (void)resizeWithOldSuperviewSize:(NSSize)oldSize;
{
    [self resize];
}
*/

// OHHTMLView subclass

- (OHHTMLPageView *)htmlPageView;
{
    return self;
}

- (void)laidOutContentsAtSize:(NSSize)contentSize;
{
    NSSize pageSize, newSize;

    // Height should be maximum of last line laid out plus the inset--or the visible height
    pageSize = [self pageSize]; // This is based on our superview's visible rect

    // We only count the textContainerInset once because we don't care about being able to scroll to see the inset of the right margin
    newSize.height = MAX(contentSize.height + textContainerInset.height, pageSize.height);
    newSize.width = MAX(contentSize.width + textContainerInset.width, pageSize.width);

    // TODO: To be safe, we ought to convert newSize into our parent view's coordinate system.
    if (!NSEqualSizes([self frame].size, newSize)) {
        [self setFrameSize:newSize relayout:NO];
    }
}

- (OWContentInfo *)parentContentInfo;
{
    return [(OHHTMLDocument *)nonretainedHTMLOwner contentInfo];
}


// OHDocumentView protocol

- (void)setFrameView:(id <OHDocumentFrame>)aFrame;
{
    nonretainedFrameView = aFrame;
    if (!nonretainedFrameView)
        [self deactivate];
}

- (id <OHDocumentFrame>)frameView;
{
    return [[nonretainedFrameView retain] autorelease];
}

- (BOOL)wantsScrollers;
{
    return YES;
}

// OHOptionalDocumentView protocol

- (IBAction)saveAs:(id)sender;
{
    NSString *filename;

    filename = [OHDownloader runSavePanelOnAddress:(OWAddress *)[[(OHHTMLDocument *)nonretainedHTMLOwner contentInfo] address] asFileType:@"rtfd"];
    if (!filename)
        return;

    [[textStorage RTFDFileWrapperFromRange:NSMakeRange(0, [textStorage length]) documentAttributes:nil] writeToFile:filename atomically:YES updateFilenames:NO];
}

- (OWAddress *)selectedAddress;
{
    return [[self selectedAnchor] address];
}

- (void)selectNextLink;
{
    OHHTMLAnchor *newAnchor;
    unsigned int anchorIndex, anchorCount;

#warning WJS: We should ask the next frame to select its first link if we wrap.
// Possibly by telling our containing frame to select link in the next frame

    anchorCount = [links count];
    if (anchorCount == 0)
        return;
    anchorIndex = [links indexOfObjectIdenticalTo:selectedAnchor];

    if (anchorIndex == NSNotFound || anchorIndex >= anchorCount - 1)
        anchorIndex = 0;
    else
        anchorIndex++;

    newAnchor = [links objectAtIndex:anchorIndex];
    [self selectAnchor:newAnchor];
    [newAnchor scrollToVisible];
}

- (void)selectPreviousLink;
{
    OHHTMLAnchor *newAnchor;
    unsigned int anchorIndex, anchorCount;

    anchorCount = [links count];
    if (anchorCount == 0)
        return;
    anchorIndex = [links indexOfObjectIdenticalTo:selectedAnchor];
    if (anchorIndex == NSNotFound || anchorIndex == 0)
        anchorIndex = anchorCount - 1;
    else
        anchorIndex--;

    newAnchor = [links objectAtIndex:anchorIndex];
    [self selectAnchor:newAnchor];
    [newAnchor scrollToVisible];
}

- (void)selectFirstKeyView;
{
    NSView *formElement = [[[nonretainedHTMLOwner htmlDocument] firstFormKeyElement] contentView];
    if (formElement) {
        [[self window] makeFirstResponder:formElement];
        [formElement scrollRectToVisible:[formElement bounds]];
    } else {
        NSBeep();
    }
}

- (void)selectLastKeyView;
{
    NSView *formElement = [[[nonretainedHTMLOwner htmlDocument] lastFormKeyElement] contentView];
    if (formElement) {
        [[self window] makeFirstResponder:formElement];
        [formElement scrollRectToVisible:[formElement bounds]];
    } else {
        NSBeep();
    }
}

- (BOOL)followSelectedAnchor;
{
    if (!selectedAnchor || ![selectedAnchor address])
        return NO;

    // Follow the selected anchor
    [selectedAnchor followFromEvent:[NSApp currentEvent]];
    return YES;
}

- (void)scrollToAnchorNamed:(NSString *)name;
{
    int anchorIndex, anchorCount;

    anchorCount = [namedAnchors count];
    for (anchorIndex = 0; anchorIndex < anchorCount; anchorIndex++) {
        OHHTMLAnchor *anAnchor;

        anAnchor = [namedAnchors objectAtIndex:anchorIndex];
        if ([name isEqualToString:[anAnchor name]]) {
            [anAnchor scrollToTop];
            return;
        }
    }

    // Netscape bug emulation: if we don't find an anchor, we scroll to top.
    [self scrollPoint:NSZeroPoint];
}

- (void)noDisplaySelectAnchorWithAddress:(OWAddress *)address;
{
    NSEnumerator *anchorEnumerator;
    OHHTMLAnchor *anAnchor;

    if (!address) {
        [self selectAnchor:nil displayInAddressWell:NO];
        return;
    }

    anchorEnumerator = [links objectEnumerator];
    while ((anAnchor = [anchorEnumerator nextObject])) {
        if ([address isEqual:[anAnchor address]]) {
            [self selectAnchor:anAnchor displayInAddressWell:NO];
            return;
        }
    }
    [self selectAnchor:nil displayInAddressWell:NO];
}

- (void)documentDisplayedInNewFrame;
{
    // TODO Sure seems like we do these in the wrong order
    [self relayout];
    [self updateLinkColors];
}

// Messages passed to our frame

- (void)displayDocumentAtAddress:(OWAddress *)address;
{
    [nonretainedFrameView displayDocumentAtAddress:address];
}

// Informal OmniFindControllerAware protocol

- (id <OAFindControllerTarget>)omniFindControllerTarget;
{
    return (id <OAFindControllerTarget>)self;
}

// OAFindControllerTarget protocol

- (BOOL)findString:(NSString *)textPattern ignoreCase:(BOOL)ignoreCase backwards:(BOOL)backwards wrap:(BOOL)wrap;
{
    if ([self findString:textPattern ignoreCase:ignoreCase backwards:backwards ignoreSelection:NO])
        return YES;

    if (!wrap)
        return NO;

    // Try again, ignoring the selection and searching from one end or the other.
    return [self findString:textPattern ignoreCase:ignoreCase backwards:backwards ignoreSelection:YES];
}


// NSMenuActionResponder informal protocol

- (BOOL)validateMenuItem:(NSMenuItem *)item
{
    if ([item action] == @selector(saveAs:)) {
        [item setTitle:@"Save As RTFD..."];
        return YES;
    }
    return [super validateMenuItem:item];
}

// NSObject subclass

- (NSMutableDictionary *)debugDictionary;
{
    NSMutableDictionary *debugDictionary;

    debugDictionary = [super debugDictionary];

    if (links)
        [debugDictionary setObject:links forKey:@"links"];
    if (namedAnchors)
        [debugDictionary setObject:namedAnchors forKey:@"namedAnchors"];
    if (selectedAnchor)
        [debugDictionary setObject:selectedAnchor forKey:@"selectedAnchor"];
    if (backgroundImageAddress)
        [debugDictionary setObject:backgroundImageAddress forKey:@"backgroundImageAddress"];
    if (backgroundImage)
        [debugDictionary setObject:backgroundImage forKey:@"backgroundImage"];

    return debugDictionary;
}

@end

@implementation OHHTMLPageView (Private)

/*
- (void)resize;
{
    NSView *superview;
    NSSize contentSize, frameSize, newSize;
    NSRect originalFrame, visibleRect;
    BOOL oldPostsFrameChangedNotifications;

    superview = [self superview];
    if (!superview)
        return;

    // Turn off frame changed notifications (saving the old state)
    oldPostsFrameChangedNotifications = [self postsFrameChangedNotifications];
    [self setPostsFrameChangedNotifications:NO];

    originalFrame = [self frame];

    // We're going to size ourselves to at least fill the superview
    newSize = [superview bounds].size;

    // Make sure we're big enough for our content
    contentSize.width = [self widthRange].minimum;
    if (newSize.width < contentSize.width)
        newSize.width = contentSize.width;

    // Set our width (assuming it changed at all)
    if (NSWidth(originalFrame) != newSize.width)
        [self setWidth:newSize.width];

    // Calculate the new height

    // Save off the frameSize and visibleRect before calling -heightForCurrentWidth, because it will change them
    frameSize = [self frame].size;
    visibleRect = [self visibleRect];

    // Calculate the height of our content
    contentSize.height = [self heightForCurrentWidth];

    // Make sure we're big enough for our content
    if (newSize.height < contentSize.height)
        newSize.height = contentSize.height;

    // Set our height (if necessary), then scroll to restore the origin to what it was
    if (!NSEqualSizes([self frame].size, newSize)) {
        [self setFrameSize:newSize relayout:NO];
        [self scrollPoint:visibleRect.origin];
    }

    if (oldPostsFrameChangedNotifications) {
        // Turn frame changed notifications back on
        [self setPostsFrameChangedNotifications: oldPostsFrameChangedNotifications];

        // Post a notification if the frame has changed since we turned them off
        if (!NSEqualRects(originalFrame, [self frame]))
            [[NSNotificationCenter defaultCenter] postNotificationName:NSViewFrameDidChangeNotification object:self];
    }
}
*/

- (void)updateLinkColors;
{
    unsigned int linkIndex, linkCount;

    linkCount = [links count];
    for (linkIndex = 0; linkIndex < linkCount; linkIndex++)
        [[links objectAtIndex:linkIndex] updateColor];
}

- (void)deactivate;
{
    // TODO: Kill timed refreshes, stillborn pipelines:
    //
    // [aPipeline state] == PipelineInit ||
    // [aPipeline contextObjectForKey:@"AbortWhenInactive"]

    [self stopAnimations:nil];
    [nonretainedHTMLOwner documentDidDeactivate:self];
}

- (void)saveAllLinkedDocumentsWithContentTypePrefix:(NSString *)contentTypePrefix excludeContentTypePrefix:(NSString *)excludeContentTypePrefix;
{
    NSOpenPanel *openPanel;
    unsigned int linkIndex, linkCount;
#ifdef YELLOW_BOX
    BOOL cancelButtonPressed;
    static BOOL alreadyPrompted = NO;
#endif
    NSString *directoryPath;

    openPanel = [NSOpenPanel openPanel];
    [openPanel setPrompt:@"Save into Directory"];
    [openPanel setCanChooseDirectories:YES];
    [openPanel setCanChooseFiles:NO];
#ifdef YELLOW_BOX
    // Yellow Box automatically remembers your last save directory if you pass in @"", OpenStep does not.
    cancelButtonPressed = [openPanel runModalForDirectory:alreadyPrompted ? nil:[OHDownloader downloadDirectoryPath] file:nil types:nil] == NSCancelButton;
    alreadyPrompted = YES;
    if (cancelButtonPressed)
        return;
#else
    if ([openPanel runModalForDirectory:[OHDownloader downloadDirectoryPath] file:nil types:nil] == NSCancelButton)
        return;
#endif
    directoryPath = [openPanel filename];

    // TODO: Something that might be cool to do would be to list all of the documents we're considering saving and let the user select which ones they really want.

    linkCount = [links count];
    for (linkIndex = 0; linkIndex < linkCount; linkIndex++) {
        OHHTMLAnchor *link;
        OWAddress *address;
        OWContentType *expectedContentType;

        link = [links objectAtIndex:linkIndex];
        address = [link address];
        expectedContentType = [OWContentType contentTypeForFilename:[[address url] path]];

        if (excludeContentTypePrefix && [[expectedContentType contentTypeString] hasPrefix:excludeContentTypePrefix])
            continue; // Matches the excluded content type prefix, skip it

        if (!contentTypePrefix || [[expectedContentType contentTypeString] hasPrefix:contentTypePrefix]) {
            OHDownloader *downloader;

            downloader = [[OHDownloader alloc] init];
            [downloader setPresentsWhenFinished:NO];
            [downloader setRequiredDirectoryPath:directoryPath];
            [OWWebPipeline startPipelineWithContent:address target:downloader];

            [downloader release];
        }
    }

    [[NSWorkspace sharedWorkspace] openFile:directoryPath];
}

@end
