J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2005-2007 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | package java.awt; |
| 26 | |
| 27 | import java.io.IOException; |
| 28 | import java.awt.image.*; |
| 29 | import java.net.URL; |
| 30 | import java.net.URLConnection; |
| 31 | import java.io.File; |
| 32 | import java.util.logging.Logger; |
| 33 | import java.util.logging.Level; |
| 34 | import sun.awt.image.SunWritableRaster; |
| 35 | |
| 36 | /** |
| 37 | * The splash screen can be created at application startup, before the |
| 38 | * Java Virtual Machine (JVM) starts. The splash screen is displayed as an |
| 39 | * undecorated window containing an image. You can use GIF, JPEG, and PNG files |
| 40 | * for the image. Animation (for GIF) and transparency (for GIF, PNG) are |
| 41 | * supported. The window is positioned at the center of the screen (the |
| 42 | * position on multi-monitor systems is not specified - it is platform and |
| 43 | * implementation dependent). |
| 44 | * The window is closed automatically as soon as the first window is displayed by |
| 45 | * Swing/AWT (may be also closed manually using the Java API, see below). |
| 46 | * <P> |
| 47 | * There are two ways to show the native splash screen: |
| 48 | * <P> |
| 49 | * <UL> |
| 50 | * <LI>If your application is run from the command line or from a shortcut, |
| 51 | * use the "-splash:" Java application launcher option to show a splash screen. |
| 52 | * <BR> |
| 53 | * For example: |
| 54 | * <PRE> |
| 55 | * java -splash:filename.gif Test |
| 56 | * </PRE> |
| 57 | * <LI>If your application is packaged in a jar file, you can use the |
| 58 | * "SplashScreen-Image" option in a manifest file to show a splash screen. |
| 59 | * Place the image in the jar archive and specify the path in the option. |
| 60 | * The path should not have a leading slash. |
| 61 | * <BR> |
| 62 | * For example, in the <code>manifest.mf</code> file: |
| 63 | * <PRE> |
| 64 | * Manifest-Version: 1.0 |
| 65 | * Main-Class: Test |
| 66 | * SplashScreen-Image: filename.gif |
| 67 | * </PRE> |
| 68 | * The command line interface has higher precedence over the manifest |
| 69 | * setting. |
| 70 | * </UL> |
| 71 | * <p> |
| 72 | * The {@code SplashScreen} class provides the API for controlling the splash |
| 73 | * screen. This class may be used to close the splash screen, change the splash |
| 74 | * screen image, get the image position/size and paint in the splash screen. It |
| 75 | * cannot be used to create the splash screen; you should use the command line or manifest |
| 76 | * file option for that. |
| 77 | * <p> |
| 78 | * This class cannot be instantiated. Only a single instance of this class |
| 79 | * can exist, and it may be obtained using the {@link #getSplashScreen()} |
| 80 | * static method. In case the splash screen has not been created at |
| 81 | * application startup via the command line or manifest file option, |
| 82 | * the <code>getSplashScreen</code> method returns <code>null</code>. |
| 83 | * |
| 84 | * @author Oleg Semenov |
| 85 | * @since 1.6 |
| 86 | */ |
| 87 | public final class SplashScreen { |
| 88 | |
| 89 | SplashScreen(long ptr) { // non-public constructor |
| 90 | splashPtr = ptr; |
| 91 | } |
| 92 | |
| 93 | /** |
| 94 | * Returns the {@code SplashScreen} object used for |
| 95 | * Java startup splash screen control. |
| 96 | * |
| 97 | * @throws UnsupportedOperationException if the splash screen feature is not |
| 98 | * supported by the current toolkit |
| 99 | * @throws HeadlessException if {@code GraphicsEnvironment.isHeadless()} |
| 100 | * returns true |
| 101 | * @return the {@link SplashScreen} instance, or <code>null</code> if there is |
| 102 | * none or it has already been closed |
| 103 | */ |
| 104 | public static SplashScreen getSplashScreen() { |
| 105 | synchronized (SplashScreen.class) { |
| 106 | if (GraphicsEnvironment.isHeadless()) { |
| 107 | throw new HeadlessException(); |
| 108 | } |
| 109 | // SplashScreen class is now a singleton |
| 110 | if (!wasClosed && theInstance == null) { |
| 111 | java.security.AccessController.doPrivileged( |
| 112 | new sun.security.action.LoadLibraryAction("splashscreen")); |
| 113 | long ptr = _getInstance(); |
| 114 | if (ptr != 0 && _isVisible(ptr)) { |
| 115 | theInstance = new SplashScreen(ptr); |
| 116 | } |
| 117 | } |
| 118 | return theInstance; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Changes the splash screen image. The new image is loaded from the |
| 124 | * specified URL; GIF, JPEG and PNG image formats are supported. |
| 125 | * The method returns after the image has finished loading and the window |
| 126 | * has been updated. |
| 127 | * The splash screen window is resized according to the size of |
| 128 | * the image and is centered on the screen. |
| 129 | * |
| 130 | * @param imageURL the non-<code>null</code> URL for the new |
| 131 | * splash screen image |
| 132 | * @throws NullPointerException if {@code imageURL} is <code>null</code> |
| 133 | * @throws IOException if there was an error while loading the image |
| 134 | * @throws IllegalStateException if the splash screen has already been |
| 135 | * closed |
| 136 | */ |
| 137 | public void setImageURL(URL imageURL) throws NullPointerException, IOException, IllegalStateException { |
| 138 | checkVisible(); |
| 139 | URLConnection connection = imageURL.openConnection(); |
| 140 | connection.connect(); |
| 141 | int length = connection.getContentLength(); |
| 142 | java.io.InputStream stream = connection.getInputStream(); |
| 143 | byte[] buf = new byte[length]; |
| 144 | int off = 0; |
| 145 | while(true) { |
| 146 | // check for available data |
| 147 | int available = stream.available(); |
| 148 | if (available <= 0) { |
| 149 | // no data available... well, let's try reading one byte |
| 150 | // we'll see what happens then |
| 151 | available = 1; |
| 152 | } |
| 153 | // check for enough room in buffer, realloc if needed |
| 154 | // the buffer always grows in size 2x minimum |
| 155 | if (off + available > length) { |
| 156 | length = off*2; |
| 157 | if (off + available > length) { |
| 158 | length = available+off; |
| 159 | } |
| 160 | byte[] oldBuf = buf; |
| 161 | buf = new byte[length]; |
| 162 | System.arraycopy(oldBuf, 0, buf, 0, off); |
| 163 | } |
| 164 | // now read the data |
| 165 | int result = stream.read(buf, off, available); |
| 166 | if (result < 0) { |
| 167 | break; |
| 168 | } |
| 169 | off += result; |
| 170 | } |
| 171 | synchronized(SplashScreen.class) { |
| 172 | checkVisible(); |
| 173 | if (!_setImageData(splashPtr, buf)) { |
| 174 | throw new IOException("Bad image format or i/o error when loading image"); |
| 175 | } |
| 176 | this.imageURL = imageURL; |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | private void checkVisible() { |
| 181 | if (!isVisible()) { |
| 182 | throw new IllegalStateException("no splash screen available"); |
| 183 | } |
| 184 | } |
| 185 | /** |
| 186 | * Returns the current splash screen image. |
| 187 | * |
| 188 | * @return URL for the current splash screen image file |
| 189 | * @throws IllegalStateException if the splash screen has already been closed |
| 190 | */ |
| 191 | public URL getImageURL() throws IllegalStateException { |
| 192 | synchronized (SplashScreen.class) { |
| 193 | checkVisible(); |
| 194 | if (imageURL == null) { |
| 195 | try { |
| 196 | String fileName = _getImageFileName(splashPtr); |
| 197 | String jarName = _getImageJarName(splashPtr); |
| 198 | if (fileName != null) { |
| 199 | if (jarName != null) { |
| 200 | imageURL = new URL("jar:"+(new File(jarName).toURL().toString())+"!/"+fileName); |
| 201 | } else { |
| 202 | imageURL = new File(fileName).toURL(); |
| 203 | } |
| 204 | } |
| 205 | } |
| 206 | catch(java.net.MalformedURLException e) { |
| 207 | if (log.isLoggable(Level.FINE)) { |
| 208 | log.log(Level.FINE, "MalformedURLException caught in the getImageURL() method", e); |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | return imageURL; |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | /** |
| 217 | * Returns the bounds of the splash screen window as a {@link Rectangle}. |
| 218 | * This may be useful if, for example, you want to replace the splash |
| 219 | * screen with your window at the same location. |
| 220 | * <p> |
| 221 | * You cannot control the size or position of the splash screen. |
| 222 | * The splash screen size is adjusted automatically when the image changes. |
| 223 | * |
| 224 | * @return a {@code Rectangle} containing the splash screen bounds |
| 225 | * @throws IllegalStateException if the splash screen has already been closed |
| 226 | */ |
| 227 | public Rectangle getBounds() throws IllegalStateException { |
| 228 | synchronized (SplashScreen.class) { |
| 229 | checkVisible(); |
| 230 | return _getBounds(splashPtr); |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | /** |
| 235 | * Returns the size of the splash screen window as a {@link Dimension}. |
| 236 | * This may be useful if, for example, |
| 237 | * you want to draw on the splash screen overlay surface. |
| 238 | * <p> |
| 239 | * You cannot control the size or position of the splash screen. |
| 240 | * The splash screen size is adjusted automatically when the image changes. |
| 241 | * |
| 242 | * @return a {@link Dimension} object indicating the splash screen size |
| 243 | * @throws IllegalStateException if the splash screen has already been closed |
| 244 | */ |
| 245 | public Dimension getSize() throws IllegalStateException { |
| 246 | return getBounds().getSize(); |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Creates a graphics context (as a {@link Graphics2D} object) for the splash |
| 251 | * screen overlay image, which allows you to draw over the splash screen. |
| 252 | * Note that you do not draw on the main image but on the image that is |
| 253 | * displayed over the main image using alpha blending. Also note that drawing |
| 254 | * on the overlay image does not necessarily update the contents of splash |
| 255 | * screen window. You should call {@code update()} on the |
| 256 | * <code>SplashScreen</code> when you want the splash screen to be |
| 257 | * updated immediately. |
| 258 | * |
| 259 | * @return graphics context for the splash screen overlay surface |
| 260 | * @throws IllegalStateException if the splash screen has already been closed |
| 261 | */ |
| 262 | public Graphics2D createGraphics() throws IllegalStateException { |
| 263 | synchronized (SplashScreen.class) { |
| 264 | if (image==null) { |
| 265 | Dimension dim = getSize(); |
| 266 | image = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); |
| 267 | } |
| 268 | return image.createGraphics(); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Updates the splash window with current contents of the overlay image. |
| 274 | * |
| 275 | * @throws IllegalStateException if the overlay image does not exist; |
| 276 | * for example, if {@code createGraphics} has never been called, |
| 277 | * or if the splash screen has already been closed |
| 278 | */ |
| 279 | public void update() throws IllegalStateException { |
| 280 | BufferedImage image; |
| 281 | synchronized (SplashScreen.class) { |
| 282 | checkVisible(); |
| 283 | image = this.image; |
| 284 | } |
| 285 | if (image == null) { |
| 286 | throw new IllegalStateException("no overlay image available"); |
| 287 | } |
| 288 | DataBuffer buf = image.getRaster().getDataBuffer(); |
| 289 | if (!(buf instanceof DataBufferInt)) { |
| 290 | throw new AssertionError("Overlay image DataBuffer is of invalid type == "+buf.getClass().getName()); |
| 291 | } |
| 292 | int numBanks = buf.getNumBanks(); |
| 293 | if (numBanks!=1) { |
| 294 | throw new AssertionError("Invalid number of banks =="+numBanks+" in overlay image DataBuffer"); |
| 295 | } |
| 296 | if (!(image.getSampleModel() instanceof SinglePixelPackedSampleModel)) { |
| 297 | throw new AssertionError("Overlay image has invalid sample model == "+image.getSampleModel().getClass().getName()); |
| 298 | } |
| 299 | SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)image.getSampleModel(); |
| 300 | int scanlineStride = sm.getScanlineStride(); |
| 301 | Rectangle rect = image.getRaster().getBounds(); |
| 302 | // Note that we steal the data array here, but just for reading |
| 303 | // so we do not need to mark the DataBuffer dirty... |
| 304 | int[] data = SunWritableRaster.stealData((DataBufferInt)buf, 0); |
| 305 | synchronized(SplashScreen.class) { |
| 306 | checkVisible(); |
| 307 | _update(splashPtr, data, rect.x, rect.y, rect.width, rect.height, scanlineStride); |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | /** |
| 312 | * Hides the splash screen, closes the window, and releases all associated |
| 313 | * resources. |
| 314 | * |
| 315 | * @throws IllegalStateException if the splash screen has already been closed |
| 316 | */ |
| 317 | public void close() throws IllegalStateException { |
| 318 | synchronized (SplashScreen.class) { |
| 319 | checkVisible(); |
| 320 | _close(splashPtr); |
| 321 | image = null; |
| 322 | wasClosed = true; |
| 323 | theInstance = null; |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | |
| 328 | /** |
| 329 | * Determines whether the splash screen is visible. The splash screen may |
| 330 | * be hidden using {@link #close()}, it is also hidden automatically when |
| 331 | * the first AWT/Swing window is made visible. |
| 332 | * |
| 333 | * @return true if the splash screen is visible (has not been closed yet), |
| 334 | * false otherwise |
| 335 | */ |
| 336 | public boolean isVisible() { |
| 337 | synchronized (SplashScreen.class) { |
| 338 | return !wasClosed && _isVisible(splashPtr); |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | private BufferedImage image; // overlay image |
| 343 | |
| 344 | private final long splashPtr; // pointer to native Splash structure |
| 345 | private static boolean wasClosed = false; |
| 346 | |
| 347 | private URL imageURL; |
| 348 | |
| 349 | /** |
| 350 | * The instance reference for the singleton. |
| 351 | * (<code>null</code> if no instance exists yet.) |
| 352 | * |
| 353 | * @see #getSplashScreen |
| 354 | * @see #close |
| 355 | */ |
| 356 | private static SplashScreen theInstance = null; |
| 357 | |
| 358 | private static final Logger log = Logger.getLogger("java.awt.SplashScreen"); |
| 359 | |
| 360 | private native static void _update(long splashPtr, int[] data, int x, int y, int width, int height, int scanlineStride); |
| 361 | private native static boolean _isVisible(long splashPtr); |
| 362 | private native static Rectangle _getBounds(long splashPtr); |
| 363 | private native static long _getInstance(); |
| 364 | private native static void _close(long splashPtr); |
| 365 | private native static String _getImageFileName(long splashPtr); |
| 366 | private native static String _getImageJarName(long SplashPtr); |
| 367 | private native static boolean _setImageData(long SplashPtr, byte[] data); |
| 368 | |
| 369 | }; |