/*
 * $Id: PagePanel.java,v 1.3 2009-01-26 05:09:01 tomoke Exp $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.sun.pdfview;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.ImageObserver;

import javax.swing.JPanel;

/**
 * A Swing-based panel that displays a PDF page image.  If the zoom tool
 * is in use, allows the user to select a particular region of the image to
 * be zoomed.
 */
public class PagePanel extends JPanel
        implements ImageObserver, MouseListener, MouseMotionListener {

    /** The image of the rendered PDF page being displayed */
    Image currentImage;
    /** The current PDFPage that was rendered into currentImage */
    PDFPage currentPage;
    /* the current transform from device space to page space */
    AffineTransform currentXform;
    /** The horizontal offset of the image from the left edge of the panel */
    int offx;
    /** The vertical offset of the image from the top of the panel */
    int offy;
    /** the current clip, in device space */
    Rectangle2D clip;
    /** the clipping region used for the image */
    Rectangle2D prevClip;
    /** the size of the image */
    Dimension prevSize;
    /** the zooming marquee */
    Rectangle zoomRect;
    /** whether the zoom tool is enabled */
    boolean useZoom = false;

    //    /** a listener for page changes */
    //    PageChangeListener listener;
    /** a flag indicating whether the current page is done or not. */
    Flag flag = new Flag();

    // Color boxcolor= new Color(255,200,200);
    /**
     * Create a new PagePanel, with a default size of 800 by 600 pixels.
     */
    public PagePanel() {
        super();
        setPreferredSize(new Dimension(800, 600));
        setFocusable(true);
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    /**
     * Stop the generation of any previous page, and draw the new one.
     * @param page the PDFPage to draw.
     */
    public synchronized void showPage(PDFPage page) {
        // stop drawing the previous page
        if (currentPage != null && prevSize != null) {
            currentPage.stop(prevSize.width, prevSize.height, prevClip);
        }

        // set up the new page
        currentPage = page;

        if (page == null) {
            // no page
            currentImage = null;
            clip = null;
            currentXform = null;
            repaint();
        } else {
            // start drawing -- clear the flag to indicate we're in progress.
            flag.clear();
            //	    System.out.println("   flag cleared");

            Dimension sz = getSize();
            if (sz.width + sz.height == 0) {
                // no image to draw.
                return;
            }
            //	    System.out.println("Ratios: scrn="+((float)sz.width/sz.height)+
            //			       ", clip="+(clip==null ? 0 : clip.getWidth()/clip.getHeight()));

            // calculate the clipping rectangle in page space from the
            // desired clip in screen space.
            Rectangle2D useClip = clip;
            if (clip != null && currentXform != null) {
                useClip = currentXform.createTransformedShape(clip).getBounds2D();
            }

            Dimension pageSize = page.getUnstretchedSize(sz.width, sz.height,
                    useClip);

            // get the new image
            currentImage = page.getImage(pageSize.width, pageSize.height,
                    useClip, this);

            // calculate the transform from screen to page space
            currentXform = page.getInitialTransform(pageSize.width,
                    pageSize.height,
                    useClip);
            try {
                currentXform = currentXform.createInverse();
            } catch (NoninvertibleTransformException nte) {
                System.out.println("Error inverting page transform!");
                nte.printStackTrace();
            }

            prevClip = useClip;
            prevSize = pageSize;

            repaint();
        }
    }

    /**
     * @deprecated
     */
    public synchronized void flush() {
        //	images.clear();
        //	lruPages.clear();
        //	nextPage= null;
        //	nextImage= null;
    }

    /**
     * Draw the image.
     */
    public void paint(Graphics g) {
        Dimension sz = getSize();
        g.setColor(getBackground());
        g.fillRect(0, 0, getWidth(), getHeight());
        if (currentImage == null) {
            // No image -- draw an empty box
            // [[MW: remove the scary red X]]
            //	    g.setColor(Color.red);
            //	    g.drawLine(0, 0, getWidth(), getHeight());
            //	    g.drawLine(0, getHeight(), getWidth(), 0);
            g.setColor(Color.black);
            g.drawString("No page selected", getWidth() / 2 - 30, getHeight() / 2);
        } else {
            // draw the image
            int imwid = currentImage.getWidth(null);
            int imhgt = currentImage.getHeight(null);

            // draw it centered within the panel
            offx = (sz.width - imwid) / 2;
            offy = (sz.height - imhgt) / 2;

            if ((imwid == sz.width && imhgt <= sz.height) ||
                    (imhgt == sz.height && imwid <= sz.width)) {

                g.drawImage(currentImage, offx, offy, this);

            } else {
                // the image is bogus.  try again, or give up.
                flush();
                if (currentPage != null) {
                    showPage(currentPage);
                }
                g.setColor(Color.red);
                g.drawLine(0, 0, getWidth(), getHeight());
                g.drawLine(0, getHeight(), getWidth(), 0);
            }
        }
        // draw the zoomrect if there is one.
        if (zoomRect != null) {
            g.setColor(Color.red);
            g.drawRect(zoomRect.x, zoomRect.y,
                    zoomRect.width, zoomRect.height);
        }
    // debugging: draw a rectangle around the portion that just changed.
    //	g.setColor(boxColor);
    //	Rectangle r= g.getClipBounds();
    //	g.drawRect(r.x, r.y, r.width-1, r.height-1);
    }

    /**
     * Gets the page currently being displayed
     */
    public PDFPage getPage() {
        return currentPage;
    }

    /**
     * Gets the size of the image currently being displayed
     */
    public Dimension getCurSize() {
        return prevSize;
    }

    /**
     * Gets the clipping rectangle in page space currently being displayed
     */
    public Rectangle2D getCurClip() {
        return prevClip;
    }

    /**
     * Waits until the page is either complete or had an error.
     */
    public void waitForCurrentPage() {
        flag.waitForFlag();
    }

    /**
     * Handles notification of the fact that some part of the image
     * changed.  Repaints that portion.
     * @return true if more updates are desired.
     */
    public boolean imageUpdate(Image img, int infoflags, int x, int y,
            int width, int height) {
        // System.out.println("Image update: " + (infoflags & ALLBITS));
        Dimension sz = getSize();
        if ((infoflags & (SOMEBITS | ALLBITS)) != 0) {
            // [[MW: dink this rectangle by 1 to handle antialias issues]]
            repaint(x + offx, y + offy, width, height);
        }
        if ((infoflags & (ALLBITS | ERROR | ABORT)) != 0) {
            flag.set();
            //	    System.out.println("   flag set");
            return false;
        } else {
            return true;
        }
    }

//    public void addPageChangeListener(PageChangeListener pl) {
//	listener= pl;
//    }

//    public void removePageChangeListener(PageChangeListener pl) {
//	listener= null;
//    }
    /**
     * Turns the zoom tool on or off.  If on, mouse drags will draw the
     * zooming marquee.  If off, mouse drags are ignored.
     */
    public void useZoomTool(boolean use) {
        useZoom = use;
    }

    /**
     * Set the desired clipping region (in screen coordinates), and redraw
     * the image.
     */
    public void setClip(Rectangle2D clip) {
        this.clip = clip;
        showPage(currentPage);
    }
    /** x location of the mouse-down event */
    int downx;
    /** y location of the mouse-down event */
    int downy;

    /** Handles a mousePressed event */
    public void mousePressed(MouseEvent evt) {
        downx = evt.getX();
        downy = evt.getY();
    }

    /**
     * Handles a mouseReleased event.  If zooming is turned on and there's
     * a valid zoom rectangle, set the image clip to the zoom rect.
     */
    public void mouseReleased(MouseEvent evt) {
        // calculate new clip
        if (!useZoom || zoomRect == null ||
                zoomRect.width == 0 || zoomRect.height == 0) {
            zoomRect = null;
            return;
        }

        setClip(new Rectangle2D.Double(zoomRect.x - offx, zoomRect.y - offy,
                zoomRect.width, zoomRect.height));

        zoomRect = null;
    }

    public void mouseClicked(MouseEvent evt) {
    }

    public void mouseEntered(MouseEvent evt) {
    }

    public void mouseExited(MouseEvent evt) {
    }

    public void mouseMoved(MouseEvent evt) {
    }

    /**
     * Handles a mouseDragged event. Constrains the zoom rect to the
     * aspect ratio of the panel unless the shift key is down.
     */
    public void mouseDragged(MouseEvent evt) {
        if (useZoom) {
            int x = evt.getX();
            int y = evt.getY();
            int dx = Math.abs(x - downx);
            int dy = Math.abs(y - downy);
            // constrain to the aspect ratio of the panel
            if ((evt.getModifiers() & evt.SHIFT_MASK) == 0) {
                float aspect = (float) dx / (float) dy;
                float waspect = (float) getWidth() / (float) getHeight();
                if (aspect > waspect) {
                    dy = (int) (dx / waspect);
                } else {
                    dx = (int) (dy * waspect);
                }
            }
            if (x < downx) {
                x = downx - dx;
            }
            if (y < downy) {
                y = downy - dy;
            }
            Rectangle old = zoomRect;
            // ignore small rectangles
            if (dx < 5 || dy < 5) {
                zoomRect = null;
            } else {
                zoomRect = new Rectangle(Math.min(downx, x), Math.min(downy, y),
                        dx, dy);
            }
            // calculate the repaint region.  Should be the union of the
            // old zoom rect and the new one, with an extra pixel on the
            // bottom and right because of the way rectangles are drawn.
            if (zoomRect != null) {
                if (old != null) {
                    old.add(zoomRect);
                } else {
                    old = new Rectangle(zoomRect);
                }
            }
            if (old != null) {
                old.width++;
                old.height++;
            }
            if (old != null) {
                repaint(old);
            }
        }
    }
}
