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