/* 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.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.Rectangle2D; import java.awt.print.Book; import java.awt.print.PageFormat; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLConnection; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Box; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.filechooser.FileFilter; import org.lobobrowser.util.SSLCertificate; import com.sun.pdfview.OutlineNode; import com.sun.pdfview.PDFDestination; import com.sun.pdfview.PDFFile; import com.sun.pdfview.PDFObject; import com.sun.pdfview.PDFPage; import com.sun.pdfview.action.GoToAction; import com.sun.pdfview.action.PDFAction; /** * The Class PdfDialog. */ public class PdfDialog extends JFrame implements KeyListener, TreeSelectionListener, PageChangeListener { /** The Constant logger. */ private static final Logger logger = LogManager.getLogger(PdfDialog.class); /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1L; /** The Constant TITLE. */ public final static String TITLE = "SwingLabs PDF Viewer"; /** The current PDFFile. */ PDFFile curFile; /** the name of the current document. */ String docName; /** The split between thumbs and page. */ JSplitPane split; /** The thumbnail scroll pane. */ JScrollPane thumbscroll; /** The thumbnail display. */ ThumbPanel thumbs; /** The page display. */ PagePanel page; /** The full screen page display, or null if not in full screen mode. */ PagePanel fspp; // Thread anim; /** The current page number (starts at 0), or -1 if no page. */ int curpage = -1; /** the full screen button. */ JToggleButton fullScreenButton; /** the current page number text field. */ JTextField pageField; /** the full screen window, or null if not in full screen mode. */ FullScreenWindow fullScreen; /** the root of the outline, or null if there is no outline. */ OutlineNode outline = null; /** The page format for printing. */ PageFormat pformat = PrinterJob.getPrinterJob().defaultPage(); /** true if the thumb panel should exist at all. */ boolean doThumb = true; /** flag to indicate when a newly added document has been announced. */ Flag docWaiter; /** a thread that pre-loads the next page for faster response. */ PagePreparer pagePrep; /** the window containing the pdf outline, or null if one doesn't exist. */ JDialog olf; /** the document menu. */ JMenu docMenu; /** * utility method to get an icon from the resources of this class. * * @param name * the name of the icon * @return the icon, or null if the icon wasn't found. */ public Icon getIcon(String name) { Icon icon = null; URL url = null; try { url = getClass().getResource(name); icon = new ImageIcon(url); } catch (Exception e) { logger.log(Level.ERROR,e); } return icon; } // / FILE MENU /** The page setup action. */ Action pageSetupAction = new AbstractAction("Page setup...") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent evt) { doPageSetup(); } }; /** The print action. */ Action printAction = new AbstractAction("Print...", getIcon("/org/lobobrowser/images/print.png")) { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent evt) { doPrint(); } }; /** The close action. */ Action closeAction = new AbstractAction("Close") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent evt) { doClose(); } }; /** The quit action. */ Action quitAction = new AbstractAction("Quit") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent evt) { doQuit(); } }; /** The zoom tool action. */ Action zoomToolAction = new AbstractAction("", getIcon("/org/lobobrowser/images/zoomin.png")) { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent evt) { doZoomTool(); } }; /** The fit in window action. */ Action fitInWindowAction = new AbstractAction("Fit in window", getIcon("/org/lobobrowser/images/zoomout.png")) { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent evt) { doFitInWindow(); } }; /** * The Class ThumbAction. */ class ThumbAction extends AbstractAction implements PropertyChangeListener { /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1L; /** The is open. */ boolean isOpen = true; /** * Instantiates a new thumb action. */ public ThumbAction() { super("Hide thumbnails"); } /* * (non-Javadoc) * * @see java.beans.PropertyChangeListener#propertyChange(java.beans. * PropertyChangeEvent ) */ @Override public void propertyChange(PropertyChangeEvent evt) { int v = ((Integer) evt.getNewValue()).intValue(); if (v <= 1) { isOpen = false; putValue(ACTION_COMMAND_KEY, "Show thumbnails"); putValue(NAME, "Show thumbnails"); } else { isOpen = true; putValue(ACTION_COMMAND_KEY, "Hide thumbnails"); putValue(NAME, "Hide thumbnails"); } } /* * (non-Javadoc) * * @see java.awt.event.ActionListener#actionPerformed(java.awt.event. * ActionEvent) */ @Override public void actionPerformed(ActionEvent evt) { doThumbs(!isOpen); } } /** The thumb action. */ ThumbAction thumbAction = new ThumbAction(); /** The full screen action. */ Action fullScreenAction = new AbstractAction("Full screen", getIcon("/org/lobobrowser/images/go.png")) { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent evt) { doFullScreen((evt.getModifiers() & ActionEvent.SHIFT_MASK) != 0); } }; /** The next action. */ Action nextAction = new AbstractAction("Next", getIcon("/org/lobobrowser/images/forward.png")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent evt) { doNext(); } }; /** The prev action. */ Action prevAction = new AbstractAction("Prev", getIcon("/org/lobobrowser/images/back.png")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent evt) { doPrev(); } }; /** * Create a new PdfDialog based on a user, with or without a thumbnail * panel. * * @param useThumbs * true if the thumb panel should exist, false if not. */ public PdfDialog(boolean useThumbs) { super(TITLE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent evt) { doQuit(); } }); doThumb = useThumbs; init(); } /** * Initialize this PdfDialog by creating the GUI. */ @SuppressWarnings("deprecation") protected void init() { page = new PagePanel(); page.addKeyListener(this); if (doThumb) { split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); split.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, thumbAction); split.setOneTouchExpandable(true); thumbs = new ThumbPanel(null); thumbscroll = new JScrollPane(thumbs, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); split.setLeftComponent(thumbscroll); split.setRightComponent(page); getContentPane().add(split, BorderLayout.CENTER); } else { getContentPane().add(page, BorderLayout.CENTER); } JToolBar toolbar = new JToolBar(); toolbar.setFloatable(false); JButton jb; jb = new JButton(prevAction); jb.setText(""); toolbar.add(jb); pageField = new JTextField("-", 3); // pageField.setEnabled(false); pageField.setMaximumSize(new Dimension(45, 32)); pageField.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { doPageTyped(); } }); toolbar.add(pageField); jb = new JButton(nextAction); jb.setText(""); toolbar.add(jb); toolbar.add(Box.createHorizontalGlue()); fullScreenButton = new JToggleButton(fullScreenAction); fullScreenButton.setText(""); toolbar.add(fullScreenButton); fullScreenButton.setEnabled(true); toolbar.add(Box.createHorizontalGlue()); JToggleButton jtb; ButtonGroup bg = new ButtonGroup(); jtb = new JToggleButton(zoomToolAction); jtb.setText(""); bg.add(jtb); toolbar.add(jtb); jtb = new JToggleButton(fitInWindowAction); jtb.setText(""); bg.add(jtb); jtb.setSelected(true); toolbar.add(jtb); toolbar.add(Box.createHorizontalGlue()); jb = new JButton(printAction); jb.setText(""); toolbar.add(jb); getContentPane().add(toolbar, BorderLayout.NORTH); JMenuBar mb = new JMenuBar(); JMenu file = new JMenu("File"); file.add(closeAction); file.addSeparator(); file.add(pageSetupAction); file.add(printAction); file.addSeparator(); file.add(quitAction); mb.add(file); setJMenuBar(mb); setEnabling(); pack(); Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); int x = (screen.width - getWidth()) / 2; int y = (screen.height - getHeight()) / 2; setLocation(x, y); if (SwingUtilities.isEventDispatchThread()) { setVisible(true); } else { try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { setVisible(true); } }); } catch (InvocationTargetException ie) { // ignore } catch (InterruptedException ie) { // ignore } } } /** * Changes the displayed page, desyncing if we're not on the same page as a * presenter. * * @param pagenum * the page to display */ @Override public void gotoPage(int pagenum) { if (pagenum < 0) { pagenum = 0; } else if (pagenum >= curFile.getNumPages()) { pagenum = curFile.getNumPages() - 1; } forceGotoPage(pagenum); } /** * Changes the displayed page. * * @param pagenum * the page to display */ public void forceGotoPage(int pagenum) { if (pagenum <= 0) { pagenum = 0; } else if (pagenum >= curFile.getNumPages()) { pagenum = curFile.getNumPages() - 1; } curpage = pagenum; // update the page text field pageField.setText(String.valueOf(curpage + 1)); // fetch the page and show it in the appropriate place PDFPage pg = curFile.getPage(pagenum + 1); if (fspp != null) { fspp.showPage(pg); fspp.requestFocus(); } else { page.showPage(pg); page.requestFocus(); } // update the thumb panel if (doThumb) { thumbs.pageShown(pagenum); } // stop any previous page prepper, and start a new one if (pagePrep != null) { pagePrep.quit(); } pagePrep = new PagePreparer(pagenum); pagePrep.start(); setEnabling(); } /** * A class to pre-cache the next page for better UI response. */ class PagePreparer extends Thread { /** The waitfor page. */ int waitforPage; /** The prep page. */ int prepPage; /** * Creates a new PagePreparer to prepare the page after the current one. * * @param waitforPage * the current page number, 0 based */ public PagePreparer(int waitforPage) { setDaemon(true); setName(getClass().getName()); this.waitforPage = waitforPage; this.prepPage = waitforPage + 1; } /** * Quit. */ public void quit() { waitforPage = -1; } /* * (non-Javadoc) * * @see java.lang.Thread#run() */ @Override public void run() { Dimension size = null; Rectangle2D clip = null; if (fspp != null) { fspp.waitForCurrentPage(); size = fspp.getCurSize(); clip = fspp.getCurClip(); } else if (page != null) { page.waitForCurrentPage(); size = page.getCurSize(); clip = page.getCurClip(); } if (waitforPage == curpage) { PDFPage pdfPage = curFile.getPage(prepPage + 1, true); if ((pdfPage != null) && (waitforPage == curpage)) { pdfPage.getImage(size.width, size.height, clip, null, true, true); } } } } /** * Enable or disable all of the actions based on the current state. */ public void setEnabling() { boolean fileavailable = curFile != null; boolean pageshown = ((fspp != null) ? fspp.getPage() != null : page.getPage() != null); boolean printable = fileavailable && curFile.isPrintable(); pageField.setEnabled(fileavailable); printAction.setEnabled(printable); closeAction.setEnabled(fileavailable); fullScreenAction.setEnabled(pageshown); prevAction.setEnabled(pageshown); nextAction.setEnabled(pageshown); zoomToolAction.setEnabled(pageshown); fitInWindowAction.setEnabled(pageshown); } /** * open a URL to a PDF file. The file is read in and processed with an * in-memory buffer. * * @param url * the url * @throws IOException * Signals that an I/O exception has occurred. */ public void openFile(URL url) throws IOException { SSLCertificate.setCertificate(); URLConnection urlConnection = url.openConnection(); int contentLength = urlConnection.getContentLength(); InputStream istr = urlConnection.getInputStream(); byte[] byteBuf = new byte[contentLength]; int offset = 0; int read = 0; while (read >= 0) { read = istr.read(byteBuf, offset, contentLength - offset); if (read > 0) { offset += read; } } if (offset != contentLength) { throw new IOException("Could not read all of URL file."); } ByteBuffer buf = ByteBuffer.allocate(contentLength); buf.put(byteBuf); openPDFByteBuffer(buf, url.toString(), url.getFile()); } /** * <p> * Open a specific pdf file. Creates a DocumentInfo from the file, and opens * that. * </p> * * <p> * <b>Note:</b> Mapping the file locks the file until the PDFFile is closed. * </p> * * @param file * the file to open * @throws IOException * Signals that an I/O exception has occurred. */ public void openFile(File file) throws IOException { // first open the file for random access RandomAccessFile raf = new RandomAccessFile(file, "r"); // extract a file channel FileChannel channel = raf.getChannel(); // now memory-map a byte-buffer ByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); openPDFByteBuffer(buf, file.getPath(), file.getName()); raf.close(); } /** * <p> * Open a specific pdf file. Creates a DocumentInfo from the file, and opens * that. * </p> * * <p> * <b>Note:</b> By not memory mapping the file its contents are not locked * down while PDFFile is open. * </p> * * @param file * the file to open * @throws IOException * Signals that an I/O exception has occurred. */ public void openFileUnMapped(File file) throws IOException { DataInputStream istr = null; try { // load a pdf from a byte buffer // avoid using a RandomAccessFile but fill a ByteBuffer directly istr = new DataInputStream(new FileInputStream(file)); long len = file.length(); if (len > Integer.MAX_VALUE) { throw new IOException("File too long to decode: " + file.getName()); } int contentLength = (int) len; byte[] byteBuf = new byte[contentLength]; int offset = 0; int read = 0; while (read >= 0) { read = istr.read(byteBuf, offset, contentLength - offset); if (read > 0) { offset += read; } } ByteBuffer buf = ByteBuffer.allocate(contentLength); buf.put(byteBuf); openPDFByteBuffer(buf, file.getPath(), file.getName()); } catch (FileNotFoundException e) { logger.error(e.getMessage()); } catch (IOException e) { logger.error(e.getMessage()); } finally { if (istr != null) { try { istr.close(); } catch (Exception e) { // ignore error on close } } } } /** * open the ByteBuffer data as a PDFFile and start to process it. * * @param buf * the buf * @param path * the path * @param name * the name */ private void openPDFByteBuffer(ByteBuffer buf, String path, String name) { // create a PDFFile from the data PDFFile newfile = null; try { newfile = new PDFFile(buf); } catch (IOException ioe) { openError(path + " doesn't appear to be a PDF file."); return; } // Now that we're reasonably sure this document is real, close the // old one. doClose(); // set up our document this.curFile = newfile; docName = name; setTitle(TITLE + ": " + docName); // set up the thumbnails if (doThumb) { thumbs = new ThumbPanel(curFile); thumbs.addPageChangeListener(this); thumbscroll.getViewport().setView(thumbs); thumbscroll.getViewport().setBackground(Color.gray); } setEnabling(); // display page 1. forceGotoPage(0); // if the PDF has an outline, display it. try { outline = curFile.getOutline(); } catch (IOException ioe) { } if (outline != null) { if (outline.getChildCount() > 0) { olf = new JDialog(this, "Outline"); olf.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); olf.setLocation(this.getLocation()); JTree jt = new JTree(outline); jt.setRootVisible(false); jt.addTreeSelectionListener(this); JScrollPane jsp = new JScrollPane(jt); olf.getContentPane().add(jsp); olf.pack(); olf.setVisible(true); } else { if (olf != null) { olf.setVisible(false); olf = null; } } } } /** * Display a dialog indicating an error. * * @param message * the message */ public void openError(String message) { JOptionPane.showMessageDialog(split, message, "Error opening file", JOptionPane.ERROR_MESSAGE); } /** * A file filter for PDF files. */ FileFilter pdfFilter = new FileFilter() { @Override public boolean accept(File f) { return f.isDirectory() || f.getName().endsWith(".pdf"); } @Override public String getDescription() { return "Choose a PDF file"; } }; /** The prev dir choice. */ private File prevDirChoice; /** * Ask the user for a PDF file to open from the local file system. */ public void doOpen() { try { JFileChooser fc = new JFileChooser(); fc.setCurrentDirectory(prevDirChoice); fc.setFileFilter(pdfFilter); fc.setMultiSelectionEnabled(false); int returnVal = fc.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { try { prevDirChoice = fc.getSelectedFile(); openFile(fc.getSelectedFile()); } catch (IOException e) { logger.error(e.getMessage()); } } } catch (Exception e) { JOptionPane.showMessageDialog(split, "Opening files from your local " + "disk is not available\nfrom the " + "Java Web Start version of this " + "program.\n", "Error opening directory", JOptionPane.ERROR_MESSAGE); logger.error(e.getMessage()); } } /** * Open a local file, given a string filename. * * @param name * the name of the file to open */ public void doOpen(String name) { try { openFile(new URL(name)); } catch (IOException ioe) { try { openFile(new File(name)); } catch (IOException ex) { LogManager.getLogger(PdfDialog.class).debug(ex); } } } /** * Posts the Page Setup dialog. */ public void doPageSetup() { PrinterJob pjob = PrinterJob.getPrinterJob(); pformat = pjob.pageDialog(pformat); } /** * A thread for printing in. */ class PrintThread extends Thread { /** The pt pages. */ PDFPrintPage ptPages; /** The pt pjob. */ PrinterJob ptPjob; /** * Instantiates a new prints the thread. * * @param pages * the pages * @param pjob * the pjob */ public PrintThread(PDFPrintPage pages, PrinterJob pjob) { ptPages = pages; ptPjob = pjob; setName(getClass().getName()); } /* * (non-Javadoc) * * @see java.lang.Thread#run() */ @Override public void run() { try { ptPages.show(ptPjob); ptPjob.print(); } catch (PrinterException pe) { JOptionPane.showMessageDialog(PdfDialog.this, "Printing Error: " + pe.getMessage(), "Print Aborted", JOptionPane.ERROR_MESSAGE); } ptPages.hide(); } } /** * Print the current document. */ public void doPrint() { PrinterJob pjob = PrinterJob.getPrinterJob(); pjob.setJobName(docName); Book book = new Book(); PDFPrintPage pages = new PDFPrintPage(curFile); book.append(pages, pformat, curFile.getNumPages()); pjob.setPageable(book); if (pjob.printDialog()) { new PrintThread(pages, pjob).start(); } } /** * Close the current document. */ public void doClose() { if (thumbs != null) { thumbs.stop(); } if (olf != null) { olf.setVisible(false); olf = null; } if (doThumb) { thumbs = new ThumbPanel(null); thumbscroll.getViewport().setView(thumbs); } setFullScreenMode(false, false); page.showPage(null); curFile = null; setTitle(TITLE); setEnabling(); } /** * Shuts down all known threads. This ought to cause the JVM to quit if the * PdfDialog is the only application running. */ public void doQuit() { doClose(); dispose(); } /** * Turns on zooming. */ public void doZoomTool() { if (fspp == null) { page.useZoomTool(true); } } /** * Turns off zooming; makes the page fit in the window. */ public void doFitInWindow() { if (fspp == null) { page.useZoomTool(false); page.setClip(null); } } /** * Shows or hides the thumbnails by moving the split pane divider. * * @param show * the show */ public void doThumbs(boolean show) { if (show) { split.setDividerLocation( thumbs.getPreferredSize().width + thumbscroll.getVerticalScrollBar().getWidth() + 4); } else { split.setDividerLocation(0); } } /** * Enter full screen mode. * * @param force * true if the user should be prompted for a screen to use in a * multiple-monitor setup. If false, the user will only be * prompted once. */ public void doFullScreen(boolean force) { setFullScreenMode(fullScreen == null, force); } /** * Do zoom. * * @param factor * the factor */ public void doZoom(double factor) { } // public void doOpenMeetingDoc(DocumentInfo doc) { // } /** * Goes to the next page. */ public void doNext() { gotoPage(curpage + 1); } /** * Goes to the previous page. */ public void doPrev() { gotoPage(curpage - 1); } /** * Goes to the first page. */ public void doFirst() { gotoPage(0); } /** * Goes to the last page. */ public void doLast() { gotoPage(curFile.getNumPages() - 1); } /** * Goes to the page that was typed in the page number text field. */ public void doPageTyped() { int pagenum = -1; try { pagenum = Integer.parseInt(pageField.getText()) - 1; } catch (NumberFormatException nfe) { } if (pagenum >= curFile.getNumPages()) { pagenum = curFile.getNumPages() - 1; } if (pagenum >= 0) { if (pagenum != curpage) { gotoPage(pagenum); } } else { pageField.setText(String.valueOf(curpage)); } } /** * Runs the FullScreenMode change in another thread. */ class PerformFullScreenMode implements Runnable { /** The force. */ boolean force; /** * Instantiates a new perform full screen mode. * * @param forcechoice * the forcechoice */ public PerformFullScreenMode(boolean forcechoice) { force = forcechoice; } /* * (non-Javadoc) * * @see java.lang.Runnable#run() */ @Override public void run() { fspp = new PagePanel(); fspp.setBackground(Color.black); page.showPage(null); fullScreen = new FullScreenWindow(fspp, force); fspp.addKeyListener(PdfDialog.this); gotoPage(curpage); fullScreenAction.setEnabled(true); } } /** * Starts or ends full screen mode. * * @param full * true to enter full screen mode, false to leave * @param force * true if the user should be prompted for a screen to use the * second time full screen mode is entered. */ public void setFullScreenMode(boolean full, boolean force) { // curpage= -1; if (full && (fullScreen == null)) { fullScreenAction.setEnabled(false); new Thread(new PerformFullScreenMode(force), getClass().getName() + ".setFullScreenMode").start(); fullScreenButton.setSelected(true); } else if (!full && (fullScreen != null)) { fullScreen.close(); fspp = null; fullScreen = null; gotoPage(curpage); fullScreenButton.setSelected(false); } } /** * Handle a key press for navigation. * * @param evt * the evt */ @Override public void keyPressed(KeyEvent evt) { int code = evt.getKeyCode(); if (code == KeyEvent.VK_LEFT) { doPrev(); } else if (code == KeyEvent.VK_RIGHT) { doNext(); } else if (code == KeyEvent.VK_UP) { doPrev(); } else if (code == KeyEvent.VK_DOWN) { doNext(); } else if (code == KeyEvent.VK_HOME) { doFirst(); } else if (code == KeyEvent.VK_END) { doLast(); } else if (code == KeyEvent.VK_PAGE_UP) { doPrev(); } else if (code == KeyEvent.VK_PAGE_DOWN) { doNext(); } else if (code == KeyEvent.VK_SPACE) { doNext(); } else if (code == KeyEvent.VK_ESCAPE) { setFullScreenMode(false, false); } } /** * Combines numeric key presses to build a multi-digit page number. */ class PageBuilder implements Runnable { /** The value. */ int value = 0; /** The timeout. */ long timeout; /** The anim. */ Thread anim; /** The Constant TIMEOUT. */ static final long TIMEOUT = 500; /** * add the digit to the page number and start the timeout thread. * * @param keyval * the keyval */ public synchronized void keyTyped(int keyval) { value = (value * 10) + keyval; timeout = System.currentTimeMillis() + TIMEOUT; if (anim == null) { anim = new Thread(this); anim.setName(getClass().getName()); anim.start(); } } /** * waits for the timeout, and if time expires, go to the specified page * number. */ @Override public void run() { long now, then; synchronized (this) { now = System.currentTimeMillis(); then = timeout; } while (now < then) { try { Thread.sleep(timeout - now); } catch (InterruptedException ie) { } synchronized (this) { now = System.currentTimeMillis(); then = timeout; } } synchronized (this) { gotoPage(value - 1); anim = null; value = 0; } } } /** The pb. */ PageBuilder pb = new PageBuilder(); /* * (non-Javadoc) * * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent) */ @Override public void keyReleased(KeyEvent evt) { } /** * gets key presses and tries to build a page if they're numeric. * * @param evt * the evt */ @Override public void keyTyped(KeyEvent evt) { char key = evt.getKeyChar(); if ((key >= '0') && (key <= '9')) { int val = key - '0'; pb.keyTyped(val); } } /** * Someone changed the selection of the outline tree. Go to the new page. * * @param e * the e */ @Override public void valueChanged(TreeSelectionEvent e) { if (e.isAddedPath()) { OutlineNode node = (OutlineNode) e.getPath().getLastPathComponent(); if (node == null) { return; } try { PDFAction action = node.getAction(); if (action == null) { return; } if (action instanceof GoToAction) { PDFDestination dest = ((GoToAction) action).getDestination(); if (dest == null) { return; } PDFObject page = dest.getPage(); if (page == null) { return; } int pageNum = curFile.getPageNumber(page); if (pageNum >= 0) { gotoPage(pageNum); } } } catch (IOException ioe) { logger.error(ioe.getMessage()); } } } }