1 /* 2 * Copyright (c) 1996, 2015, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.awt; 26 27 import java.beans.ConstructorProperties; 28 import java.io.ByteArrayOutputStream; 29 import java.io.InputStream; 30 import java.security.AccessController; 31 import java.security.PrivilegedAction; 32 import java.security.PrivilegedExceptionAction; 33 import java.util.Hashtable; 34 import java.util.Properties; 35 import java.util.StringTokenizer; 36 37 import sun.awt.AWTAccessor; 38 import sun.util.logging.PlatformLogger; 39 40 /** 41 * A class to encapsulate the bitmap representation of the mouse cursor. 42 * 43 * @see Component#setCursor 44 * @author Amy Fowler 45 */ 46 public class Cursor implements java.io.Serializable { 47 48 /** 49 * The default cursor type (gets set if no cursor is defined). 50 */ 51 public static final int DEFAULT_CURSOR = 0; 52 53 /** 54 * The crosshair cursor type. 55 */ 56 public static final int CROSSHAIR_CURSOR = 1; 57 58 /** 59 * The text cursor type. 60 */ 61 public static final int TEXT_CURSOR = 2; 62 63 /** 64 * The wait cursor type. 65 */ 66 public static final int WAIT_CURSOR = 3; 67 68 /** 69 * The south-west-resize cursor type. 70 */ 71 public static final int SW_RESIZE_CURSOR = 4; 72 73 /** 74 * The south-east-resize cursor type. 75 */ 76 public static final int SE_RESIZE_CURSOR = 5; 77 78 /** 79 * The north-west-resize cursor type. 80 */ 81 public static final int NW_RESIZE_CURSOR = 6; 82 83 /** 84 * The north-east-resize cursor type. 85 */ 86 public static final int NE_RESIZE_CURSOR = 7; 87 88 /** 89 * The north-resize cursor type. 90 */ 91 public static final int N_RESIZE_CURSOR = 8; 92 93 /** 94 * The south-resize cursor type. 95 */ 96 public static final int S_RESIZE_CURSOR = 9; 97 98 /** 99 * The west-resize cursor type. 100 */ 101 public static final int W_RESIZE_CURSOR = 10; 102 103 /** 104 * The east-resize cursor type. 105 */ 106 public static final int E_RESIZE_CURSOR = 11; 107 108 /** 109 * The hand cursor type. 110 */ 111 public static final int HAND_CURSOR = 12; 112 113 /** 114 * The move cursor type. 115 */ 116 public static final int MOVE_CURSOR = 13; 117 118 /** 119 * @deprecated As of JDK version 1.7, the {@link #getPredefinedCursor(int)} 120 * method should be used instead. 121 */ 122 @Deprecated 123 protected static Cursor predefined[] = new Cursor[14]; 124 125 /** 126 * This field is a private replacement for 'predefined' array. 127 */ 128 private final static Cursor[] predefinedPrivate = new Cursor[14]; 129 130 /* Localization names and default values */ 131 static final String[][] cursorProperties = { 132 { "AWT.DefaultCursor", "Default Cursor" }, 133 { "AWT.CrosshairCursor", "Crosshair Cursor" }, 134 { "AWT.TextCursor", "Text Cursor" }, 135 { "AWT.WaitCursor", "Wait Cursor" }, 136 { "AWT.SWResizeCursor", "Southwest Resize Cursor" }, 137 { "AWT.SEResizeCursor", "Southeast Resize Cursor" }, 138 { "AWT.NWResizeCursor", "Northwest Resize Cursor" }, 139 { "AWT.NEResizeCursor", "Northeast Resize Cursor" }, 140 { "AWT.NResizeCursor", "North Resize Cursor" }, 141 { "AWT.SResizeCursor", "South Resize Cursor" }, 142 { "AWT.WResizeCursor", "West Resize Cursor" }, 143 { "AWT.EResizeCursor", "East Resize Cursor" }, 144 { "AWT.HandCursor", "Hand Cursor" }, 145 { "AWT.MoveCursor", "Move Cursor" }, 146 }; 147 148 /** 149 * The chosen cursor type initially set to 150 * the <code>DEFAULT_CURSOR</code>. 151 * 152 * @serial 153 * @see #getType() 154 */ 155 int type = DEFAULT_CURSOR; 156 157 /** 158 * The type associated with all custom cursors. 159 */ 160 public static final int CUSTOM_CURSOR = -1; 161 162 /* 163 * hashtable, resource prefix, filename, and properties for custom cursors 164 * support 165 */ 166 private static final Hashtable<String,Cursor> systemCustomCursors = new Hashtable<>(1); 167 private static final String RESOURCE_PREFIX = "/sun/awt/resources/cursors/"; 168 private static final String PROPERTIES_FILE = RESOURCE_PREFIX + "cursors.properties"; 169 170 private static Properties systemCustomCursorProperties = null; 171 172 private static final String CURSOR_DOT_PREFIX = "Cursor."; 173 private static final String DOT_FILE_SUFFIX = ".File"; 174 private static final String DOT_HOTSPOT_SUFFIX = ".HotSpot"; 175 private static final String DOT_NAME_SUFFIX = ".Name"; 176 177 /* 178 * JDK 1.1 serialVersionUID 179 */ 180 private static final long serialVersionUID = 8028237497568985504L; 181 182 private static final PlatformLogger log = PlatformLogger.getLogger("java.awt.Cursor"); 183 184 static { 185 /* ensure that the necessary native libraries are loaded */ 186 Toolkit.loadLibraries(); 187 if (!GraphicsEnvironment.isHeadless()) { 188 initIDs(); 189 } 190 191 AWTAccessor.setCursorAccessor( 192 new AWTAccessor.CursorAccessor() { 193 public long getPData(Cursor cursor) { 194 return cursor.pData; 195 } 196 197 public void setPData(Cursor cursor, long pData) { 198 cursor.pData = pData; 199 } 200 201 public int getType(Cursor cursor) { 202 return cursor.type; 203 } 204 }); 205 } 206 207 /** 208 * Initialize JNI field and method IDs for fields that may be 209 * accessed from C. 210 */ 211 private static native void initIDs(); 212 213 /** 214 * Hook into native data. 215 */ 216 private transient long pData; 217 218 private transient Object anchor = new Object(); 219 220 static class CursorDisposer implements sun.java2d.DisposerRecord { 221 volatile long pData; 222 public CursorDisposer(long pData) { 223 this.pData = pData; 224 } 225 public void dispose() { 226 if (pData != 0) { 227 finalizeImpl(pData); 228 } 229 } 230 } 231 transient CursorDisposer disposer; 232 private void setPData(long pData) { 233 this.pData = pData; 234 if (GraphicsEnvironment.isHeadless()) { 235 return; 236 } 237 if (disposer == null) { 238 disposer = new CursorDisposer(pData); 239 // anchor is null after deserialization 240 if (anchor == null) { 241 anchor = new Object(); 242 } 243 sun.java2d.Disposer.addRecord(anchor, disposer); 244 } else { 245 disposer.pData = pData; 246 } 247 } 248 249 /** 250 * The user-visible name of the cursor. 251 * 252 * @serial 253 * @see #getName() 254 */ 255 protected String name; 256 257 /** 258 * Returns a cursor object with the specified predefined type. 259 * 260 * @param type the type of predefined cursor 261 * @return the specified predefined cursor 262 * @throws IllegalArgumentException if the specified cursor type is 263 * invalid 264 */ 265 public static Cursor getPredefinedCursor(int type) { 266 if (type < Cursor.DEFAULT_CURSOR || type > Cursor.MOVE_CURSOR) { 267 throw new IllegalArgumentException("illegal cursor type"); 268 } 269 Cursor c = predefinedPrivate[type]; 270 if (c == null) { 271 predefinedPrivate[type] = c = new Cursor(type); 272 } 273 // fill 'predefined' array for backwards compatibility. 274 if (predefined[type] == null) { 275 predefined[type] = c; 276 } 277 return c; 278 } 279 280 /** 281 * Returns a system-specific custom cursor object matching the 282 * specified name. Cursor names are, for example: "Invalid.16x16" 283 * 284 * @param name a string describing the desired system-specific custom cursor 285 * @return the system specific custom cursor named 286 * @exception HeadlessException if 287 * <code>GraphicsEnvironment.isHeadless</code> returns true 288 * @exception AWTException in case of erroneous retrieving of the cursor 289 */ 290 public static Cursor getSystemCustomCursor(final String name) 291 throws AWTException, HeadlessException { 292 GraphicsEnvironment.checkHeadless(); 293 Cursor cursor = systemCustomCursors.get(name); 294 295 if (cursor == null) { 296 synchronized(systemCustomCursors) { 297 if (systemCustomCursorProperties == null) 298 loadSystemCustomCursorProperties(); 299 } 300 301 String prefix = CURSOR_DOT_PREFIX + name; 302 String key = prefix + DOT_FILE_SUFFIX; 303 304 if (!systemCustomCursorProperties.containsKey(key)) { 305 if (log.isLoggable(PlatformLogger.Level.FINER)) { 306 log.finer("Cursor.getSystemCustomCursor(" + name + ") returned null"); 307 } 308 return null; 309 } 310 311 final String fileName = 312 systemCustomCursorProperties.getProperty(key); 313 314 final String localized = systemCustomCursorProperties.getProperty( 315 prefix + DOT_NAME_SUFFIX, name); 316 317 String hotspot = systemCustomCursorProperties.getProperty(prefix + DOT_HOTSPOT_SUFFIX); 318 319 if (hotspot == null) 320 throw new AWTException("no hotspot property defined for cursor: " + name); 321 322 StringTokenizer st = new StringTokenizer(hotspot, ","); 323 324 if (st.countTokens() != 2) 325 throw new AWTException("failed to parse hotspot property for cursor: " + name); 326 327 final Point hotPoint; 328 try { 329 hotPoint = new Point(Integer.parseInt(st.nextToken()), 330 Integer.parseInt(st.nextToken())); 331 } catch (NumberFormatException nfe) { 332 throw new AWTException("failed to parse hotspot property for cursor: " + name); 333 } 334 final Toolkit toolkit = Toolkit.getDefaultToolkit(); 335 final String file = RESOURCE_PREFIX + fileName; 336 final InputStream in = AccessController.doPrivileged( 337 (PrivilegedAction<InputStream>) () -> { 338 return Cursor.class.getResourceAsStream(file); 339 }); 340 try (in) { 341 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 342 in.transferTo(baos); 343 Image image = toolkit.createImage(baos.toByteArray()); 344 cursor = toolkit.createCustomCursor(image, hotPoint, localized); 345 } catch (Exception e) { 346 throw new AWTException( 347 "Exception: " + e.getClass() + " " + e.getMessage() + 348 " occurred while creating cursor " + name); 349 } 350 351 if (cursor == null) { 352 if (log.isLoggable(PlatformLogger.Level.FINER)) { 353 log.finer("Cursor.getSystemCustomCursor(" + name + ") returned null"); 354 } 355 } else { 356 systemCustomCursors.put(name, cursor); 357 } 358 } 359 360 return cursor; 361 } 362 363 /** 364 * Return the system default cursor. 365 * 366 * @return the default cursor 367 */ 368 public static Cursor getDefaultCursor() { 369 return getPredefinedCursor(Cursor.DEFAULT_CURSOR); 370 } 371 372 /** 373 * Creates a new cursor object with the specified type. 374 * @param type the type of cursor 375 * @throws IllegalArgumentException if the specified cursor type 376 * is invalid 377 */ 378 @ConstructorProperties({"type"}) 379 public Cursor(int type) { 380 if (type < Cursor.DEFAULT_CURSOR || type > Cursor.MOVE_CURSOR) { 381 throw new IllegalArgumentException("illegal cursor type"); 382 } 383 this.type = type; 384 385 // Lookup localized name. 386 name = Toolkit.getProperty(cursorProperties[type][0], 387 cursorProperties[type][1]); 388 } 389 390 /** 391 * Creates a new custom cursor object with the specified name.<p> 392 * Note: this constructor should only be used by AWT implementations 393 * as part of their support for custom cursors. Applications should 394 * use Toolkit.createCustomCursor(). 395 * @param name the user-visible name of the cursor. 396 * @see java.awt.Toolkit#createCustomCursor 397 */ 398 protected Cursor(String name) { 399 this.type = Cursor.CUSTOM_CURSOR; 400 this.name = name; 401 } 402 403 /** 404 * Returns the type for this cursor. 405 * 406 * @return the cursor type 407 */ 408 public int getType() { 409 return type; 410 } 411 412 /** 413 * Returns the name of this cursor. 414 * @return a localized description of this cursor. 415 * @since 1.2 416 */ 417 public String getName() { 418 return name; 419 } 420 421 /** 422 * Returns a string representation of this cursor. 423 * @return a string representation of this cursor. 424 * @since 1.2 425 */ 426 public String toString() { 427 return getClass().getName() + "[" + getName() + "]"; 428 } 429 430 /* 431 * load the cursor.properties file 432 */ 433 private static void loadSystemCustomCursorProperties() throws AWTException { 434 synchronized(systemCustomCursors) { 435 systemCustomCursorProperties = new Properties(); 436 437 try { 438 AccessController.doPrivileged( 439 (PrivilegedExceptionAction<Object>) () -> { 440 try (InputStream is = Cursor.class 441 .getResourceAsStream(PROPERTIES_FILE)) { 442 systemCustomCursorProperties.load(is); 443 } 444 return null; 445 }); 446 } catch (Exception e) { 447 systemCustomCursorProperties = null; 448 throw new AWTException("Exception: " + e.getClass() + " " + 449 e.getMessage() + " occurred while loading: " + 450 PROPERTIES_FILE); 451 } 452 } 453 } 454 455 private native static void finalizeImpl(long pData); 456 }