1 /*
2  * $Id: JGraphPrintingScrollPane.java,v 1.1 2009/09/25 15:14:15 david Exp $
3  * Copyright (c) 2001-2005, Gaudenz Alder
4  *
5  * All rights reserved.
6  *
7  * See LICENSE file for license details. If you are unable to locate
8  * this file please contact info (at) jgraph (dot) com.
9  */
10 package com.jgraph.util;
11 
12 import java.awt.BasicStroke;
13 import java.awt.Color;
14 import java.awt.Dimension;
15 import java.awt.Graphics;
16 import java.awt.Graphics2D;
17 import java.awt.Image;
18 import java.awt.Point;
19 import java.awt.Stroke;
20 import java.awt.geom.AffineTransform;
21 import java.awt.geom.Point2D;
22 import java.awt.geom.Rectangle2D;
23 import java.awt.print.PageFormat;
24 import java.awt.print.Printable;
25 
26 import javax.swing.JScrollPane;
27 import javax.swing.JViewport;
28 import javax.swing.RepaintManager;
29 
30 import org.jgraph.JGraph;
31 
32 /**
33  * Wrapper panel for a diagram/JGraph-pair that implements automatic sizing,
34  * backgrounds, printing and undo support. When wrapped in a scrollpane this
35  * panel adds rulers to the enclosing scrollpane. Furthermore, it automatically
36  * sets the minimum size and scale of the graph based on its settings.
37  */
38 public class JGraphPrintingScrollPane  extends JScrollPane implements Printable {
39 
40 	/**
41 	 * Specifies the default page scale. Default is 1.5
42 	 */
43 	public static final double DEFAULT_PAGESCALE = 1.5;
44 
45 	/**
46 	 * Background page format.
47 	 */
48 	protected PageFormat pageFormat = new PageFormat();
49 
50 	/**
51 	 * Specifies if the background page is visible. Default is true.
52 	 */
53 	protected boolean isPageVisible = true;
54 
55 	/**
56 	 * Defines the scaling for the background page metrics. Default is
57 	 * {@link #DEFAULT_PAGESCALE}.
58 	 */
59 	protected double pageScale = DEFAULT_PAGESCALE;
60 
61 	/**
62 	 * References the inner graph.
63 	 */
64 	protected JGraph graph;
65 
66 	/**
67 	 * Bound property names for the respective properties.
68 	 */
69 	public static String PROPERTY_METRIC = "metric",
70 			PROPERTY_PAGEVISIBLE = "pageVisible",
71 			PROPERTY_BACKGROUNDIMAGE = "backgroundImage",
72 			PROPERTY_RULERSVISIBLE = "rulersVisible",
73 			PROPERTY_PAGEFORMAT = "pageFormat",
74 			PROPERTY_AUTOSCALEPOLICY = "autoScalePolicy",
75 			PROPERTY_PAGESCALE = "pageScale";
76 
77 	/**
78 	 * Returns the inner graph.
79 	 *
80 	 * @return Returns the graph.
81 	 */
getGraph()82 	public JGraph getGraph() {
83 		return graph;
84 	}
85 
86 	/**
87 	 * Returns the page format of the background page.
88 	 *
89 	 * @return Returns the pageFormat.
90 	 */
getPageFormat()91 	public PageFormat getPageFormat() {
92 		return pageFormat;
93 	}
94 
95 	/**
96 	 * Sets the page format of the background page.Fires a property change event
97 	 * for {@link #PROPERTY_PAGEFORMAT}.
98 	 *
99 	 * @param pageFormat
100 	 *            The pageFormat to set.
101 	 */
setPageFormat(PageFormat pageFormat)102 	public void setPageFormat(PageFormat pageFormat) {
103 		Object oldValue = this.pageFormat;
104 		this.pageFormat = pageFormat;
105 		updateMinimumSize();
106 		firePropertyChange(PROPERTY_PAGEFORMAT, oldValue, pageFormat);
107 	}
108 
109 	/**
110 	 * Returns the scale of the page metrics.
111 	 *
112 	 * @return Returns the pageScale.
113 	 */
getPageScale()114 	public double getPageScale() {
115 		return pageScale;
116 	}
117 
118 	/**
119 	 * Sets the scale of the page metrics.Fires a property change event for
120 	 * {@link #PROPERTY_PAGESCALE}.
121 	 *
122 	 * @param pageScale
123 	 *            The pageScale to set.
124 	 */
setPageScale(double pageScale)125 	public void setPageScale(double pageScale) {
126 		double oldValue = this.pageScale;
127 		this.pageScale = pageScale;
128 		firePropertyChange(PROPERTY_PAGESCALE, oldValue, pageScale);
129 	}
130 
131 	/**
132 	 * Updates the minimum size of the graph according to the current state of
133 	 * the background page: if the page is not visible then the minimum size is
134 	 * set to <code>null</code>, otherwise the minimum size is set to the
135 	 * smallest area of pages containing the graph.
136 	 */
updateMinimumSize()137 	protected void updateMinimumSize() {
138 		if (isPageVisible() && pageFormat != null) {
139 			Rectangle2D bounds = graph.getCellBounds(graph.getRoots());
140 			Dimension size = (bounds != null) ? new Dimension((int) (bounds
141 					.getX() + bounds.getWidth()), (int) (bounds.getY() + bounds
142 					.getHeight())) : new Dimension(1, 1);
143 			int w = (int) (pageFormat.getWidth() * pageScale);
144 			int h = (int) (pageFormat.getHeight() * pageScale);
145 			int cols = (int) Math.ceil((double) (size.width - 5) / (double) w);
146 			int rows = (int) Math.ceil((double) (size.height - 5) / (double) h);
147 			size = new Dimension(Math.max(cols, 1) * w + 5, Math.max(rows, 1)
148 					* h + 5);
149 			graph.setMinimumSize(size);
150 		} else {
151 			graph.setMinimumSize(null);
152 		}
153 		graph.revalidate();
154 	}
155 
156 	/**
157 	 * Computes the scale for the window autoscale policy.
158 	 *
159 	 * @param border
160 	 *            The border to use.
161 	 * @return Returns the scale to use for the graph.
162 	 */
computeWindowScale(int border)163 	protected double computeWindowScale(int border) {
164 		Dimension size = getViewport().getExtentSize();
165 		Rectangle2D p = getGraph().getCellBounds(getGraph().getRoots());
166 		if (p != null) {
167 			return Math.min((double) size.getWidth()
168 					/ (p.getX() + p.getWidth() + border), (double) size
169 					.getHeight()
170 					/ (p.getY() + p.getHeight() + border));
171 		}
172 		return 0;
173 	}
174 
175 	/**
176 	 * Computes the scale for the page autoscale policy.
177 	 *
178 	 * @return Returns the scale to use for the graph.
179 	 */
computePageScale()180 	protected double computePageScale() {
181 		Dimension size = getViewport().getExtentSize();
182 		Dimension p = getGraph().getMinimumSize();
183 		if (p != null && (p.getWidth() != 0 || p.getHeight() != 0)) {
184 			return Math.min((double) size.getWidth() / (double) p.getWidth(),
185 					(double) size.getHeight() / (double) p.getHeight());
186 		}
187 		return 0;
188 	}
189 
190 	/**
191 	 * Computes the scale for the pagewidth autoscale policy.
192 	 *
193 	 * @param border
194 	 *            The border to use.
195 	 * @return Returns the scale to use for the graph.
196 	 */
computePageWidthScale(int border)197 	protected double computePageWidthScale(int border) {
198 		Dimension size = getViewport().getExtentSize();
199 		Dimension p = getGraph().getMinimumSize();
200 		if (p != null && (p.getWidth() != 0 || p.getHeight() != 0)) {
201 			size.width = size.width - border;
202 			return (double) size.getWidth() / (double) p.getWidth();
203 		}
204 		return 0;
205 	}
206 
207 	/**
208 	 * Prints the specified page on the specified graphics using
209 	 * <code>pageForm</code> for the page format.
210 	 *
211 	 * @param g
212 	 *            The graphics to paint the graph on.
213 	 * @param printFormat
214 	 *            The page format to use for printing.
215 	 * @param page
216 	 *            The page to print
217 	 * @return Returns {@link Printable#PAGE_EXISTS} or
218 	 *         {@link Printable#NO_SUCH_PAGE}.
219 	 */
print(Graphics g, PageFormat printFormat, int page)220 	public int print(Graphics g, PageFormat printFormat, int page) {
221 		Dimension pSize = graph.getPreferredSize();
222 		int w = (int) (printFormat.getWidth() * pageScale);
223 		int h = (int) (printFormat.getHeight() * pageScale);
224 		int cols = (int) Math.max(Math.ceil((double) (pSize.width - 5)
225 				/ (double) w), 1);
226 		int rows = (int) Math.max(Math.ceil((double) (pSize.height - 5)
227 				/ (double) h), 1);
228 		if (page < cols * rows) {
229 
230 			// Configures graph for printing
231 			RepaintManager currentManager = RepaintManager.currentManager(this);
232 			currentManager.setDoubleBufferingEnabled(false);
233 			double oldScale = getGraph().getScale();
234 			getGraph().setScale(1 / pageScale);
235 			int dx = (int) ((page % cols) * printFormat.getWidth());
236 			int dy = (int) ((page % rows) * printFormat.getHeight());
237 			g.translate(-dx, -dy);
238 			g.setClip(dx, dy, (int) (dx + printFormat.getWidth()),
239 					(int) (dy + printFormat.getHeight()));
240 
241 			// Prints the graph on the graphics.
242 			getGraph().paint(g);
243 
244 			// Restores graph
245 			g.translate(dx, dy);
246 			graph.setScale(oldScale);
247 			currentManager.setDoubleBufferingEnabled(true);
248 			return PAGE_EXISTS;
249 		} else {
250 			return NO_SUCH_PAGE;
251 		}
252 	}
253 
254 	/**
255 	 * Viewport for diagram panes that is in charge of painting the background
256 	 * image or page.
257 	 */
258 	public class Viewport extends JViewport {
259 
260 		/**
261 		 * Paints the background.
262 		 *
263 		 * @param g
264 		 *            The graphics object to paint the background on.
265 		 */
paint(Graphics g)266 		public void paint(Graphics g) {
267 			if (isPageVisible())
268 				paintBackgroundPages((Graphics2D) g);
269 			else
270 				setBackground(graph.getBackground());
271 			if (graph.getBackgroundImage() != null) {
272 				paintBackgroundImage((Graphics2D) g);
273 			}
274 			setOpaque(!isPageVisible() && graph.getBackgroundImage() == null);
275 			super.paint(g);
276 			setOpaque(true);
277 		}
278 
279 		/**
280 		 * Hook for subclassers to paint the background image.
281 		 *
282 		 * @param g2
283 		 *            The graphics object to paint the image on.
284 		 */
paintBackgroundImage(Graphics2D g2)285 		protected void paintBackgroundImage(Graphics2D g2) {
286 			// Clears the background
287 			if (!isPageVisible()) {
288 				g2.setColor(graph.getBackground());
289 				g2.fillRect(0, 0, graph.getWidth(), graph.getHeight());
290 			}
291 			// Paints the image
292 			AffineTransform tmp = g2.getTransform();
293 			Point offset = getViewPosition();
294 			g2.translate(-offset.x, -offset.y);
295 			g2.scale(graph.getScale(), graph.getScale());
296 			Image img = graph.getBackgroundImage().getImage();
297 			g2.drawImage(img, 0, 0, graph);
298 			g2.setTransform(tmp);
299 		}
300 
301 		/**
302 		 * Hook for subclassers to paint the background page(s).
303 		 *
304 		 * @param g2
305 		 *            The graphics object to paint the background page(s) on.
306 		 */
paintBackgroundPages(Graphics2D g2)307 		protected void paintBackgroundPages(Graphics2D g2) {
308 			Point2D p = graph.toScreen(new Point2D.Double(
309 					pageFormat.getWidth(), pageFormat.getHeight()));
310 			Dimension pSize = graph.getPreferredSize();
311 			int w = (int) (p.getX() * pageScale);
312 			int h = (int) (p.getY() * pageScale);
313 			int cols = (int) Math.max(Math.ceil((double) (pSize.width - 5)
314 					/ (double) w), 1);
315 			int rows = (int) Math.max(Math.ceil((double) (pSize.height - 5)
316 					/ (double) h), 1);
317 			g2.setColor(graph.getHandleColor());
318 
319 			// Draws the pages.
320 			Point offset = getViewPosition();
321 			g2.translate(-offset.x, -offset.y);
322 			g2.fillRect(0, 0, graph.getWidth(), graph.getHeight());
323 			g2.setColor(Color.darkGray);
324 			g2.fillRect(3, 3, cols * w, rows * h);
325 			g2.setColor(getGraph().getBackground());
326 			g2.fillRect(1, 1, cols * w - 1, rows * h - 1);
327 
328 			// Draws the pagebreaks.
329 			Stroke previousStroke = g2.getStroke();
330 			g2.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
331 					BasicStroke.JOIN_MITER, 10.0f, new float[] { 1, 2 }, 0));
332 			g2.setColor(Color.darkGray);
333 			for (int i = 1; i < cols; i++)
334 				g2.drawLine(i * w, 1, i * w, rows * h - 1);
335 			for (int i = 1; i < rows; i++)
336 				g2.drawLine(1, i * h, cols * w - 1, i * h);
337 
338 			// Restores the graphics.
339 			g2.setStroke(previousStroke);
340 			g2.translate(offset.x, offset.y);
341 			g2.clipRect(0, 0, cols * w - 1 - offset.x, rows * h - 1 - offset.y);
342 		}
343 
344 	}
345 
346 	/**
347 	 * Returns true if the background page is visible.
348 	 *
349 	 * @return Returns the isPageVisible.
350 	 */
isPageVisible()351 	public boolean isPageVisible() {
352 		return isPageVisible;
353 	}
354 
355 	/**
356 	 * Sets if the background page should be visible.Fires a property change
357 	 * event for {@link #PROPERTY_PAGEVISIBLE}.
358 	 *
359 	 * @param isPageVisible
360 	 *            The isPageVisible to set.
361 	 */
setPageVisible(boolean isPageVisible)362 	public void setPageVisible(boolean isPageVisible) {
363 		boolean oldValue = this.isPageVisible;
364 		this.isPageVisible = isPageVisible;
365 		updateMinimumSize();
366 		firePropertyChange(PROPERTY_PAGEVISIBLE, oldValue, isPageVisible);
367 	}
368 }