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