View Javadoc

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