/* * $Id: PDFViewer.java,v 1.9 2009/03/21 23:45:38 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.BorderLayout; import java.awt.Color; import java.awt.Dimension; 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.awt.Toolkit; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.*; import java.io.IOException; import java.io.RandomAccessFile; import java.net.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.logging.Level; import java.util.logging.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.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.filechooser.FileFilter; import javax.swing.SwingUtilities; import com.sun.pdfview.action.GoToAction; import com.sun.pdfview.action.PDFAction; import java.lang.reflect.InvocationTargetException; /** * A demo PDF Viewer application. */ public class PDFViewer extends JFrame implements KeyListener, TreeSelectionListener, PageChangeListener { 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); if (icon == null) { System.out.println("Couldn't find " + url); } } catch (Exception e) { System.out.println("Couldn't find " + getClass().getName() + "/" + name); e.printStackTrace(); } return icon; } /// FILE MENU Action openAction = new AbstractAction("Open...") { public void actionPerformed(ActionEvent evt) { doOpen(); } }; Action pageSetupAction = new AbstractAction("Page setup...") { public void actionPerformed(ActionEvent evt) { doPageSetup(); } }; Action printAction = new AbstractAction("Print...", getIcon("gfx/print.gif")) { public void actionPerformed(ActionEvent evt) { doPrint(); } }; Action closeAction = new AbstractAction("Close") { public void actionPerformed(ActionEvent evt) { doClose(); } }; Action quitAction = new AbstractAction("Quit") { public void actionPerformed(ActionEvent evt) { doQuit(); } }; class ZoomAction extends AbstractAction { double zoomfactor = 1.0; public ZoomAction(String name, double factor) { super(name); zoomfactor = factor; } public ZoomAction(String name, Icon icon, double factor) { super(name, icon); zoomfactor = factor; } public void actionPerformed(ActionEvent evt) { doZoom(zoomfactor); } } ZoomAction zoomInAction = new ZoomAction("Zoom in", getIcon("gfx/zoomin.gif"), 2.0); ZoomAction zoomOutAction = new ZoomAction("Zoom out", getIcon("gfx/zoomout.gif"), 0.5); Action zoomToolAction = new AbstractAction("", getIcon("gfx/zoom.gif")) { public void actionPerformed(ActionEvent evt) { doZoomTool(); } }; Action fitInWindowAction = new AbstractAction("Fit in window", getIcon("gfx/fit.gif")) { public void actionPerformed(ActionEvent evt) { doFitInWindow(); } }; class ThumbAction extends AbstractAction implements PropertyChangeListener { boolean isOpen = true; public ThumbAction() { super("Hide thumbnails"); } 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"); } } public void actionPerformed(ActionEvent evt) { doThumbs(!isOpen); } } ThumbAction thumbAction = new ThumbAction(); Action fullScreenAction = new AbstractAction("Full screen", getIcon("gfx/fullscrn.gif")) { public void actionPerformed(ActionEvent evt) { doFullScreen((evt.getModifiers() & evt.SHIFT_MASK) != 0); } }; Action nextAction = new AbstractAction("Next", getIcon("gfx/next.gif")) { public void actionPerformed(ActionEvent evt) { doNext(); } }; Action firstAction = new AbstractAction("First", getIcon("gfx/first.gif")) { public void actionPerformed(ActionEvent evt) { doFirst(); } }; Action lastAction = new AbstractAction("Last", getIcon("gfx/last.gif")) { public void actionPerformed(ActionEvent evt) { doLast(); } }; Action prevAction = new AbstractAction("Prev", getIcon("gfx/prev.gif")) { public void actionPerformed(ActionEvent evt) { doPrev(); } }; /** * Create a new PDFViewer based on a user, with or without a thumbnail * panel. * @param useThumbs true if the thumb panel should exist, false if not. */ public PDFViewer(boolean useThumbs) { super(TITLE); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { doQuit(); } }); doThumb = useThumbs; init(); } /** * Initialize this PDFViewer by creating the GUI. */ protected void init() { page = new PagePanel(); page.addKeyListener(this); if (doThumb) { split = new JSplitPane(split.HORIZONTAL_SPLIT); split.addPropertyChangeListener(split.DIVIDER_LOCATION_PROPERTY, thumbAction); split.setOneTouchExpandable(true); thumbs = new ThumbPanel(null); thumbscroll = new JScrollPane(thumbs, thumbscroll.VERTICAL_SCROLLBAR_ALWAYS, thumbscroll.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(firstAction); jb.setText(""); toolbar.add(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() { public void actionPerformed(ActionEvent evt) { doPageTyped(); } }); toolbar.add(pageField); jb = new JButton(nextAction); jb.setText(""); toolbar.add(jb); jb = new JButton(lastAction); 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(openAction); file.add(closeAction); file.addSeparator(); file.add(pageSetupAction); file.add(printAction); file.addSeparator(); file.add(quitAction); mb.add(file); JMenu view = new JMenu("View"); JMenu zoom = new JMenu("Zoom"); zoom.add(zoomInAction); zoom.add(zoomOutAction); zoom.add(fitInWindowAction); zoom.setEnabled(false); view.add(zoom); view.add(fullScreenAction); if (doThumb) { view.addSeparator(); view.add(thumbAction); } mb.add(view); 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()) { show(); } else { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { show(); } }); } 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 */ 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; } // System.out.println("Going to page " + pagenum); 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 { int waitforPage; 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; } public void quit() { waitforPage = -1; } public void run() { Dimension size = null; Rectangle2D clip = null; // wait for the current page // System.out.println("Preparer waiting for page " + (waitforPage + 1)); 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) { // don't go any further if the user changed pages. // System.out.println("Preparer generating page " + (prepPage + 2)); PDFPage pdfPage = curFile.getPage(prepPage + 1, true); if (pdfPage != null && waitforPage == curpage) { // don't go any further if the user changed pages // System.out.println("Generating image for page " + (prepPage + 2)); pdfPage.getImage(size.width, size.height, clip, null, true, true); // System.out.println("Generated image for page "+ (prepPage+2)); } } } } /** * 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); firstAction.setEnabled(fileavailable); lastAction.setEnabled(fileavailable); zoomToolAction.setEnabled(pageshown); fitInWindowAction.setEnabled(pageshown); zoomInAction.setEnabled(pageshown); zoomOutAction.setEnabled(pageshown); } /** * open a URL to a PDF file. The file is read in and processed * with an in-memory buffer. * * @param url * @throws java.io.IOException */ public void openFile(URL url) throws IOException { 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 */ 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()); } /** * <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 */ 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 fnfe) { fnfe.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } 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 * @param path */ 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." + "\n: " + ioe.getMessage ()); 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(JDialog.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. */ 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() { public boolean accept(File f) { return f.isDirectory() || f.getName().endsWith(".pdf"); } public String getDescription() { return "Choose a PDF file"; } }; 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 ioe) { ioe.printStackTrace(); } } } 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); e.printStackTrace(); } } /** * Open a local file, given a string filename * @param name the name of the file to open */ public void doOpen(String name) { try { URL url = new URL(name); openFile(new URL(name)); } catch (IOException ioe) { try { openFile(new File(name)); } catch (IOException ex) { Logger.getLogger(PDFViewer.class.getName()).log(Level.SEVERE, null, 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 { PDFPrintPage ptPages; PrinterJob ptPjob; public PrintThread(PDFPrintPage pages, PrinterJob pjob) { ptPages = pages; ptPjob = pjob; setName(getClass().getName()); } public void run() { try { ptPages.show(ptPjob); ptPjob.print(); } catch (PrinterException pe) { JOptionPane.showMessageDialog(PDFViewer.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 PDFViewer is the only application running. */ public void doQuit() { // if (thumbs != null) { // thumbs.stop(); // } doClose(); dispose(); System.exit(0); } /** * 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 */ public void doThumbs(boolean show) { if (show) { split.setDividerLocation((int) thumbs.getPreferredSize().width + (int) 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); } 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 { boolean force; public PerformFullScreenMode(boolean forcechoice) { force = forcechoice; } public void run() { fspp = new PagePanel(); fspp.setBackground(Color.black); page.showPage(null); fullScreen = new FullScreenWindow(fspp, force); fspp.addKeyListener(PDFViewer.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); } } public static void main(String args[]) { String fileName = null; boolean useThumbs = true; for (int i = 0; i < args.length; i++) { if (args[i].equalsIgnoreCase("-noThumb")) { useThumbs = false; } else if (args[i].equalsIgnoreCase("-help") || args[i].equalsIgnoreCase("-h") || args[i].equalsIgnoreCase("-?")) { System.out.println("java com.sun.awc.PDFViewer [flags] [file]"); System.out.println("flags: [-noThumb] [-help or -h or -?]"); System.exit(0); } else { fileName = args[i]; } } // start the viewer PDFViewer viewer; viewer = new PDFViewer(useThumbs); if (fileName != null) { viewer.doOpen(fileName); } } /** * Handle a key press for navigation */ public void keyPressed(KeyEvent evt) { int code = evt.getKeyCode(); if (code == evt.VK_LEFT) { doPrev(); } else if (code == evt.VK_RIGHT) { doNext(); } else if (code == evt.VK_UP) { doPrev(); } else if (code == evt.VK_DOWN) { doNext(); } else if (code == evt.VK_HOME) { doFirst(); } else if (code == evt.VK_END) { doLast(); } else if (code == evt.VK_PAGE_UP) { doPrev(); } else if (code == evt.VK_PAGE_DOWN) { doNext(); } else if (code == evt.VK_SPACE) { doNext(); } else if (code == evt.VK_ESCAPE) { setFullScreenMode(false, false); } } /** * Combines numeric key presses to build a multi-digit page number. */ class PageBuilder implements Runnable { int value = 0; long timeout; Thread anim; static final long TIMEOUT = 500; /** add the digit to the page number and start the timeout thread */ 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 */ 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; } } } PageBuilder pb = new PageBuilder(); public void keyReleased(KeyEvent evt) { } /** * gets key presses and tries to build a page if they're numeric */ 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. */ 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) { ioe.printStackTrace(); } } } }