001: /*
002: * $Id: PagePanel.java,v 1.2 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.MouseEvent;
030: import java.awt.event.MouseListener;
031: import java.awt.event.MouseMotionListener;
032: import java.awt.geom.AffineTransform;
033: import java.awt.geom.NoninvertibleTransformException;
034: import java.awt.geom.Rectangle2D;
035: import java.awt.image.ImageObserver;
036:
037: import javax.swing.JPanel;
038:
039: /**
040: * A Swing-based panel that displays a PDF page image. If the zoom tool
041: * is in use, allows the user to select a particular region of the image to
042: * be zoomed.
043: */
044: public class PagePanel extends JPanel implements ImageObserver,
045: MouseListener, MouseMotionListener {
046: /** The image of the rendered PDF page being displayed */
047: Image currentImage;
048:
049: /** The current PDFPage that was rendered into currentImage */
050: PDFPage currentPage;
051:
052: /* the current transform from device space to page space */
053: AffineTransform currentXform;
054:
055: /** The horizontal offset of the image from the left edge of the panel */
056: int offx;
057:
058: /** The vertical offset of the image from the top of the panel */
059: int offy;
060:
061: /** the current clip, in device space */
062: Rectangle2D clip;
063:
064: /** the clipping region used for the image */
065: Rectangle2D prevClip;
066:
067: /** the size of the image */
068: Dimension prevSize;
069:
070: /** the zooming marquee */
071: Rectangle zoomRect;
072:
073: /** whether the zoom tool is enabled */
074: boolean useZoom = false;
075:
076: // /** a listener for page changes */
077: // PageChangeListener listener;
078:
079: /** a flag indicating whether the current page is done or not. */
080: Flag flag = new Flag();
081:
082: // Color boxcolor= new Color(255,200,200);
083:
084: /**
085: * Create a new PagePanel, with a default size of 800 by 600 pixels.
086: */
087: public PagePanel() {
088: super ();
089: setPreferredSize(new Dimension(800, 600));
090: setFocusable(true);
091: addMouseListener(this );
092: addMouseMotionListener(this );
093: }
094:
095: /**
096: * Stop the generation of any previous page, and draw the new one.
097: * @param page the PDFPage to draw.
098: */
099: public synchronized void showPage(PDFPage page) {
100: // stop drawing the previous page
101: if (currentPage != null && prevSize != null) {
102: currentPage.stop(prevSize.width, prevSize.height, prevClip);
103: }
104:
105: // set up the new page
106: currentPage = page;
107:
108: if (page == null) {
109: // no page
110: currentImage = null;
111: clip = null;
112: currentXform = null;
113: repaint();
114: } else {
115: // start drawing -- clear the flag to indicate we're in progress.
116: flag.clear();
117: // System.out.println(" flag cleared");
118:
119: Dimension sz = getSize();
120: if (sz.width + sz.height == 0) {
121: // no image to draw.
122: return;
123: }
124: // System.out.println("Ratios: scrn="+((float)sz.width/sz.height)+
125: // ", clip="+(clip==null ? 0 : clip.getWidth()/clip.getHeight()));
126:
127: // calculate the clipping rectangle in page space from the
128: // desired clip in screen space.
129: Rectangle2D useClip = clip;
130: if (clip != null && currentXform != null) {
131: useClip = currentXform.createTransformedShape(clip)
132: .getBounds2D();
133: }
134:
135: Dimension pageSize = page.getUnstretchedSize(sz.width,
136: sz.height, useClip);
137:
138: // get the new image
139: currentImage = page.getImage(pageSize.width,
140: pageSize.height, useClip, this );
141:
142: // calculate the transform from screen to page space
143: currentXform = page.getInitialTransform(pageSize.width,
144: pageSize.height, useClip);
145: try {
146: currentXform = currentXform.createInverse();
147: } catch (NoninvertibleTransformException nte) {
148: System.out.println("Error inverting page transform!");
149: nte.printStackTrace();
150: }
151:
152: prevClip = useClip;
153: prevSize = pageSize;
154:
155: repaint();
156: }
157: }
158:
159: /**
160: * @deprecated
161: */
162: public synchronized void flush() {
163: // images.clear();
164: // lruPages.clear();
165: // nextPage= null;
166: // nextImage= null;
167: }
168:
169: /**
170: * Draw the image.
171: */
172: public void paint(Graphics g) {
173: Dimension sz = getSize();
174: g.setColor(getBackground());
175: g.fillRect(0, 0, getWidth(), getHeight());
176: if (currentImage == null) {
177: // No image -- draw an empty box
178: // [[MW: remove the scary red X]]
179: // g.setColor(Color.red);
180: // g.drawLine(0, 0, getWidth(), getHeight());
181: // g.drawLine(0, getHeight(), getWidth(), 0);
182: g.setColor(Color.black);
183: g.drawString("No page selected", getWidth() / 2 - 30,
184: getHeight() / 2);
185: } else {
186: // draw the image
187: int imwid = currentImage.getWidth(null);
188: int imhgt = currentImage.getHeight(null);
189:
190: // draw it centered within the panel
191: offx = (sz.width - imwid) / 2;
192: offy = (sz.height - imhgt) / 2;
193:
194: if ((imwid == sz.width && imhgt <= sz.height)
195: || (imhgt == sz.height && imwid <= sz.width)) {
196:
197: g.drawImage(currentImage, offx, offy, this );
198:
199: } else {
200: // the image is bogus. try again, or give up.
201: flush();
202: if (currentPage != null) {
203: showPage(currentPage);
204: }
205: g.setColor(Color.red);
206: g.drawLine(0, 0, getWidth(), getHeight());
207: g.drawLine(0, getHeight(), getWidth(), 0);
208: }
209: }
210: // draw the zoomrect if there is one.
211: if (zoomRect != null) {
212: g.setColor(Color.red);
213: g.drawRect(zoomRect.x, zoomRect.y, zoomRect.width,
214: zoomRect.height);
215: }
216: // debugging: draw a rectangle around the portion that just changed.
217: // g.setColor(boxColor);
218: // Rectangle r= g.getClipBounds();
219: // g.drawRect(r.x, r.y, r.width-1, r.height-1);
220: }
221:
222: /**
223: * Gets the page currently being displayed
224: */
225: public PDFPage getPage() {
226: return currentPage;
227: }
228:
229: /**
230: * Gets the size of the image currently being displayed
231: */
232: public Dimension getCurSize() {
233: return prevSize;
234: }
235:
236: /**
237: * Gets the clipping rectangle in page space currently being displayed
238: */
239: public Rectangle2D getCurClip() {
240: return prevClip;
241: }
242:
243: /**
244: * Waits until the page is either complete or had an error.
245: */
246: public void waitForCurrentPage() {
247: flag.waitForFlag();
248: }
249:
250: /**
251: * Handles notification of the fact that some part of the image
252: * changed. Repaints that portion.
253: * @return true if more updates are desired.
254: */
255: public boolean imageUpdate(Image img, int infoflags, int x, int y,
256: int width, int height) {
257: // System.out.println("Image update: " + (infoflags & ALLBITS));
258: Dimension sz = getSize();
259: if ((infoflags & (SOMEBITS | ALLBITS)) != 0) {
260: // [[MW: dink this rectangle by 1 to handle antialias issues]]
261: repaint(x + offx, y + offy, width, height);
262: }
263: if ((infoflags & (ALLBITS | ERROR | ABORT)) != 0) {
264: flag.set();
265: // System.out.println(" flag set");
266: return false;
267: } else {
268: return true;
269: }
270: }
271:
272: // public void addPageChangeListener(PageChangeListener pl) {
273: // listener= pl;
274: // }
275:
276: // public void removePageChangeListener(PageChangeListener pl) {
277: // listener= null;
278: // }
279:
280: /**
281: * Turns the zoom tool on or off. If on, mouse drags will draw the
282: * zooming marquee. If off, mouse drags are ignored.
283: */
284: public void useZoomTool(boolean use) {
285: useZoom = use;
286: }
287:
288: /**
289: * Set the desired clipping region (in screen coordinates), and redraw
290: * the image.
291: */
292: public void setClip(Rectangle2D clip) {
293: this .clip = clip;
294: showPage(currentPage);
295: }
296:
297: /** x location of the mouse-down event */
298: int downx;
299:
300: /** y location of the mouse-down event */
301: int downy;
302:
303: /** Handles a mousePressed event */
304: public void mousePressed(MouseEvent evt) {
305: downx = evt.getX();
306: downy = evt.getY();
307: }
308:
309: /**
310: * Handles a mouseReleased event. If zooming is turned on and there's
311: * a valid zoom rectangle, set the image clip to the zoom rect.
312: */
313: public void mouseReleased(MouseEvent evt) {
314: // calculate new clip
315: if (!useZoom || zoomRect == null || zoomRect.width == 0
316: || zoomRect.height == 0) {
317: zoomRect = null;
318: return;
319: }
320:
321: setClip(new Rectangle2D.Double(zoomRect.x - offx, zoomRect.y
322: - offy, zoomRect.width, zoomRect.height));
323:
324: zoomRect = null;
325: }
326:
327: public void mouseClicked(MouseEvent evt) {
328: }
329:
330: public void mouseEntered(MouseEvent evt) {
331: }
332:
333: public void mouseExited(MouseEvent evt) {
334: }
335:
336: public void mouseMoved(MouseEvent evt) {
337: }
338:
339: /**
340: * Handles a mouseDragged event. Constrains the zoom rect to the
341: * aspect ratio of the panel unless the shift key is down.
342: */
343: public void mouseDragged(MouseEvent evt) {
344: if (useZoom) {
345: int x = evt.getX();
346: int y = evt.getY();
347: int dx = Math.abs(x - downx);
348: int dy = Math.abs(y - downy);
349: // constrain to the aspect ratio of the panel
350: if ((evt.getModifiers() & evt.SHIFT_MASK) == 0) {
351: float aspect = (float) dx / (float) dy;
352: float waspect = (float) getWidth()
353: / (float) getHeight();
354: if (aspect > waspect) {
355: dy = (int) (dx / waspect);
356: } else {
357: dx = (int) (dy * waspect);
358: }
359: }
360: if (x < downx) {
361: x = downx - dx;
362: }
363: if (y < downy) {
364: y = downy - dy;
365: }
366: Rectangle old = zoomRect;
367: // ignore small rectangles
368: if (dx < 5 || dy < 5) {
369: zoomRect = null;
370: } else {
371: zoomRect = new Rectangle(Math.min(downx, x), Math.min(
372: downy, y), dx, dy);
373: }
374: // calculate the repaint region. Should be the union of the
375: // old zoom rect and the new one, with an extra pixel on the
376: // bottom and right because of the way rectangles are drawn.
377: if (zoomRect != null) {
378: if (old != null) {
379: old.add(zoomRect);
380: } else {
381: old = new Rectangle(zoomRect);
382: }
383: }
384: if (old != null) {
385: old.width++;
386: old.height++;
387: }
388: if (old != null) {
389: repaint(old);
390: }
391: }
392: }
393:
394: }
|