View Javadoc
1   /*
2    * Copyright (c) 1996, 2013, 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.io.File;
28  import java.io.FileInputStream;
29  
30  import java.beans.ConstructorProperties;
31  import java.util.Hashtable;
32  import java.util.Properties;
33  import java.util.StringTokenizer;
34  
35  import java.security.AccessController;
36  
37  import sun.util.logging.PlatformLogger;
38  import sun.awt.AWTAccessor;
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, filesystem dir prefix, filename, and properties for custom cursors support
164      */
165 
166     private static final Hashtable<String,Cursor> systemCustomCursors = new Hashtable<>(1);
167     private static final String systemCustomCursorDirPrefix = initCursorDir();
168 
169     private static String initCursorDir() {
170         String jhome = java.security.AccessController.doPrivileged(
171                new sun.security.action.GetPropertyAction("java.home"));
172         return jhome +
173             File.separator + "lib" + File.separator + "images" +
174             File.separator + "cursors" + File.separator;
175     }
176 
177     private static final String     systemCustomCursorPropertiesFile = systemCustomCursorDirPrefix + "cursors.properties";
178 
179     private static       Properties systemCustomCursorProperties = null;
180 
181     private static final String CursorDotPrefix  = "Cursor.";
182     private static final String DotFileSuffix    = ".File";
183     private static final String DotHotspotSuffix = ".HotSpot";
184     private static final String DotNameSuffix    = ".Name";
185 
186     /*
187      * JDK 1.1 serialVersionUID
188      */
189     private static final long serialVersionUID = 8028237497568985504L;
190 
191     private static final PlatformLogger log = PlatformLogger.getLogger("java.awt.Cursor");
192 
193     static {
194         /* ensure that the necessary native libraries are loaded */
195         Toolkit.loadLibraries();
196         if (!GraphicsEnvironment.isHeadless()) {
197             initIDs();
198         }
199 
200         AWTAccessor.setCursorAccessor(
201             new AWTAccessor.CursorAccessor() {
202                 public long getPData(Cursor cursor) {
203                     return cursor.pData;
204                 }
205 
206                 public void setPData(Cursor cursor, long pData) {
207                     cursor.pData = pData;
208                 }
209 
210                 public int getType(Cursor cursor) {
211                     return cursor.type;
212                 }
213             });
214     }
215 
216     /**
217      * Initialize JNI field and method IDs for fields that may be
218      * accessed from C.
219      */
220     private static native void initIDs();
221 
222     /**
223      * Hook into native data.
224      */
225     private transient long pData;
226 
227     private transient Object anchor = new Object();
228 
229     static class CursorDisposer implements sun.java2d.DisposerRecord {
230         volatile long pData;
231         public CursorDisposer(long pData) {
232             this.pData = pData;
233         }
234         public void dispose() {
235             if (pData != 0) {
236                 finalizeImpl(pData);
237             }
238         }
239     }
240     transient CursorDisposer disposer;
241     private void setPData(long pData) {
242         this.pData = pData;
243         if (GraphicsEnvironment.isHeadless()) {
244             return;
245         }
246         if (disposer == null) {
247             disposer = new CursorDisposer(pData);
248             // anchor is null after deserialization
249             if (anchor == null) {
250                 anchor = new Object();
251             }
252             sun.java2d.Disposer.addRecord(anchor, disposer);
253         } else {
254             disposer.pData = pData;
255         }
256     }
257 
258     /**
259      * The user-visible name of the cursor.
260      *
261      * @serial
262      * @see #getName()
263      */
264     protected String name;
265 
266     /**
267      * Returns a cursor object with the specified predefined type.
268      *
269      * @param type the type of predefined cursor
270      * @return the specified predefined cursor
271      * @throws IllegalArgumentException if the specified cursor type is
272      *         invalid
273      */
274     static public Cursor getPredefinedCursor(int type) {
275         if (type < Cursor.DEFAULT_CURSOR || type > Cursor.MOVE_CURSOR) {
276             throw new IllegalArgumentException("illegal cursor type");
277         }
278         Cursor c = predefinedPrivate[type];
279         if (c == null) {
280             predefinedPrivate[type] = c = new Cursor(type);
281         }
282         // fill 'predefined' array for backwards compatibility.
283         if (predefined[type] == null) {
284             predefined[type] = c;
285         }
286         return c;
287     }
288 
289     /**
290      * Returns a system-specific custom cursor object matching the
291      * specified name.  Cursor names are, for example: "Invalid.16x16"
292      *
293      * @param name a string describing the desired system-specific custom cursor
294      * @return the system specific custom cursor named
295      * @exception HeadlessException if
296      * <code>GraphicsEnvironment.isHeadless</code> returns true
297      */
298     static public Cursor getSystemCustomCursor(final String name)
299         throws AWTException, HeadlessException {
300         GraphicsEnvironment.checkHeadless();
301         Cursor cursor = systemCustomCursors.get(name);
302 
303         if (cursor == null) {
304             synchronized(systemCustomCursors) {
305                 if (systemCustomCursorProperties == null)
306                     loadSystemCustomCursorProperties();
307             }
308 
309             String prefix = CursorDotPrefix + name;
310             String key    = prefix + DotFileSuffix;
311 
312             if (!systemCustomCursorProperties.containsKey(key)) {
313                 if (log.isLoggable(PlatformLogger.Level.FINER)) {
314                     log.finer("Cursor.getSystemCustomCursor(" + name + ") returned null");
315                 }
316                 return null;
317             }
318 
319             final String fileName =
320                 systemCustomCursorProperties.getProperty(key);
321 
322             String localized = systemCustomCursorProperties.getProperty(prefix + DotNameSuffix);
323 
324             if (localized == null) localized = name;
325 
326             String hotspot = systemCustomCursorProperties.getProperty(prefix + DotHotspotSuffix);
327 
328             if (hotspot == null)
329                 throw new AWTException("no hotspot property defined for cursor: " + name);
330 
331             StringTokenizer st = new StringTokenizer(hotspot, ",");
332 
333             if (st.countTokens() != 2)
334                 throw new AWTException("failed to parse hotspot property for cursor: " + name);
335 
336             int x = 0;
337             int y = 0;
338 
339             try {
340                 x = Integer.parseInt(st.nextToken());
341                 y = Integer.parseInt(st.nextToken());
342             } catch (NumberFormatException nfe) {
343                 throw new AWTException("failed to parse hotspot property for cursor: " + name);
344             }
345 
346             try {
347                 final int fx = x;
348                 final int fy = y;
349                 final String flocalized = localized;
350 
351                 cursor = java.security.AccessController.<Cursor>doPrivileged(
352                     new java.security.PrivilegedExceptionAction<Cursor>() {
353                     public Cursor run() throws Exception {
354                         Toolkit toolkit = Toolkit.getDefaultToolkit();
355                         Image image = toolkit.getImage(
356                            systemCustomCursorDirPrefix + fileName);
357                         return toolkit.createCustomCursor(
358                                     image, new Point(fx,fy), flocalized);
359                     }
360                 });
361             } catch (Exception e) {
362                 throw new AWTException(
363                     "Exception: " + e.getClass() + " " + e.getMessage() +
364                     " occurred while creating cursor " + name);
365             }
366 
367             if (cursor == null) {
368                 if (log.isLoggable(PlatformLogger.Level.FINER)) {
369                     log.finer("Cursor.getSystemCustomCursor(" + name + ") returned null");
370                 }
371             } else {
372                 systemCustomCursors.put(name, cursor);
373             }
374         }
375 
376         return cursor;
377     }
378 
379     /**
380      * Return the system default cursor.
381      */
382     static public Cursor getDefaultCursor() {
383         return getPredefinedCursor(Cursor.DEFAULT_CURSOR);
384     }
385 
386     /**
387      * Creates a new cursor object with the specified type.
388      * @param type the type of cursor
389      * @throws IllegalArgumentException if the specified cursor type
390      * is invalid
391      */
392     @ConstructorProperties({"type"})
393     public Cursor(int type) {
394         if (type < Cursor.DEFAULT_CURSOR || type > Cursor.MOVE_CURSOR) {
395             throw new IllegalArgumentException("illegal cursor type");
396         }
397         this.type = type;
398 
399         // Lookup localized name.
400         name = Toolkit.getProperty(cursorProperties[type][0],
401                                    cursorProperties[type][1]);
402     }
403 
404     /**
405      * Creates a new custom cursor object with the specified name.<p>
406      * Note:  this constructor should only be used by AWT implementations
407      * as part of their support for custom cursors.  Applications should
408      * use Toolkit.createCustomCursor().
409      * @param name the user-visible name of the cursor.
410      * @see java.awt.Toolkit#createCustomCursor
411      */
412     protected Cursor(String name) {
413         this.type = Cursor.CUSTOM_CURSOR;
414         this.name = name;
415     }
416 
417     /**
418      * Returns the type for this cursor.
419      */
420     public int getType() {
421         return type;
422     }
423 
424     /**
425      * Returns the name of this cursor.
426      * @return    a localized description of this cursor.
427      * @since     1.2
428      */
429     public String getName() {
430         return name;
431     }
432 
433     /**
434      * Returns a string representation of this cursor.
435      * @return    a string representation of this cursor.
436      * @since     1.2
437      */
438     public String toString() {
439         return getClass().getName() + "[" + getName() + "]";
440     }
441 
442     /*
443      * load the cursor.properties file
444      */
445     private static void loadSystemCustomCursorProperties() throws AWTException {
446         synchronized(systemCustomCursors) {
447             systemCustomCursorProperties = new Properties();
448 
449             try {
450                 AccessController.<Object>doPrivileged(
451                       new java.security.PrivilegedExceptionAction<Object>() {
452                     public Object run() throws Exception {
453                         FileInputStream fis = null;
454                         try {
455                             fis = new FileInputStream(
456                                            systemCustomCursorPropertiesFile);
457                             systemCustomCursorProperties.load(fis);
458                         } finally {
459                             if (fis != null)
460                                 fis.close();
461                         }
462                         return null;
463                     }
464                 });
465             } catch (Exception e) {
466                 systemCustomCursorProperties = null;
467                  throw new AWTException("Exception: " + e.getClass() + " " +
468                    e.getMessage() + " occurred while loading: " +
469                                         systemCustomCursorPropertiesFile);
470             }
471         }
472     }
473 
474     private native static void finalizeImpl(long pData);
475 }