1 /**
2  * The chess framework library.
3  * More information is available at http://www.jinchess.com/.
4  * Copyright (C) 2006 Alexander Maryanovsky.
5  * All rights reserved.
6  *
7  * The chess framework library is free software; you can redistribute
8  * it and/or modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * The chess framework library is distributed in the hope that it will
13  * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with the chess framework library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 
22 package free.chess;
23 
24 import java.awt.Component;
25 import java.awt.Graphics;
26 import java.awt.Image;
27 import java.awt.Toolkit;
28 import java.io.IOException;
29 import java.net.URL;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.Properties;
33 import java.util.Set;
34 
35 import free.util.IOUtilities;
36 import free.util.ImageUtilities;
37 
38 
39 
40 /**
41  * A <code>BoardPainter</code> which paints the entire board using a single,
42  * scaled image.
43  */
44 
45 public class BoardImageBoardPainter implements ResourceBoardPainter{
46 
47 
48 
49   /**
50    * A delegate board painter we use while the board image is being loaded.
51    */
52 
53   private static final BoardPainter whileLoadingDelegate = new DefaultBoardPainter();
54 
55 
56 
57   /**
58    * True if the board image is to be loaded asynchronously, and in the meanwhile,
59    * the delegate should be used.
60    */
61 
62   private static volatile boolean asyncImageLoad = false;
63 
64 
65 
66   /**
67    * The board image. When <code>BoardImageBoardPainter</code> is used as a
68    * <code>ResourceBoardPainter</code>, this remains <code>null</code> until
69    * the image data is fully loaded.
70    */
71 
72   private Image boardImage = null;
73 
74 
75 
76   /**
77    * The board image, scaled according to the last request to paint a board.
78    */
79 
80   private Image scaledBoardImage = null;
81 
82 
83 
84   /**
85    * When <code>BoardImageBoardPainter</code> is used as a
86    * <code>ResourceBoardPainter</code>, this specified the URL of the board's
87    * image. Otherwise, it remains null.
88    */
89 
90   private URL boardImageUrl = null;
91 
92 
93 
94   /**
95    * The <code>ImageDataReceiver</code>, if any, currently waiting on board
96    * image data to load.
97    */
98 
99   private ImageDataReceiver imageDataReceiver = null;
100 
101 
102 
103   /**
104    * A no-arg constructor so that this <code>BoardImageBoardPainter</code> can
105    * be used as a <code>ResourceBoardPainter</code>.
106    */
107 
BoardImageBoardPainter()108   public BoardImageBoardPainter(){
109 
110   }
111 
112 
113 
114   /**
115    * Creates a new <code>BoardImageBoardPainter</code> which paints the board
116    * with the given image.
117    */
118 
BoardImageBoardPainter(Image boardImage)119   public BoardImageBoardPainter(Image boardImage){
120     this.boardImage = boardImage;
121   }
122 
123 
124 
125   /**
126    * Sets whether the board image is to be loaded asynchronously and a simpler
127    * board painter delegate used while it loads. Asynchronous loading is off
128    * by default.
129    */
130 
setAsyncImageLoad(boolean asyncImageLoad)131   public static void setAsyncImageLoad(boolean asyncImageLoad){
132     BoardImageBoardPainter.asyncImageLoad = asyncImageLoad;
133   }
134 
135 
136 
137   /**
138    * Since <code>BoardImageBoardPainter</code>s are immutable, simply returns
139    * <code>this</code>.
140    */
141 
freshInstance()142   public BoardPainter freshInstance(){
143     return this;
144   }
145 
146 
147 
148   /**
149    * Loads the painter from the specified URL. The file structure at the URL is
150    * described below.
151    * <p>A properties file named "definition" must be located at the URL.
152    * That file should contain the following property:
153    * <ul>
154    *   <li><code>ext</code>: Specifies the extension (type) of the
155    *       image(s) - gif, png, etc. If this is omitted, "gif" is assumed.
156    * </ul>
157    * An image file named <code>board.ext</code> must be located at the URL,
158    * where "ext" is the value of the <code>ext</code> property.
159    */
160 
load(URL url)161   public void load(URL url) throws IOException{
162     if (boardImageUrl != null)
163       throw new IllegalStateException("This BoardImageBoardPainter has already been loaded");
164 
165     URL defURL = new URL(url, "definition");
166 
167     Properties def = IOUtilities.loadProperties(defURL, true);
168     if (def == null)
169       def = new Properties();
170 
171     String ext = def.getProperty("ext", "gif");
172 
173     boardImageUrl = new URL(url, "board." + ext);
174   }
175 
176 
177 
178   /**
179    * If the board image is already loaded, prepares the scaled version for the
180    * specified board size and returns <code>true</code>. Otherwise, starts
181    * loading it. If asynchronous loading is enabled, the loading is done in a
182    * background thread, otherwise, waits until the loading is done.
183    * Returns whether the image is ready.
184    */
185 
prepareBoardImage(int width, int height, Component target)186   protected synchronized boolean prepareBoardImage(int width, int height, Component target){
187     if (boardImage == null){
188       if (imageDataReceiver != null){ // Already being loaded
189         imageDataReceiver.addComponentToRepaint(target);
190         return false;
191       }
192 
193       if (asyncImageLoad){
194         imageDataReceiver = new ImageDataReceiver(target);
195         IOUtilities.loadAsynchronously(new URL[]{boardImageUrl}, null, imageDataReceiver, true);
196         return false;
197       }
198       else{
199         imageDataReceiver = new ImageDataReceiver(null);
200         IOUtilities.loadSynchronously(new URL[]{boardImageUrl}, null, imageDataReceiver, true);
201         if (boardImage == null)
202           return false;
203 
204         scaleBoardImage(width, height);
205         return true;
206       }
207     }
208     else{
209       scaleBoardImage(width, height);
210       return true;
211     }
212   }
213 
214 
215 
216   /**
217    * Scales the board image to the specified size.
218    */
219 
scaleBoardImage(int width, int height)220   private void scaleBoardImage(int width, int height){
221     if ((scaledBoardImage != null) &&
222         (scaledBoardImage.getWidth(null) == width) &&
223         (scaledBoardImage.getHeight(null) == height))
224       return;
225 
226     scaledBoardImage = boardImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
227     ImageUtilities.preload(scaledBoardImage);
228   }
229 
230 
231 
232   /**
233    * Paints the board at the given location on the given Graphics scaled to
234    * the given size.
235    */
236 
paintBoard(Graphics g, Component component, int x, int y, int width, int height)237   public void paintBoard(Graphics g, Component component, int x, int y, int width, int height){
238     if (prepareBoardImage(width, height, component))
239       g.drawImage(scaledBoardImage, x, y, component);
240     else
241       whileLoadingDelegate.paintBoard(g, component, x, y, width, height);
242   }
243 
244 
245 
246 
247   /**
248    * The receiver of board image data. Responsible for creating the board image.
249    */
250 
251   private class ImageDataReceiver implements IOUtilities.DataReceiver{
252 
253 
254 
255     /**
256      * The set of components to repaint once the image is loaded.
257      */
258 
259     private final Set componentsToRepaint = new HashSet(2);
260 
261 
262 
263     /**
264      * Creates a new <code>ImageDataReceiver</code> with the specified component
265      * to repaint once the image data is loaded.
266      */
267 
ImageDataReceiver(Component component)268     public ImageDataReceiver(Component component){
269       componentsToRepaint.add(component);
270     }
271 
272 
273 
274     /**
275      * Adds the specified component to the set of components to repaint once
276      * loading the image is done.
277      */
278 
addComponentToRepaint(Component component)279     public void addComponentToRepaint(Component component){
280       componentsToRepaint.add(component);
281     }
282 
283 
284 
285     /**
286      * Called when the image data has been loaded. Creates the board image.
287      */
288 
dataRead(URL [] urls, Object id, byte [][] data, IOException [] exceptions)289     public void dataRead(URL [] urls, Object id, byte [][] data, IOException [] exceptions){
290       // If there are any exceptions, we simply quit - this will cause
291       // the painter to keep using the delegate to paint the board.
292       for (int i = 0; i < exceptions.length; i++)
293         if (exceptions[i] != null)
294           return;
295 
296       synchronized(BoardImageBoardPainter.this){
297         boardImage = Toolkit.getDefaultToolkit().createImage(data[0]);
298         ImageUtilities.preload(boardImage);
299 
300         imageDataReceiver = null;
301 
302         for (Iterator i = componentsToRepaint.iterator(); i.hasNext();){
303           Component component = (Component)i.next();
304           if (component != null)
305             component.repaint();
306         }
307       }
308     }
309 
310 
311 
312   }
313 
314 
315 
316 }
317