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