// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.print;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import javax.swing.JPanel;
import javax.swing.JViewport;
import javax.swing.RepaintManager;
import org.openstreetmap.josm.Main;
/**
* A generic print preview component
*
* Given a Printable, a PageFormat and a zoom factor, this component
* will render a scrollable and zoomable print preview. If no Printable
* is defined, it will fill the printable area of the page format in
* plain gray.
*
* There is a special zoom-to-page condition where the component will
* automatically adjust the zoom level such that the whole page preview
* will just fit into the size of the component.
* @author Kai Pastor
*/
class PrintPreview extends JPanel {
/**
* The PageFormat chosen for printing (and preview)
*/
protected transient PageFormat format;
/**
* The current zoom factor for the preview
*/
protected double zoom = 1.0;
/**
* If true, the preview is always zoomed such that
* the whole page fits into the preview area.
*/
protected boolean zoomToPage = true;
/**
* When this flag is true, no painting operations will be performed.
*/
protected boolean paintingDisabled;
/**
* the printable object for rendering preview contents
*/
protected transient Printable printable;
/**
* Constructs a new preview component
*/
PrintPreview() {
super();
}
/**
* Constructs a new preview component for a printable
*
* @param p the printable object for rendering preview contents, or null
*/
PrintPreview(Printable p) {
super();
printable = p;
}
/**
* Sets (and unsets) the printable object
*
* @param p a printable object, or null
*/
public void setPrintable(Printable p) {
printable = p;
repaint();
}
/**
* Sets the page format
*
* @param format the PageFormat chosen for printing (and preview)
*/
public void setPageFormat(PageFormat format) {
this.format = format;
revalidate();
repaint();
}
/**
* Sets the preview zoom relativ to the actual size
*
* @param zoom the zoom factor
*/
public void setZoom(double zoom) {
setZoom(zoom, false);
}
/**
* Zoom into the preview
*
* Doubles the current zoom factor, if smaller than 5.0.
*/
public void zoomIn() {
double z = getZoom();
if (z < 5.0) {
setZoom(2.0 * z);
}
}
/**
* Zoom out of the preview
*
* Set the zoom factor to half its current value, if bigger than 0.1.
*/
public void zoomOut() {
double z = getZoom();
if (z > 0.1) {
setZoom(0.5 * z);
}
}
/**
* Zoom to fit the page size
*
* Set the zoom factor such that the whole page fits into the preview area.
*/
public void zoomToPage() {
if (format == null)
return;
Container parent = getParent();
Dimension dim;
if (parent instanceof JViewport) {
dim = getParent().getSize(); // could get rid of scrollbars
} else {
dim = getVisibleRect().getSize();
}
int resolution = Toolkit.getDefaultToolkit().getScreenResolution();
double widthZoom = 72.0 * dim.getWidth() / resolution / format.getWidth();
double heightZoom = 72.0 * dim.getHeight() / resolution / format.getHeight();
setZoom(Math.min(widthZoom, heightZoom), true);
}
/**
* Sets the preview zoom relativ to the actual size
*
* @param zoom the zoom factor
* @param zoomToPage when set to true, the zoom factor will be
* adjusted on resize operations such that the whole page always
* fits to the preview area.
*/
protected void setZoom(double zoom, boolean zoomToPage) {
if (format == null || zoom == this.zoom) {
return;
}
Dimension oldDim = getZoomedPageDimension();
Rectangle oldView = getVisibleRect();
paintingDisabled = true; // avoid flickering during zooming
this.zoom = zoom;
this.zoomToPage = zoomToPage;
revalidate(); // Make JScrollPane aware of the change in size
RepaintManager.currentManager(this).validateInvalidComponents();
Dimension dim = getZoomedPageDimension();
Rectangle view = getVisibleRect();
view.x = (int) ((0.5*oldView.width+oldView.x) /
Math.max(oldView.width, oldDim.width)*Math.max(view.width, dim.width)-0.5*view.width);
view.y = (int) ((0.5*oldView.height+oldView.y) /
Math.max(oldView.height, oldDim.height)*Math.max(view.height, dim.height)-0.5*view.height);
scrollRectToVisible(view);
paintingDisabled = false;
repaint(view); // repaint now when zooming is completed
}
/**
* Returns the actual current zoom
*
* This factor might be different from the last explicitly set factor
* when zoomToPage is true.
*
* @return the zoom factor
*/
public double getZoom() {
if (format != null && (zoomToPage || zoom < 0.01)) {
// actually this is zoom-to-page
Dimension dim = getParent().getSize();
int resolution = Toolkit.getDefaultToolkit().getScreenResolution();
double widthZoom = 72.0 * dim.getWidth() / resolution / format.getWidth();
double heightZoom = 72.0 * dim.getHeight() / resolution / format.getHeight();
return Math.min(widthZoom, heightZoom);
}
return zoom;
}
/**
* Get the current dimension of the page preview
*
* @return dimension in pixels
*/
public Dimension getZoomedPageDimension() {
int resolution = Toolkit.getDefaultToolkit().getScreenResolution();
double z = getZoom();
int width = (int) (z * resolution * format.getWidth() / 72.0);
int height = (int) (z * resolution * format.getHeight() / 72.0);
return new Dimension(width, height);
}
/**
* Get the current preferred size of the component
*
* @return indicates a zero size when zoomToPage is active,
* the zoomed page dimension otherwise
*/
@Override
public Dimension getPreferredSize() {
if (format == null || zoomToPage || zoom < 0.01) {
return new Dimension(0, 0);
}
return getZoomedPageDimension();
}
/**
* Paints the component
*
* This will do nothing, not even call super.paintComponent(g), if
* painting is disabled in this component.
*
* @param g a Graphics to draw to
*/
@Override
public void paintComponent(Graphics g) {
if (paintingDisabled) {
return;
}
super.paintComponent(g);
if (format == null) {
return;
}
Graphics2D g2d = (Graphics2D) g;
AffineTransform at = g2d.getTransform();
Dimension out = getZoomedPageDimension();
double scale = Math.min(
out.getHeight()/format.getHeight(), out.getWidth()/format.getWidth());
double left = 0.5 * (getWidth() - scale * format.getWidth());
double top = 0.5 * (getHeight() - scale * format.getHeight());
g2d.translate(left, top);
g2d.setColor(Color.black);
g2d.drawRect(-1, -1, out.width+1, out.height+1);
g2d.setColor(Color.white);
g2d.fillRect(0, 0, out.width, out.height);
g2d.scale(scale, scale);
g2d.clip(new Rectangle2D.Double(
format.getImageableX(),
format.getImageableY(),
format.getImageableWidth(),
format.getImageableHeight()));
if (printable != null) {
try {
printable.print(g2d, format, 0);
} catch (PrinterException e) {
// should never happen since we are not printing
Main.error(e);
}
} else {
g2d.setColor(Color.gray);
g2d.fillRect(0, 0, (int) format.getWidth(), (int) format.getHeight());
}
g2d.setTransform(at);
}
}