/* class JScrollPane
 *
 * Copyright (C) 2001, 2002, 2003  R M Pitman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package charvax.swing;

import charva.awt.*;
import charva.awt.event.ScrollEvent;
import charva.awt.event.ScrollListener;
import charvax.swing.border.Border;
import charvax.swing.table.TableHeader;

/**
 * provides a scrollable view of a component.
 */
public class JScrollPane
        extends Container
        implements ScrollListener {

    /**
     * Creates an empty JScrollPane.
     */
    public JScrollPane() {
    }

    /**
     * Create a JScrollPane that displays the contents of the specified
     * component.
     *
     * @param component_ The component to be displayed.  This component must
     *                   implement the Scrollable interface.
     */
    public JScrollPane(Component component_) {
        setViewportView(component_);
    }

    /**
     * Creates a viewport if necessary, and then sets its view.
     *
     * @param component_ the view to set in the viewport.
     */
    public void setViewportView(Component component_) {
        _child = component_;

        _childViewport.setView(component_);
        add(_childViewport);
        _childViewport.setParent(this);

        /* This will cause a ClassCastException if the component does
         * not implement the Scrollable interface.
         */
        Scrollable scrollable = (Scrollable) component_;
        scrollable.addScrollListener(this);

        /* If the child component is a JTable, we display two viewports
         * instead of one; the TableHeader corresponding to the table is
         * displayed in the top viewport and scrolls left and right but
         * not up and down. The contents of the table are displayed in the
         * bottom viewport and scroll in all directions.
         */
        if (component_ instanceof JTable) {
            JTable table = (JTable) component_;
            TableHeader header = new TableHeader(table.getModel());
            _headerViewport = new JViewport();
            _headerViewport.setView(header);
            add(_headerViewport);
            _headerViewport.setParent(this);

            _childViewport.setLocation(new Point(0, 1));
            _childViewport.setViewPosition(new Point(0, -1));
        }
    }

    /**
     * Returns the viewport of the component being displayed.
     */
    public JViewport getViewport() {
        return _childViewport;
    }

    /**
     * Overrides the corresponding method in Container.
     */
    public void setSize(int width_, int height_) {
        super.setSize(width_, height_);
        Dimension size = new Dimension(width_, height_);
        if (_border != null) {
            Insets borderInsets = _border.getBorderInsets(this);
            size.height -= (borderInsets.top + borderInsets.bottom);
            size.width -= (borderInsets.left + borderInsets.right);
        }

        // Set the size of the viewport(s) as well
        setViewportExtents(size);
    }

    /**
     * Overrides the corresponding method in Container.
     */
    public void setSize(Dimension size_) {
        this.setSize(size_.width, size_.height);
    }

    /**
     * Overrides the minimumSize() method of Container.
     */
    public Dimension minimumSize() {

        Dimension size = new Dimension();
        Component view = getViewport().getView();
        if (view instanceof Scrollable) {
            Scrollable s = (Scrollable) view;
            size.setSize(s.getPreferredScrollableViewportSize());
        } else {
            size.setSize(view.getSize());
        }

        // Set the size of the viewport(s) as well
        setViewportExtents(size);

        if (_border != null) {
            Insets borderInsets = _border.getBorderInsets(this);
            size.height += (borderInsets.top + borderInsets.bottom);
            size.width += (borderInsets.left + borderInsets.right);
        }

        return size;
    }

    /**
     * Called by a Scrollable object such as JTable or JList, when its state
     * changes in such a way that it may need to be scrolled.
     */
    public void scroll(ScrollEvent e_) {
        Scrollable scrollable = e_.getScrollable();

        int direction = e_.getDirection();

        /* "limit" gives the row and column of the view component that
         * must appear just inside the JViewport after scrolling.
         */
        Point limit = e_.getLimit();

        /* Determine the value of "limit" relative to the top left
         * corner of the JScrollPane.
         */
        Point viewportLocation = (Point) getViewport().getLocation();
        limit.translate(viewportLocation.x, viewportLocation.y);
        limit.translate(scrollable.getLocation());

        /* Get the bounding rectangle of the child viewport, relative to
         * the top left corner of the JScrollPane.
         */
        Rectangle viewport = _childViewport.getBounds();
//        Dimension viewportExtent = _childViewport.getExtentSize();

        Point viewPosition = _childViewport.getViewPosition();
        Point headerPosition = null;
        if (_headerViewport != null)
            headerPosition = _headerViewport.getViewPosition();

        /* If the limit is inside the viewport, the component
         * doesn't need to be scrolled.
         * First do the left/right scrolling.
         */
        if (limit.x > viewport.getRight()) {
            if ((direction == ScrollEvent.LEFT ||
                    direction == ScrollEvent.UP_LEFT ||
                    direction == ScrollEvent.DOWN_LEFT)) {

                viewPosition.x -= (limit.x - viewport.getRight());
                if (_headerViewport != null)
                    headerPosition.x -= (limit.x - viewport.getRight());
            } else if (direction == ScrollEvent.RIGHT ||
                    direction == ScrollEvent.UP_RIGHT ||
                    direction == ScrollEvent.DOWN_RIGHT) {

                viewPosition.x += (viewport.getLeft() - limit.x);
                if (_headerViewport != null)
                    headerPosition.x += (viewport.getLeft() - limit.x);
            }
        } else if (limit.x < viewport.getLeft()) {
            if (direction == ScrollEvent.RIGHT ||
                    direction == ScrollEvent.UP_RIGHT ||
                    direction == ScrollEvent.DOWN_RIGHT) {

                viewPosition.x += (viewport.getLeft() - limit.x);
                if (_headerViewport != null)
                    headerPosition.x += (viewport.getLeft() - limit.x);
            } else if (direction == ScrollEvent.LEFT ||
                    direction == ScrollEvent.UP_LEFT ||
                    direction == ScrollEvent.DOWN_LEFT) {
                viewPosition.x -= (limit.x - viewport.getRight());
                if (_headerViewport != null)
                    headerPosition.x -= (limit.x - viewport.getRight());
            }
        }

        // Now do the up/down scrolling
        if (limit.y < viewport.getTop() &&
                (direction == ScrollEvent.DOWN ||
                direction == ScrollEvent.DOWN_LEFT ||
                direction == ScrollEvent.DOWN_RIGHT)) {

            viewPosition.y += (viewport.getTop() - limit.y);
        } else if (limit.y > viewport.getBottom() &&
                (direction == ScrollEvent.UP ||
                direction == ScrollEvent.UP_LEFT ||
                direction == ScrollEvent.UP_RIGHT)) {

            viewPosition.y -= (limit.y - viewport.getBottom());
        }

        _childViewport.setViewPosition(viewPosition);
        if (_headerViewport != null)
            _headerViewport.setViewPosition(headerPosition);

        draw();

        /* Ensure the cursor is within the viewport (if the component
         * contained within the viewport is offset a long way to the left,
         * the cursor position can get scrambled).
         */
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Point cursor = toolkit.getCursor();
        Point viewportOrigin = _childViewport.getLocationOnScreen();
        if (cursor.x < viewportOrigin.x || cursor.y < viewportOrigin.y) {
            if (cursor.x < viewportOrigin.x)
                cursor.x = viewportOrigin.x;
            if (cursor.y < viewportOrigin.y)
                cursor.y = viewportOrigin.y;
            toolkit.setCursor(cursor);
        }
    }

    /**
     */
    public void draw() {

        /* Get the absolute origin of this component.
         */
        Point origin = getLocationOnScreen();
        Insets borderInsets;
        if (getViewportBorder() != null) {
            borderInsets = getViewportBorder().getBorderInsets(this);
        } else
            borderInsets = new Insets(0, 0, 0, 0);

        Toolkit term = Toolkit.getDefaultToolkit();
        int colorpair = getCursesColor();
        Dimension size = minimumSize();

        if (_border != null) {

            _border.paintBorder(this,
                    0, origin.x, origin.y,
                    size.width, size.height);
        }

        // Don't draw scrollbars if the child component is a TableHeader.
        if (_child instanceof TableHeader)
            return;

        /* If the child component is larger than the viewport, draw
         * scrollbars.
         */

        // The size of the component displayed within the viewport.
        Dimension childSize = (Dimension)
                getViewport().getViewSize();

        // The size of the viewport
        Dimension extentSize = (Dimension)
                getViewport().getExtentSize();
        Point viewPosition = getViewport().getViewPosition();

        // If the child is a JTable, we have to adjust the
        // parameters a bit because the viewport includes the header.
        /*if (_child instanceof JTable) {
            viewport_height--;
            child_height--;
            view_y++;
            }*/

        if (childSize.height > extentSize.height) {

            int scrollbar_height = (extentSize.height * extentSize.height)
                    / childSize.height;

            // Round the height upwards to the nearest integer.
            if (((extentSize.height * extentSize.height)
                    % childSize.height) != 0)
                scrollbar_height++;

            int scrollbar_offset =
                    (-1 * viewPosition.y * extentSize.height)
                    / childSize.height;

            for (int i = 0; i < extentSize.height; i++) {

                term.setCursor(origin.addOffset(borderInsets.left + extentSize.width,
                        borderInsets.top + i));
                if (i >= scrollbar_offset &&
                        i < scrollbar_offset + scrollbar_height) {
                    term.addChar(Toolkit.ACS_CKBOARD, 0, colorpair);
                }
            }
        }

        if (childSize.width > extentSize.width) {

            int scrollbar_width = (extentSize.width * extentSize.width)
                    / childSize.width;

            // Round the width upwards to the nearest integer.
            if (((extentSize.width * extentSize.width)
                    % childSize.width) != 0)
                scrollbar_width++;

            int scrollbar_offset =
                    (-1 * viewPosition.x * extentSize.width)
                    / childSize.width;

            for (int i = 0; i < extentSize.width; i++) {

                term.setCursor(origin.addOffset(borderInsets.left + i,
                        borderInsets.top + extentSize.height));
                if (i >= scrollbar_offset &&
                        i < scrollbar_offset + scrollbar_width) {
                    term.addChar(Toolkit.ACS_CKBOARD, 0, colorpair);
                }
            }
        }

        // Draw the child viewport(s) by calling the draw() method
        // of the Container class.
        super.draw();
    }

    /**
     * Adds a border around the viewport.
     */
    public void setViewportBorder(Border viewportBorder_) {
        _border = viewportBorder_;
        Insets insets = _border.getBorderInsets(this);


        if (_headerViewport != null) {
            // This must be a JTable.
            _headerViewport.setLocation(new Point(insets.left, insets.top));
            _childViewport.setLocation(new Point(insets.left, insets.top + 1));
        } else
            _childViewport.setLocation(new Point(insets.left, insets.top));
    }

    /**
     * Returns a reference to the border around the JScrollPane's viewport.
     */
    public Border getViewportBorder() {
        return _border;
    }

    public void debug(int level_) {
        for (int i = 0; i < level_; i++)
            System.err.print("    ");
        System.err.println("JScrollPane origin=" + _origin +
                " size=" + getSize());
        super.debug(level_ + 1);
    }

    public String toString() {
        return ("JScrollPane origin=" + _origin + " size=" + getSize());
    }

    /**
     * Sets the size of the visible part of the view.
     */
    private void setViewportExtents(Dimension size) {
        if (_headerViewport != null) {
            _headerViewport.setExtentSize(size.width, 1);
            _childViewport.setExtentSize(size.width, size.height - 1);
        } else {
            _childViewport.setExtentSize(size.width, size.height);
        }
    }

    //====================================================================
    // INSTANCE VARIABLES

    private Component _child;

    /**
     * This is used only if the contained component is a JTable.
     */
    private JViewport _headerViewport = null;

    /**
     * A JViewport container that holds the (single) child component.
     */
    private JViewport _childViewport = new JViewport();

//    private JScrollBar _vertical;

//    private JScrollBar _horiz;

    private Border _border;
}
