View Javadoc

1   /*
2    *   Firemox is a turn based strategy simulator
3    *   Copyright (C) 2003-2007 Fabrice Daugan
4    *
5    *   This program is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License as published by the Free
7    * Software Foundation; either version 2 of the License, or (at your option) any
8    * later version.
9    *
10   *   This program is distributed in the hope that it will be useful, but WITHOUT
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13   * details.
14   *
15   *   You should have received a copy of the GNU General Public License along
16   * with this program; if not, write to the Free Software Foundation, Inc.,
17   * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   *
19   */
20  package net.sf.firemox.tools;
21  
22  import java.awt.Color;
23  import java.awt.Dimension;
24  import java.awt.Graphics;
25  import java.awt.Graphics2D;
26  import java.awt.Image;
27  import java.awt.RenderingHints;
28  import java.awt.image.BufferedImage;
29  import java.io.BufferedInputStream;
30  import java.io.BufferedOutputStream;
31  import java.io.File;
32  import java.io.FileInputStream;
33  import java.io.FileOutputStream;
34  import java.io.IOException;
35  import java.net.MalformedURLException;
36  import java.net.URL;
37  import java.net.URLConnection;
38  
39  import javax.swing.JComponent;
40  
41  import net.sf.firemox.clickable.target.card.CardFactory;
42  import net.sf.firemox.database.DatabaseFactory;
43  import net.sf.firemox.management.MonitoredCheckContent;
44  import net.sf.firemox.token.IdConst;
45  import net.sf.firemox.ui.MagicUIComponents;
46  import net.sf.firemox.ui.ToolKit;
47  import net.sf.firemox.ui.component.LoaderConsole;
48  import net.sf.firemox.ui.i18n.LanguageManager;
49  
50  import org.apache.commons.io.FileUtils;
51  import org.apache.commons.io.IOUtils;
52  
53  import sun.awt.image.ToolkitImage;
54  
55  /***
56   * A JComponent displaying an image. The picture is sized to suit to the
57   * component size.<br>
58   * <ul>
59   * TODO Add context menu to this component to:
60   * <li>display the picture with right size</li>
61   * <li>obtain more information about illustrator</li>
62   * </ul>
63   * 
64   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
65   * @author brius for http proxy configuration
66   * @since 0.83 Empty file are deleted to force file to be downloaded.
67   */
68  public class Picture extends JComponent {
69  
70  	/***
71  	 * The string delimiting the jar/zip content.
72  	 */
73  	public static final String STR_ZIP_PATH = "!/";
74  
75  	/***
76  	 * Creates a new instance of Picture <br>
77  	 * The displayed picture will be the back picture of current TBS game. If no
78  	 * TBS game is loaded, no picture would be drawn. The [preferred]size of this
79  	 * component will be set to the given dimensions.
80  	 * 
81  	 * @param width
82  	 *          the fixed width of this picture
83  	 * @param height
84  	 *          the fixed height of this picture
85  	 */
86  	public Picture(int width, int height) {
87  		setPreferredSize(new Dimension(width, height));
88  		setSize(width, height);
89  		setImage(DatabaseFactory.backImage, null);
90  	}
91  
92  	/***
93  	 * Set the image and card's name to display. Repaint method is called
94  	 * immediately.
95  	 * 
96  	 * @param cardImage
97  	 *          the new card's image to display.
98  	 * @param cardName
99  	 *          the new card's name to use as tooltip.
100 	 */
101 	public void setImage(Image cardImage, String cardName) {
102 		if (cardImage != this.cardImage || cardName != this.cardName) {
103 			this.cardImage = cardImage;
104 			this.cardName = cardName;
105 			if (cardName == null) {
106 				setToolTipText("??");
107 			} else {
108 				setToolTipText(cardName);
109 			}
110 			repaint();
111 		}
112 	}
113 
114 	/***
115 	 * Download a file from the specified URL to the specified local file.
116 	 * 
117 	 * @param localFile
118 	 *          is the new card's picture to try first
119 	 * @param remoteFile
120 	 *          is the URL where this picture will be downloaded in case of the
121 	 *          specified card name has not been found locally.
122 	 * @since 0.83 Empty file are deleted to force file to be downloaded.
123 	 */
124 	public static void download(String localFile, URL remoteFile) {
125 		download(localFile, remoteFile, null);
126 	}
127 
128 	/***
129 	 * Download a file from the specified URL to the specified local file.
130 	 * 
131 	 * @param localFile
132 	 *          is the new card's picture to try first
133 	 * @param remoteFile
134 	 *          is the URL where this picture will be downloaded in case of the
135 	 *          specified card name has not been found locally.
136 	 * @param listener
137 	 *          the component waiting for this picture.
138 	 * @since 0.83 Empty file are deleted to force file to be downloaded.
139 	 */
140 	public static synchronized void download(String localFile, URL remoteFile,
141 			MonitoredCheckContent listener) {
142 		BufferedOutputStream out = null;
143 		BufferedInputStream in = null;
144 		final File toDownload = MToolKit.getFile(localFile, false);
145 		if (toDownload.exists() && toDownload.length() == 0
146 				&& toDownload.canWrite()) {
147 			toDownload.delete();
148 		}
149 		if (!toDownload.exists()
150 				|| (toDownload.length() == 0 && toDownload.canWrite())) {
151 			// the file has to be downloaded
152 			try {
153 				if ("file".equals(remoteFile.getProtocol())) {
154 					File localRemoteFile = MToolKit.getFile(remoteFile.toString()
155 							.substring(7).replaceAll("%20", " "), false);
156 					int contentLength = (int) localRemoteFile.length();
157 					Log.info("Copying from " + localRemoteFile.getAbsolutePath());
158 					LoaderConsole.beginTask(LanguageManager.getString("downloading")
159 							+ " " + localRemoteFile.getAbsolutePath() + "("
160 							+ FileUtils.byteCountToDisplaySize(contentLength) + ")");
161 
162 					// Copy file
163 					in = new BufferedInputStream(new FileInputStream(localRemoteFile));
164 					byte[] buf = new byte[2048];
165 					int currentLength = 0;
166 					boolean succeed = false;
167 					for (int bufferLen = in.read(buf); bufferLen >= 0; bufferLen = in
168 							.read(buf)) {
169 						if (!succeed) {
170 							toDownload.getParentFile().mkdirs();
171 							out = new BufferedOutputStream(new FileOutputStream(localFile));
172 							succeed = true;
173 						}
174 						currentLength += bufferLen;
175 						if (out != null) {
176 							out.write(buf, 0, bufferLen);
177 						}
178 						if (listener != null) {
179 							listener.updateProgress(contentLength, currentLength);
180 						}
181 					}
182 
183 					// Step 3: close streams
184 					IOUtils.closeQuietly(in);
185 					IOUtils.closeQuietly(out);
186 					in = null;
187 					out = null;
188 					return;
189 				}
190 
191 				// Testing mode?
192 				if (!MagicUIComponents.isUILoaded()) {
193 					return;
194 				}
195 
196 				// Step 1: open streams
197 				final URLConnection connection = MToolKit.getHttpConnection(remoteFile);
198 				int contentLength = connection.getContentLength();
199 				in = new BufferedInputStream(connection.getInputStream());
200 				Log.info("Download from " + remoteFile + "("
201 						+ FileUtils.byteCountToDisplaySize(contentLength) + ")");
202 				LoaderConsole.beginTask(LanguageManager.getString("downloading") + " "
203 						+ remoteFile + "("
204 						+ FileUtils.byteCountToDisplaySize(contentLength) + ")");
205 
206 				// Step 2: read and write until done
207 				byte[] buf = new byte[2048];
208 				int currentLength = 0;
209 				boolean succeed = false;
210 				for (int bufferLen = in.read(buf); bufferLen >= 0; bufferLen = in
211 						.read(buf)) {
212 					if (!succeed) {
213 						toDownload.getParentFile().mkdirs();
214 						out = new BufferedOutputStream(new FileOutputStream(localFile));
215 						succeed = true;
216 					}
217 					currentLength += bufferLen;
218 					if (out != null) {
219 						out.write(buf, 0, bufferLen);
220 					}
221 					if (listener != null) {
222 						listener.updateProgress(contentLength, currentLength);
223 					}
224 				}
225 
226 				// Step 3: close streams
227 				IOUtils.closeQuietly(in);
228 				IOUtils.closeQuietly(out);
229 				in = null;
230 				out = null;
231 				return;
232 			} catch (IOException e1) {
233 				if (MToolKit.getFile(localFile) != null) {
234 					MToolKit.getFile(localFile).delete();
235 				}
236 				if (remoteFile.getFile().equals(remoteFile.getFile().toLowerCase())) {
237 					Log.fatal("could not load picture " + localFile + " from URL "
238 							+ remoteFile + ", " + e1.getMessage());
239 				}
240 				String tmpRemote = remoteFile.toString().toLowerCase();
241 				try {
242 					download(localFile, new URL(tmpRemote), listener);
243 				} catch (MalformedURLException e) {
244 					Log.fatal("could not load picture " + localFile + " from URL "
245 							+ tmpRemote + ", " + e.getMessage());
246 				}
247 			}
248 		}
249 	}
250 
251 	/***
252 	 * Load the file picture and return the Image object.
253 	 * 
254 	 * @param localFile
255 	 *          is the new card's picture to try first
256 	 * @return the Image object representing this fileName picture
257 	 * @throws MalformedURLException
258 	 */
259 	public static Image loadImage(String localFile) throws MalformedURLException {
260 		if (localFile == null) {
261 			return null;
262 		}
263 		MonitoredCheckContent res = loadImage(localFile, null);
264 		if (res != null) {
265 			return res.getContent();
266 		}
267 		return null;
268 	}
269 
270 	/***
271 	 * Load the file picture and return the Image object. If the specified
272 	 * filename did not exist, the specified URL is used to download it.
273 	 * 
274 	 * @param localFile
275 	 *          is the new card's picture to try first
276 	 * @param remoteFile
277 	 *          is the URL where this picture will be downloaded in case of the
278 	 *          specified card name has not been found locally.
279 	 * @return the Image object representing this fileName picture
280 	 * @throws MalformedURLException
281 	 */
282 	public static MonitoredCheckContent loadImage(String localFile, URL remoteFile)
283 			throws MalformedURLException {
284 		return loadImage(localFile, remoteFile, null);
285 	}
286 
287 	/***
288 	 * Load the file picture and return the Image object. If the specified
289 	 * filename did not exist, the specified URL is used to download it.
290 	 * 
291 	 * @param localFile
292 	 *          is the new card's picture to try first
293 	 * @param remoteFile
294 	 *          is the URL where this picture will be downloaded in case of the
295 	 *          specified card name has not been found locally.
296 	 * @param listener
297 	 *          the component waiting for this picture.
298 	 * @return the Image object representing this fileName picture
299 	 * @throws MalformedURLException
300 	 * @since 0.83 Empty file are deleted to force file to be downloaded.
301 	 * @since 0.90 zipped files are managed.
302 	 */
303 	public static MonitoredCheckContent loadImage(String localFile,
304 			URL remoteFile, MonitoredCheckContent listener)
305 			throws MalformedURLException {
306 		File toDownload = null;
307 		try {
308 			// First determines the file is within a zip file
309 			if (localFile.contains(STR_ZIP_PATH)) {
310 				final String zipName = localFile.substring(0, localFile
311 						.indexOf(STR_ZIP_PATH));
312 				toDownload = MToolKit.getFile(zipName);
313 				if (toDownload == null) {
314 					// The zip file does not exist, we get it first
315 					toDownload = MToolKit.getFile(zipName, false);
316 					if (toDownload.exists() && toDownload.isFile()
317 							&& toDownload.length() == 0) {
318 						// delete previous null sized zip file
319 						toDownload.delete();
320 					}
321 
322 					if (!toDownload.exists()) {
323 						// the file does not exist
324 						if (remoteFile == null) {
325 							return null;
326 						}
327 						download(zipName, new URL(remoteFile.toString().substring(0,
328 								remoteFile.toString().indexOf(STR_ZIP_PATH))), listener);
329 					}
330 				}
331 				return new MonitoredCheckContent(MToolKit.getLocalPicture("jar:"
332 						+ MToolKit.getFile(zipName, false).toURI().toURL()
333 						+ localFile.substring(localFile.indexOf(STR_ZIP_PATH))));
334 			}
335 
336 			toDownload = MToolKit.getFile(localFile);
337 			if (toDownload == null)
338 				toDownload = MToolKit.getFile(localFile, false);
339 			if (toDownload.exists() && toDownload.isFile()
340 					&& toDownload.length() == 0) {
341 				// delete previous null sized picture file
342 				toDownload.delete();
343 			}
344 
345 			if (!toDownload.exists()) {
346 				// the file does not exist
347 				if (remoteFile == null) {
348 					return null;
349 				}
350 				download(localFile, remoteFile, listener);
351 			}
352 			// load picture from it's local location
353 			return new MonitoredCheckContent(MToolKit.getLocalPicture(localFile));
354 		} catch (InterruptedException e) {
355 			// IGNORED
356 			return null;
357 		}
358 	}
359 
360 	@Override
361 	public void paint(Graphics g) {
362 		Graphics2D g2d = (Graphics2D) g;
363 		g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
364 				RenderingHints.VALUE_INTERPOLATION_BILINEAR);
365 		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
366 				RenderingHints.VALUE_ANTIALIAS_ON);
367 		g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
368 				RenderingHints.VALUE_RENDER_QUALITY);
369 		if (cardImage != null) {
370 			g.drawImage(cardImage, 0, 0, getWidth(), getHeight(), this);
371 		}
372 	}
373 
374 	/***
375 	 * Return a scaled picture of the given one suiting to card dimension.
376 	 * 
377 	 * @param image
378 	 *          the original card picture.
379 	 * @return a scaled picture of the given one suiting to card dimension.
380 	 */
381 	public static BufferedImage getScaledImage(Image image) {
382 		BufferedImage buffImage = ((ToolkitImage) image).getBufferedImage();
383 		return ToolKit
384 				.getScaledInstance(buffImage, CardFactory.cardWidth,
385 						CardFactory.cardHeight, IdConst.BORDER_WIDTH,
386 						getBorderColor(buffImage));
387 	}
388 
389 	/***
390 	 * Return the border color of this card. If the picture of this card is not
391 	 * <code>null</code> the returned color corresponds to the pixel placed on
392 	 * the topmost leftmost pixel.
393 	 * 
394 	 * @return the border color of this card.
395 	 */
396 	private static Color getBorderColor(BufferedImage image) {
397 		Color borderColor;
398 		// The border color is not yet cached
399 		if (CardFactory.borderColor != null) {
400 			// manual border
401 			borderColor = CardFactory.borderColor;
402 		} else {
403 			// auto border
404 			if (image != null) {
405 				borderColor = new Color(image.getRGB(0, 0));
406 				if (borderColor.getRed() > 175 && borderColor.getGreen() > 175
407 						&& borderColor.getBlue() > 175) {
408 					borderColor = Color.WHITE.darker();
409 				} else {
410 					borderColor = Color.BLACK.brighter();
411 				}
412 			} else {
413 				borderColor = CardFactory.borderColor;
414 			}
415 		}
416 		return borderColor;
417 	}
418 
419 	/***
420 	 * card's image
421 	 */
422 	private Image cardImage;
423 
424 	/***
425 	 * card's name
426 	 */
427 	private String cardName;
428 
429 }