/**
* NanoDoA - File based document archive
*
* Copyright (C) 2011-2012 Christian Packenius, christian.packenius@googlemail.com
*
* 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 version 3 of the License, or
* 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 Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.chris_soft.utilities.swing.pdf;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.border.LineBorder;
import javax.swing.event.ListSelectionListener;
import de.chris_soft.utilities.LogUtils;
import de.chris_soft.utilities.pdf.PdfPageRenderUtility;
import de.chris_soft.utilities.swing.SwingUtils;
/**
* Swing list that displays thumbnails of the pages of a PDF file.
* @author Christian Packenius.
*/
public class PdfThumbnailList implements ListCellRenderer, Runnable {
private final JScrollPane scrollPane = new JScrollPane();
private final int iconSize;
private final List<Image> thumbnailList = new ArrayList<Image>();
private final List<JLabel> labelList = new ArrayList<JLabel>();
final DefaultListModel model = new DefaultListModel();
private final JList jList = new JList(model);
private volatile PdfPageRenderUtility pdfRenderer;
int pageCount;
private final Color backgroundColor = new JLabel().getBackground();
private final BufferedImage notLoadedIcon;
private final BufferedImage errorIcon;
/**
* Constructor.
* @param iconSize Size of the icons (N*N).
*/
public PdfThumbnailList(int iconSize) {
this.iconSize = iconSize;
notLoadedIcon = new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB);
errorIcon = new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB);
createDefaultImageIcon(notLoadedIcon);
createDefaultImageIcon(errorIcon);
jList.setPrototypeCellValue(notLoadedIcon);
jList.setCellRenderer(this);
jList.setBackground(backgroundColor);
scrollPane.setViewportView(jList);
scrollPane.setPreferredSize(new Dimension(iconSize + 25, 0));
}
private void createDefaultImageIcon(BufferedImage image) {
Graphics2D g2d = (Graphics2D) image.getGraphics();
g2d.setBackground(Color.LIGHT_GRAY);
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillRect(0, 0, iconSize, iconSize);
if (image == errorIcon) {
g2d.setColor(Color.RED);
g2d.setBackground(Color.RED);
g2d.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.drawLine(iconSize / 5, iconSize / 5, iconSize - 1 - iconSize / 5, iconSize - 1 - iconSize / 5);
g2d.drawLine(iconSize - 1 - iconSize / 5, iconSize / 5, iconSize / 5, iconSize - 1 - iconSize / 5);
g2d.dispose();
g2d.setColor(Color.GRAY);
g2d.setBackground(Color.GRAY);
g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.drawLine(iconSize / 5, iconSize / 5, iconSize - 1 - iconSize / 5, iconSize - 1 - iconSize / 5);
g2d.drawLine(iconSize - 1 - iconSize / 5, iconSize / 5, iconSize / 5, iconSize - 1 - iconSize / 5);
g2d.dispose();
}
}
/**
* Test method.
* @param args Not used.
* @throws IOException
*/
public static void main(String[] args) throws IOException {
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(BorderLayout.CENTER, new JLabel("adsf"));
PdfThumbnailList ptl = new PdfThumbnailList(128);
frame.getContentPane().add(BorderLayout.WEST, ptl.getComponent());
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
frame.setVisible(true);
ptl.setPdfFile(new PdfPageRenderUtility(new File("1-3262-7391-9387.pdf")));
}
/**
* Returns the swing component for this PDF thumbnail list.
* @return JScrollPane object.
*/
public JScrollPane getComponent() {
return scrollPane;
}
/**
* Adds a PDF file to the view list.
* @param pdfRenderer PDF file to add in the list.
*/
public void setPdfFile(PdfPageRenderUtility pdfRenderer) {
if (pdfRenderer != null && this.pdfRenderer != pdfRenderer) {
this.pdfRenderer = pdfRenderer;
Thread thread = new Thread(this, "PDF thumbnail list thread");
thread.setDaemon(true);
thread.start();
}
}
/**
* Adds a selection listener to the pdf list.
* @param listener Selection listener.
*/
public void addListSelectionListener(ListSelectionListener listener) {
jList.addListSelectionListener(listener);
}
/**
* Returns the current selected page in the PDF page list.
* @return Selected page index (1..N).
*/
public int getSelectedPage() {
int index = jList.getSelectedIndex();
if (index >= 0) {
index++;
}
return index;
}
/**
* @see javax.swing.ListCellRenderer#getListCellRendererComponent(javax.swing.JList,
* java.lang.Object, int, boolean, boolean)
*/
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
boolean cellHasFocus) {
int pageID = -1;
String errorText = null;
if (value instanceof String) {
try {
pageID = Integer.parseInt((String) value);
}
catch (NumberFormatException nfe) {
errorText = (String) value;
}
}
Image image = getImageForRendering(pageID);
String text = getTextForRendering(pageID);
JLabel label = createLabelForThumbnailList(isSelected, image, errorText == null ? text : errorText);
labelList.add(index, label);
return label;
}
private Image getImageForRendering(int pageID) {
if (pageID <= 0 || pageID > thumbnailList.size()) {
return notLoadedIcon;
}
return thumbnailList.get(pageID - 1);
}
private String getTextForRendering(int pageID) {
if (pageID <= 0) {
return "Loading...";
}
if (pageID > thumbnailList.size()) {
return "Loading page " + pageID + "...";
}
if (thumbnailList.get(pageID - 1) == null) {
return "Cannot load page " + pageID;
}
return "Page " + pageID;
}
private JLabel createLabelForThumbnailList(boolean isSelected, Image image, String text) {
JLabel label = new JLabel();
label.setHorizontalAlignment(SwingConstants.CENTER);
if (image != null) {
label.setIcon(new ImageIcon(image));
}
if (text != null) {
label.setText(text);
}
if (text != null && image != null) {
label.setVerticalTextPosition(SwingConstants.BOTTOM);
label.setHorizontalTextPosition(SwingConstants.CENTER);
}
if (isSelected) {
boolean hasFocus = jList.hasFocus();
if (hasFocus) {
label.setBorder(new LineBorder(Color.DARK_GRAY, 3));
}
else {
label.setBorder(new LineBorder(Color.LIGHT_GRAY, 3));
}
}
else {
label.setBorder(new LineBorder(backgroundColor, 3));
}
return label;
}
/**
* Set a page as selected.
* @param pageID Page to select (1..N).
*/
public void setSelectedPage(final int pageID) {
if (pageID - 1 != jList.getSelectedIndex()) {
try {
SwingUtils.runInGuiNow(new Runnable() {
@Override
public void run() {
try {
changeSelectedPdfPage(pageID);
}
catch (Exception exception) {
LogUtils.log(exception);
}
}
});
}
catch (Exception exception) {
LogUtils.log(exception);
}
}
}
void changeSelectedPdfPage(final int pageID) {
try {
if (pageID <= model.getSize() && pageID > 0) {
jList.setSelectedIndex(pageID - 1);
jList.ensureIndexIsVisible(pageID - 1);
}
}
catch (Exception e) {
LogUtils.log(e);
}
}
/**
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
synchronized (this) {
File pdfFile2view = initRenderingOfNewPdfFile();
for (int i = 1; i <= pageCount; i++) {
if (!pdfFile2view.equals(pdfRenderer.pdfFile)) {
break;
}
renderAndDisplaySingleImage(i);
}
}
}
private File initRenderingOfNewPdfFile() {
File pdfFile2view = pdfRenderer.pdfFile;
thumbnailList.clear();
pageCount = 0;
try {
pageCount = pdfRenderer.pageCount;
SwingUtils.runInGuiNow(new Runnable() {
@Override
public void run() {
model.removeAllElements();
// model.setSize(pageCount);
}
});
for (int i = 1; i <= pageCount; i++) {
if (!pdfFile2view.equals(pdfRenderer.pdfFile)) {
break;
}
final int pageID = i;
SwingUtils.runInGuiNow(new Runnable() {
@Override
public void run() {
// if (model.getSize() > pageID) {
// model.setElementAt("" + pageID, pageID);
// }
// else {
model.addElement("" + pageID);
// }
}
});
if (pageID == 1) {
setSelectedPage(1);
}
}
}
catch (Exception exception) {
LogUtils.log(exception);
}
return pdfFile2view;
}
private void renderAndDisplaySingleImage(int i) {
final int k = i;
String text = "" + k;
try {
Image image = pdfRenderer.getPage(i, iconSize);
thumbnailList.add(image);
}
catch (Exception exception) {
try {
if (thumbnailList.size() < i) {
thumbnailList.add(errorIcon);
}
text = "Cannot load page " + i + ".";
}
catch (Exception e) {
// An error occurs only if an image change has happend!
// LogUtils.log(e);
}
}
finally {
repaintListItem(k, text);
}
}
private void repaintListItem(final int k, final String text) {
try {
SwingUtils.runInGuiNow(new Runnable() {
@Override
public void run() {
// MUST be a new string object!
model.set(k - 1, new String(text));
}
});
}
catch (Exception exception) {
// An error occurs only if an image change has happend!
// LogUtils.log(e);
}
}
}