001: /*
002: * $Id: ThumbPanel.java,v 1.3 2007/12/20 18:33:33 rbair Exp $
003: *
004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005: * Santa Clara, California 95054, U.S.A. All rights reserved.
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020: */
021:
022: package com.sun.pdfview;
023:
024: import java.awt.Color;
025: import java.awt.Dimension;
026: import java.awt.Graphics;
027: import java.awt.Image;
028: import java.awt.Rectangle;
029: import java.awt.event.MouseAdapter;
030: import java.awt.event.MouseEvent;
031: import java.awt.image.BufferedImage;
032: import java.awt.image.ImageObserver;
033:
034: import javax.swing.JPanel;
035: import javax.swing.JViewport;
036: import javax.swing.Scrollable;
037: import javax.swing.SwingUtilities;
038:
039: /**
040: * A panel of thumbnails, one for each page of a PDFFile. You can add
041: * a PageChangeListener to be informed of when the user clicks one of the
042: * pages.
043: */
044: public class ThumbPanel extends JPanel implements Runnable, Scrollable,
045: ImageObserver {
046: /** The PDFFile being displayed */
047: PDFFile file;
048:
049: /** Array of images, one per page in the file */
050: Image images[];
051:
052: /** Size of the border between images*/
053: int border = 2;
054:
055: /**
056: * Height of each line. Thumbnails will be scaled to this height
057: * (minus the border).
058: */
059: int lineheight = 96 + border;
060:
061: /**
062: * Guesstimate of the width of a thumbnail that hasn't been processed
063: * yet.
064: */
065: int defaultWidth = (lineheight - border) * 4 / 3;
066:
067: /**
068: * Array of the x locations of each of the thumbnails. Every 0 stored
069: * in this array indicates the start of a new line of thumbnails.
070: */
071: int xloc[];
072:
073: /** Thread that renders each thumbnail in turn */
074: Thread anim;
075:
076: /** Which thumbnail is selected, or -1 if no thumbnail selected. */
077: int showing = -1;
078:
079: /**
080: * Which thumbnail needs to be drawn next, or -1 if the previous
081: * needy thumbnail is being processed.
082: */
083: int needdrawn = -1;
084:
085: /**
086: * Whether the default width has been guesstimated for this PDFFile
087: * yet.
088: */
089: boolean defaultNotSet = true;
090:
091: /** The PageChangeListener that is listening for page changes */
092: PageChangeListener listener;
093:
094: // Flag flag= new Flag();
095:
096: /**
097: * Creates a new ThumbPanel based on a PDFFile. The file may be null.
098: * Automatically starts rendering thumbnails for that file.
099: */
100: public ThumbPanel(PDFFile file) {
101: super ();
102: this .file = file;
103: if (file != null) {
104: int count = file.getNumPages();
105: images = new Image[count];
106: xloc = new int[count];
107: setPreferredSize(new Dimension(defaultWidth, 200));
108: addMouseListener(new MouseAdapter() {
109: public void mouseClicked(MouseEvent evt) {
110: handleClick(evt.getX(), evt.getY());
111: }
112: });
113: anim = new Thread(this );
114: anim.start();
115: } else {
116: images = new Image[0];
117: setPreferredSize(new Dimension(defaultWidth, 200));
118: }
119: }
120:
121: /**
122: * Renders each of the pages in the PDFFile into a thumbnail.
123: * Preferentially works on the needdrawn thumbnail, otherwise, go in
124: * order.
125: */
126: public void run() {
127: int workingon = 0; // the thumbnail we'll be rendering next.
128:
129: while (anim == Thread.currentThread()) {
130:
131: if (needdrawn >= 0) {
132: workingon = needdrawn;
133: needdrawn = -1;
134: }
135:
136: // find an unfinished page
137: int loop;
138: for (loop = images.length; loop > 0; loop--) {
139: if (images[workingon] == null) {
140: break;
141: }
142: workingon++;
143: if (workingon >= images.length) {
144: workingon = 0;
145: }
146: }
147: if (loop == 0) {
148: // done all pages.
149: break;
150: }
151:
152: // build the page
153: try {
154: int pagetoread = workingon + 1;
155: // int pagetoread= 1;
156: // System.out.println("Read page: " + pagetoread);
157: PDFPage p = file.getPage(pagetoread, true);
158:
159: int wid = (int) Math.ceil((lineheight - border)
160: * p.getAspectRatio());
161: // if (!p.isFinished()) {
162: // System.out.println("Page not finished!");
163: // p.waitForFinish();
164: // }
165: // flag.clear();
166: // int pagetowrite= 0;
167: int pagetowrite = workingon;
168:
169: Image i = p.getImage(wid, (lineheight - border), null,
170: this , true, true);
171:
172: // images[0] = i;
173: images[pagetowrite] = i;
174:
175: // flag.waitForFlag();
176: if (defaultNotSet) {
177: defaultNotSet = false;
178: setDefaultWidth(wid);
179: }
180: repaint();
181: } catch (Exception e) {
182: e.printStackTrace();
183:
184: int size = lineheight - border;
185: images[workingon] = new BufferedImage(size, size,
186: BufferedImage.TYPE_BYTE_BINARY);
187: }
188: }
189: }
190:
191: /**
192: * Adds a PageChangeListener to receive notification of page clicks.
193: */
194: public void addPageChangeListener(PageChangeListener pl) {
195: // [[MW: should be an array list instead of only one]]
196: listener = pl;
197: }
198:
199: /**
200: * Removes a PageChangeListener from the notification list.
201: */
202: public void removePageChangeListener(PageChangeListener pl) {
203: // [[MW: should be an array list instead of only one]]
204: listener = null;
205: }
206:
207: /**
208: * Stops the render thread. Be sure to call this before dropping
209: * a ThumbPanel.
210: */
211: public void stop() {
212: anim = null;
213: }
214:
215: /**
216: * Sets the default width of an un-processed thumbnail.
217: * @param width the width of an unknown thumbnail, in pixels.
218: */
219: public void setDefaultWidth(int width) {
220: defaultWidth = width;
221: // setPreferredSize(new Dimension(width, lineheight));
222: }
223:
224: /**
225: * Handles a mouse click in the panel. Figures out which page was
226: * clicked, and calls showPage.
227: * @param x the x coordinate of the mouse click
228: * @param y the y coordinate of the mouse click
229: */
230: public void handleClick(int x, int y) {
231: int linecount = -1;
232: int line = y / lineheight;
233: // run through the thumbnail locations, counting new lines
234: // until the appropriate line is reached.
235: for (int i = 0; i < xloc.length; i++) {
236: if (xloc[i] == 0) {
237: linecount++;
238: }
239: if (line == linecount
240: && xloc[i]
241: + (images[i] != null ? images[i]
242: .getWidth(null) : defaultWidth) > x) {
243: showPage(i);
244: break;
245: }
246: }
247: }
248:
249: /**
250: * Sets the currently viewed page, indicates it with a highlight
251: * border, and makes sure the thumbnail is visible.
252: */
253: public void pageShown(int pagenum) {
254: if (showing != pagenum) {
255: // FIND THE SELECTION RECTANGLE
256: // getViewPort.scrollRectToVisible(r);
257: if (pagenum >= 0 && getParent() instanceof JViewport) {
258: int y = -lineheight;
259: for (int i = 0; i <= pagenum; i++) {
260: if (xloc[i] == 0) {
261: y += lineheight;
262: }
263: }
264: Rectangle r = new Rectangle(xloc[pagenum], y,
265: (images[pagenum] == null ? defaultWidth
266: : images[pagenum].getWidth(null)),
267: lineheight);
268: scrollRectToVisible(r);
269: }
270: showing = pagenum;
271: repaint();
272: }
273: }
274:
275: /**
276: * Notifies the listeners that a page has been selected. Performs
277: * the notification in the AWT thread.
278: * Also highlights the selected page. Does this first so that feedback
279: * is immediate.
280: */
281: public void showPage(int pagenum) {
282: pageShown(pagenum);
283: SwingUtilities.invokeLater(new GotoLater(pagenum));
284: }
285:
286: /**
287: * Simple runnable to tell listeners that the page has changed.
288: */
289: class GotoLater implements Runnable {
290: int page;
291:
292: public GotoLater(int pagenum) {
293: page = pagenum;
294: }
295:
296: public void run() {
297: if (listener != null) {
298: listener.gotoPage(page);
299: }
300: }
301: }
302:
303: /**
304: * Updates the positions of the thumbnails, and draws them to the
305: * screen.
306: */
307: public void paint(Graphics g) {
308: int x = 0;
309: int y = 0;
310: int maxwidth = 0;
311: Rectangle clip = g.getClipBounds();
312: g.setColor(Color.gray);
313: int width = getWidth();
314: g.fillRect(0, 0, width, getHeight());
315:
316: for (int i = 0; i < images.length; i++) {
317: // calculate the x location of the thumbnail, based on its width
318: int w = defaultWidth + 2;
319: if (images[i] != null) {
320: w = (int) images[i].getWidth(null) + 2;
321: }
322: // need a new line?
323: if (x + w > width && x != 0) {
324: x = 0;
325: y += lineheight;
326: }
327: // if the thumbnail is visible, draw it.
328: if (clip.intersects(new Rectangle(x, y, w, lineheight))) {
329: if (images[i] != null) {
330: // thumbnail is ready.
331: g.drawImage(images[i], x + 1, y + 1, this );
332: } else {
333: // thumbnail isn't ready. Remember that we need it...
334: if (needdrawn == -1) {
335: needdrawn = i;
336: }
337: // ... and draw a blank thumbnail.
338: g.setColor(Color.lightGray);
339: g.fillRect(x + 1, y + 1, w - border, lineheight
340: - border);
341: g.setColor(Color.darkGray);
342: g.drawRect(x + 1, y + 1, w - border - 1, lineheight
343: - border - 1);
344: }
345: // draw the selection highlight if needed.
346: if (i == showing) {
347: g.setColor(Color.red);
348: g.drawRect(x, y, w - 1, lineheight - 1);
349: g.drawRect(x + 1, y + 1, w - 3, lineheight - 3);
350: }
351: }
352: // save the x location of this thumbnail.
353: xloc[i] = x;
354: x += w;
355: // remember the longest line
356: if (x > maxwidth) {
357: maxwidth = x;
358: }
359: }
360: // if there weren't any thumbnails, make a default line width
361: if (maxwidth == 0) {
362: maxwidth = defaultWidth;
363: }
364: Dimension d = getPreferredSize();
365: if (d.height != y + lineheight || d.width != maxwidth) {
366: setPreferredSize(new Dimension(maxwidth, y + lineheight));
367: revalidate();
368: }
369: }
370:
371: /**
372: * Handles notification of any image updates. Not used any more.
373: */
374: public boolean imageUpdate(Image img, int infoflags, int x, int y,
375: int width, int height) {
376: // if ((infoflags & ALLBITS)!=0) {
377: // flag.set();
378: // }
379: return ((infoflags & (ALLBITS | ERROR | ABORT)) == 0);
380: }
381:
382: public Dimension getPreferredScrollableViewportSize() {
383: return getPreferredSize();
384: }
385:
386: public int getScrollableBlockIncrement(Rectangle visrect,
387: int orientation, int direction) {
388: return Math.max(lineheight, (visrect.height / lineheight)
389: * lineheight);
390: }
391:
392: public boolean getScrollableTracksViewportHeight() {
393: return false;
394: }
395:
396: public boolean getScrollableTracksViewportWidth() {
397: return true;
398: }
399:
400: public int getScrollableUnitIncrement(Rectangle visrect,
401: int orientation, int direction) {
402: return lineheight;
403: }
404: }
|