/* * $Id: ThumbPanel.java,v 1.5 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.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import javax.swing.JPanel; import javax.swing.JViewport; import javax.swing.Scrollable; import javax.swing.SwingUtilities; /** * A panel of thumbnails, one for each page of a PDFFile. You can add * a PageChangeListener to be informed of when the user clicks one of the * pages. */ public class ThumbPanel extends JPanel implements Runnable, Scrollable, ImageObserver { /** The PDFFile being displayed */ PDFFile file; /** Array of images, one per page in the file */ Image images[]; /** Size of the border between images*/ int border = 2; /** * Height of each line. Thumbnails will be scaled to this height * (minus the border). */ int lineheight = 96 + border; /** * Guesstimate of the width of a thumbnail that hasn't been processed * yet. */ int defaultWidth = (lineheight - border) * 4 / 3; /** * Array of the x locations of each of the thumbnails. Every 0 stored * in this array indicates the start of a new line of thumbnails. */ int xloc[]; /** Thread that renders each thumbnail in turn */ Thread anim; /** Which thumbnail is selected, or -1 if no thumbnail selected. */ int showing = -1; /** * Which thumbnail needs to be drawn next, or -1 if the previous * needy thumbnail is being processed. */ int needdrawn = -1; /** * Whether the default width has been guesstimated for this PDFFile * yet. */ boolean defaultNotSet = true; /** The PageChangeListener that is listening for page changes */ PageChangeListener listener; // Flag flag= new Flag(); /** * Creates a new ThumbPanel based on a PDFFile. The file may be null. * Automatically starts rendering thumbnails for that file. */ public ThumbPanel(PDFFile file) { super(); this.file = file; if (file != null) { int count = file.getNumPages(); images = new Image[count]; xloc = new int[count]; setPreferredSize(new Dimension(defaultWidth, 200)); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent evt) { handleClick(evt.getX(), evt.getY()); } }); anim = new Thread(this); anim.setName(getClass().getName()); anim.start(); } else { images = new Image[0]; setPreferredSize(new Dimension(defaultWidth, 200)); } } /** * Renders each of the pages in the PDFFile into a thumbnail. * Preferentially works on the needdrawn thumbnail, otherwise, go in * order. */ public void run() { int workingon = 0; // the thumbnail we'll be rendering next. while (anim == Thread.currentThread()) { if (needdrawn >= 0) { workingon = needdrawn; needdrawn = -1; } // find an unfinished page int loop; for (loop = images.length; loop > 0; loop--) { if (images[workingon] == null) { break; } workingon++; if (workingon >= images.length) { workingon = 0; } } if (loop == 0) { // done all pages. break; } // build the page try { int pagetoread = workingon + 1; // int pagetoread= 1; // System.out.println("Read page: " + pagetoread); PDFPage p = file.getPage(pagetoread, true); int wid = (int) Math.ceil((lineheight - border) * p.getAspectRatio()); // if (!p.isFinished()) { // System.out.println("Page not finished!"); // p.waitForFinish(); // } // flag.clear(); // int pagetowrite= 0; int pagetowrite = workingon; Image i = p.getImage(wid, (lineheight - border), null, this, true, true); // images[0] = i; images[pagetowrite] = i; // flag.waitForFlag(); if (defaultNotSet) { defaultNotSet = false; setDefaultWidth(wid); } repaint(); } catch (Exception e) { e.printStackTrace(); int size = lineheight - border; images[workingon] = new BufferedImage(size, size, BufferedImage.TYPE_BYTE_BINARY); } } } /** * Adds a PageChangeListener to receive notification of page clicks. */ public void addPageChangeListener(PageChangeListener pl) { // [[MW: should be an array list instead of only one]] listener = pl; } /** * Removes a PageChangeListener from the notification list. */ public void removePageChangeListener(PageChangeListener pl) { // [[MW: should be an array list instead of only one]] listener = null; } /** * Stops the render thread. Be sure to call this before dropping * a ThumbPanel. */ public void stop() { anim = null; } /** * Sets the default width of an un-processed thumbnail. * @param width the width of an unknown thumbnail, in pixels. */ public void setDefaultWidth(int width) { defaultWidth = width; // setPreferredSize(new Dimension(width, lineheight)); } /** * Handles a mouse click in the panel. Figures out which page was * clicked, and calls showPage. * @param x the x coordinate of the mouse click * @param y the y coordinate of the mouse click */ public void handleClick(int x, int y) { int linecount = -1; int line = y / lineheight; // run through the thumbnail locations, counting new lines // until the appropriate line is reached. for (int i = 0; i < xloc.length; i++) { if (xloc[i] == 0) { linecount++; } if (line == linecount && xloc[i] + (images[i] != null ? images[i].getWidth(null) : defaultWidth) > x) { showPage(i); break; } } } /** * Sets the currently viewed page, indicates it with a highlight * border, and makes sure the thumbnail is visible. */ public void pageShown(int pagenum) { if (showing != pagenum) { // FIND THE SELECTION RECTANGLE // getViewPort.scrollRectToVisible(r); if (pagenum >= 0 && getParent() instanceof JViewport) { int y = -lineheight; for (int i = 0; i <= pagenum; i++) { if (xloc[i] == 0) { y += lineheight; } } Rectangle r = new Rectangle(xloc[pagenum], y, (images[pagenum] == null ? defaultWidth : images[pagenum].getWidth(null)), lineheight); scrollRectToVisible(r); } showing = pagenum; repaint(); } } /** * Notifies the listeners that a page has been selected. Performs * the notification in the AWT thread. * Also highlights the selected page. Does this first so that feedback * is immediate. */ public void showPage(int pagenum) { pageShown(pagenum); SwingUtilities.invokeLater(new GotoLater(pagenum)); } /** * Simple runnable to tell listeners that the page has changed. */ class GotoLater implements Runnable { int page; public GotoLater(int pagenum) { page = pagenum; } public void run() { if (listener != null) { listener.gotoPage(page); } } } /** * Updates the positions of the thumbnails, and draws them to the * screen. */ public void paint(Graphics g) { int x = 0; int y = 0; int maxwidth = 0; Rectangle clip = g.getClipBounds(); g.setColor(Color.gray); int width = getWidth(); g.fillRect(0, 0, width, getHeight()); for (int i = 0; i < images.length; i++) { // calculate the x location of the thumbnail, based on its width int w = defaultWidth + 2; if (images[i] != null) { w = (int) images[i].getWidth(null) + 2; } // need a new line? if (x + w > width && x != 0) { x = 0; y += lineheight; } // if the thumbnail is visible, draw it. if (clip.intersects(new Rectangle(x, y, w, lineheight))) { if (images[i] != null) { // thumbnail is ready. g.drawImage(images[i], x + 1, y + 1, this); } else { // thumbnail isn't ready. Remember that we need it... if (needdrawn == -1) { needdrawn = i; } // ... and draw a blank thumbnail. g.setColor(Color.lightGray); g.fillRect(x + 1, y + 1, w - border, lineheight - border); g.setColor(Color.darkGray); g.drawRect(x + 1, y + 1, w - border - 1, lineheight - border - 1); } // draw the selection highlight if needed. if (i == showing) { g.setColor(Color.red); g.drawRect(x, y, w - 1, lineheight - 1); g.drawRect(x + 1, y + 1, w - 3, lineheight - 3); } } // save the x location of this thumbnail. xloc[i] = x; x += w; // remember the longest line if (x > maxwidth) { maxwidth = x; } } // if there weren't any thumbnails, make a default line width if (maxwidth == 0) { maxwidth = defaultWidth; } Dimension d = getPreferredSize(); if (d.height != y + lineheight || d.width != maxwidth) { setPreferredSize(new Dimension(maxwidth, y + lineheight)); revalidate(); } } /** * Handles notification of any image updates. Not used any more. */ public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { // if ((infoflags & ALLBITS)!=0) { // flag.set(); // } return ((infoflags & (ALLBITS | ERROR | ABORT)) == 0); } public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } public int getScrollableBlockIncrement(Rectangle visrect, int orientation, int direction) { return Math.max(lineheight, (visrect.height / lineheight) * lineheight); } public boolean getScrollableTracksViewportHeight() { return false; } public boolean getScrollableTracksViewportWidth() { return true; } public int getScrollableUnitIncrement(Rectangle visrect, int orientation, int direction) { return lineheight; } }