/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 of the License, or (at your option) any later version. This program 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 General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ package org.lobobrowser.primary.gui.pdf; 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; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.sun.pdfview.PDFFile; import com.sun.pdfview.PDFPage; /** * 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 Constant logger. */ private static final Logger logger = LogManager.getLogger(ThumbPanel.class); /** The Constant serialVersionUID. */ private static final long serialVersionUID = -6761217072379594185L; /** 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() { @Override 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. */ @Override 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; PDFPage p = file.getPage(pagetoread, true); int wid = (int) Math.ceil((lineheight - border) * p.getAspectRatio()); int pagetowrite = workingon; Image i = p.getImage(wid, (lineheight - border), null, this, true, true); images[pagetowrite] = i; if (defaultNotSet) { defaultNotSet = false; setDefaultWidth(wid); } repaint(); } catch (Exception e) { logger.log(Level.ERROR, e); 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 guesstimate of the width of a thumbnail that hasn't been * processed yet. * * @param width * the new guesstimate of the width of a thumbnail that hasn't * been processed yet */ 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 { /** The page. */ int page; public GotoLater(int pagenum) { page = pagenum; } @Override public void run() { if (listener != null) { listener.gotoPage(page); } } } /** * Updates the positions of the thumbnails, and draws them to the screen. */ @Override 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 = 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. */ @Override 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); } @Override public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } @Override public int getScrollableBlockIncrement(Rectangle visrect, int orientation, int direction) { return Math.max(lineheight, (visrect.height / lineheight) * lineheight); } @Override public boolean getScrollableTracksViewportHeight() { return false; } @Override public boolean getScrollableTracksViewportWidth() { return true; } @Override public int getScrollableUnitIncrement(Rectangle visrect, int orientation, int direction) { return lineheight; } }