1. /*
  2. * @(#)LookAndFeel.java 1.19 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import java.awt.Font;
  9. import java.awt.event.InputEvent;
  10. import java.awt.event.KeyEvent;
  11. import java.awt.Color;
  12. import java.awt.SystemColor;
  13. import javax.swing.text.*;
  14. import javax.swing.border.*;
  15. import javax.swing.plaf.*;
  16. import java.net.URL;
  17. import java.io.*;
  18. import java.util.StringTokenizer;
  19. /**
  20. * Completely characterizes a look and feel from the point of view
  21. * of the pluggable look and feel components.
  22. *
  23. * @version 1.19 11/29/01
  24. * @author Tom Ball
  25. * @author Hans Muller
  26. */
  27. public abstract class LookAndFeel
  28. {
  29. /**
  30. * Convenience method for initializing a component's foreground
  31. * and background color properties with values from the current
  32. * defaults table. The properties are only set if the current
  33. * value is either null or a UIResource.
  34. *
  35. * @param c the target component for installing default color/font properties
  36. * @param defaultBgName the key for the default background
  37. * @param defaultFgName the key for the default foreground
  38. *
  39. * @see #installColorsAndFont
  40. * @see UIManager#getColor
  41. */
  42. public static void installColors(JComponent c,
  43. String defaultBgName,
  44. String defaultFgName)
  45. {
  46. Color bg = c.getBackground();
  47. if (bg == null || bg instanceof UIResource) {
  48. c.setBackground(UIManager.getColor(defaultBgName));
  49. }
  50. Color fg = c.getForeground();
  51. if (fg == null || fg instanceof UIResource) {
  52. c.setForeground(UIManager.getColor(defaultFgName));
  53. }
  54. }
  55. /**
  56. * Convenience method for initializing a components foreground
  57. * background and font properties with values from the current
  58. * defaults table. The properties are only set if the current
  59. * value is either null or a UIResource.
  60. *
  61. * @param c the target component for installing default color/font properties
  62. * @param defaultBgName the key for the default background
  63. * @param defaultFgName the key for the default foreground
  64. * @param defaultFontName the key for the default font
  65. *
  66. * @see #installColors
  67. * @see UIManager#getColor
  68. * @see UIManager#getFont
  69. */
  70. public static void installColorsAndFont(JComponent c,
  71. String defaultBgName,
  72. String defaultFgName,
  73. String defaultFontName) {
  74. Font f = c.getFont();
  75. if (f == null || f instanceof UIResource) {
  76. c.setFont(UIManager.getFont(defaultFontName));
  77. }
  78. installColors(c, defaultBgName, defaultFgName);
  79. }
  80. /**
  81. * Convenience method for installing a component's default Border
  82. * object on the specified component if either the border is
  83. * currently null or already an instance of UIResource.
  84. * @param c the target component for installing default border
  85. * @param defaultBorderName the key specifying the default border
  86. */
  87. public static void installBorder(JComponent c, String defaultBorderName) {
  88. Border b = c.getBorder();
  89. if (b == null || b instanceof UIResource) {
  90. c.setBorder(UIManager.getBorder(defaultBorderName));
  91. }
  92. }
  93. /**
  94. * Convenience method for un-installing a component's default
  95. * border on the specified component if the border is
  96. * currently an instance of UIResource.
  97. * @param c the target component for uninstalling default border
  98. */
  99. public static void uninstallBorder(JComponent c) {
  100. if (c.getBorder() instanceof UIResource) {
  101. c.setBorder(null);
  102. }
  103. }
  104. /*
  105. * // see parseKeyStroke (private)
  106. */
  107. private static class ModifierKeyword {
  108. final String keyword;
  109. final int mask;
  110. ModifierKeyword(String keyword, int mask) {
  111. this.keyword = keyword;
  112. this.mask = mask;
  113. }
  114. int getModifierMask(String s) {
  115. return (s.equals(keyword)) ? mask : 0;
  116. }
  117. };
  118. /*
  119. * // see parseKeyStroke (private)
  120. */
  121. private static ModifierKeyword[] modifierKeywords = {
  122. new ModifierKeyword("shift", InputEvent.SHIFT_MASK),
  123. new ModifierKeyword("control", InputEvent.CTRL_MASK),
  124. new ModifierKeyword("meta", InputEvent.META_MASK),
  125. new ModifierKeyword("alt", InputEvent.ALT_MASK),
  126. new ModifierKeyword("button1", InputEvent.BUTTON1_MASK),
  127. new ModifierKeyword("button2", InputEvent.BUTTON2_MASK),
  128. new ModifierKeyword("button3", InputEvent.BUTTON3_MASK)
  129. };
  130. /**
  131. * Parse a string with the following syntax and return an a KeyStroke:
  132. * <pre>
  133. * "<modifiers>* <key>"
  134. * modifiers := shift | control | meta | alt | button1 | button2 | button3
  135. * key := KeyEvent keycode name, i.e. the name following "VK_".
  136. * </pre>
  137. * Here are some examples:
  138. * <pre>
  139. * "INSERT" => new KeyStroke(0, KeyEvent.VK_INSERT);
  140. * "control DELETE" => new KeyStroke(InputEvent.CTRL_MASK, KeyEvent.VK_DELETE);
  141. * "alt shift X" => new KeyStroke(InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK, KeyEvent.VK_X);
  142. * </pre>
  143. */
  144. private static KeyStroke parseKeyStroke(String s)
  145. {
  146. StringTokenizer st = new StringTokenizer(s);
  147. String token;
  148. int mask = 0;
  149. while((token = st.nextToken()) != null) {
  150. int tokenMask = 0;
  151. /* if token matches a modifier keyword update mask and continue */
  152. for(int i = 0; (tokenMask == 0) && (i < modifierKeywords.length); i++) {
  153. tokenMask = modifierKeywords[i].getModifierMask(token);
  154. }
  155. if (tokenMask != 0) {
  156. mask |= tokenMask;
  157. continue;
  158. }
  159. /* otherwise token is the keycode name less the "VK_" prefix */
  160. String keycodeName = "VK_" + token;
  161. int keycode;
  162. try {
  163. keycode = KeyEvent.class.getField(keycodeName).getInt(KeyEvent.class);
  164. }
  165. catch (Exception e) {
  166. e.printStackTrace();
  167. throw new Error("Unrecognized keycode name: " + keycodeName);
  168. }
  169. return KeyStroke.getKeyStroke(keycode, mask);
  170. }
  171. throw new Error("Can't parse KeyStroke: \"" + s + "\"");
  172. }
  173. /**
  174. * Convenience method for building lists of KeyBindings.
  175. * <p>
  176. * Return an array of KeyBindings, one for each KeyStroke,Action pair
  177. * in <b>keyBindingList</b>. A KeyStroke can either be a string in
  178. * the format specified by the private <code>parseKeyStroke</code>
  179. * method or a KeyStroke object.
  180. * <p>
  181. * Actions are strings. Here's an example:
  182. * <pre>
  183. * JTextComponent.KeyBinding[] multilineBindings = makeKeyBindings( new Object[] {
  184. * "UP", DefaultEditorKit.upAction,
  185. * "DOWN", DefaultEditorKit.downAction,
  186. * "PAGE_UP", DefaultEditorKit.pageUpAction,
  187. * "PAGE_DOWN", DefaultEditorKit.pageDownAction,
  188. * "ENTER", DefaultEditorKit.insertBreakAction,
  189. * "TAB", DefaultEditorKit.insertTabAction
  190. * });
  191. * </pre>
  192. *
  193. * @param keyBindingList an array of KeyStroke,Action pairs
  194. * @return an array of KeyBindings
  195. */
  196. public static JTextComponent.KeyBinding[] makeKeyBindings(Object[] keyBindingList)
  197. {
  198. JTextComponent.KeyBinding[] rv = new JTextComponent.KeyBinding[keyBindingList.length / 2];
  199. for(int i = 0; i < keyBindingList.length; i += 2) {
  200. KeyStroke keystroke = (keyBindingList[i] instanceof KeyStroke)
  201. ? (KeyStroke)keyBindingList[i]
  202. : parseKeyStroke((String)keyBindingList[i]);
  203. String action = (String)keyBindingList[i+1];
  204. rv[i / 2] = new JTextComponent.KeyBinding(keystroke, action);
  205. }
  206. return rv;
  207. }
  208. /**
  209. * Utility method that creates a UIDefaults.LazyValue that creates
  210. * an ImageIcon UIResource for the specified <code>gifFile</code>
  211. * filename.
  212. */
  213. public static Object makeIcon(final Class baseClass, final String gifFile) {
  214. return new UIDefaults.LazyValue() {
  215. public Object createValue(UIDefaults table) {
  216. /* Copy resource into a byte array. This is
  217. * necessary because several browsers consider
  218. * Class.getResource a security risk because it
  219. * can be used to load additional classes.
  220. * Class.getResourceAsStream just returns raw
  221. * bytes, which we can convert to an image.
  222. */
  223. final byte[][] buffer = new byte[1][];
  224. SwingUtilities.doPrivileged(new Runnable() {
  225. public void run() {
  226. try {
  227. InputStream resource =
  228. baseClass.getResourceAsStream(gifFile);
  229. if (resource == null) {
  230. return;
  231. }
  232. BufferedInputStream in =
  233. new BufferedInputStream(resource);
  234. ByteArrayOutputStream out =
  235. new ByteArrayOutputStream(1024);
  236. buffer[0] = new byte[1024];
  237. int n;
  238. while ((n = in.read(buffer[0])) > 0) {
  239. out.write(buffer[0], 0, n);
  240. }
  241. in.close();
  242. out.flush();
  243. buffer[0] = out.toByteArray();
  244. } catch (IOException ioe) {
  245. System.err.println(ioe.toString());
  246. return;
  247. }
  248. }
  249. });
  250. if (buffer[0] == null) {
  251. System.err.println(baseClass.getName() + "/" +
  252. gifFile + " not found.");
  253. return null;
  254. }
  255. if (buffer[0].length == 0) {
  256. System.err.println("warning: " + gifFile +
  257. " is zero-length");
  258. return null;
  259. }
  260. return new IconUIResource(new ImageIcon(buffer[0]));
  261. }
  262. };
  263. }
  264. /**
  265. * Return a short string that identifies this look and feel, e.g.
  266. * "CDE/Motif". This string should be appropriate for a menu item.
  267. * Distinct look and feels should have different names, e.g.
  268. * a subclass of MotifLookAndFeel that changes the way a few components
  269. * are rendered should be called "CDE/Motif My Way"; something
  270. * that would be useful to a user trying to select a L&F from a list
  271. * of names.
  272. */
  273. public abstract String getName();
  274. /**
  275. * Return a string that identifies this look and feel. This string
  276. * will be used by applications/services that want to recognize
  277. * well known look and feel implementations. Presently
  278. * the well known names are "Motif", "Windows", "Mac", "Metal". Note
  279. * that a LookAndFeel derived from a well known superclass
  280. * that doesn't make any fundamental changes to the look or feel
  281. * shouldn't override this method.
  282. */
  283. public abstract String getID();
  284. /**
  285. * Return a one line description of this look and feel implementation,
  286. * e.g. "The CDE/Motif Look and Feel". This string is intended for
  287. * the user, e.g. in the title of a window or in a ToolTip message.
  288. */
  289. public abstract String getDescription();
  290. /**
  291. * If the underlying platform has a "native" look and feel, and this
  292. * is an implementation of it, return true. For example a CDE/Motif
  293. * look and implementation would return true when the underlying
  294. * platform was Solaris.
  295. */
  296. public abstract boolean isNativeLookAndFeel();
  297. /**
  298. * Return true if the underlying platform supports and or permits
  299. * this look and feel. This method returns false if the look
  300. * and feel depends on special resources or legal agreements that
  301. * aren't defined for the current platform.
  302. *
  303. * @see UIManager#setLookAndFeel
  304. */
  305. public abstract boolean isSupportedLookAndFeel();
  306. /**
  307. * UIManager.setLookAndFeel calls this method before the first
  308. * call (and typically the only call) to getDefaults(). Subclasses
  309. * should do any one-time setup they need here, rather than
  310. * in a static initializer, because look and feel class objects
  311. * may be loaded just to discover that isSupportedLookAndFeel()
  312. * returns false.
  313. *
  314. * @see #uninitialize
  315. * @see UIManager#setLookAndFeel
  316. */
  317. public void initialize() {
  318. }
  319. /**
  320. * UIManager.setLookAndFeel calls this method just before we're
  321. * replaced by a new default look and feel. Subclasses may
  322. * choose to free up some resources here.
  323. *
  324. * @see #initialize
  325. */
  326. public void uninitialize() {
  327. }
  328. /**
  329. * This method is called once by UIManager.setLookAndFeel to create
  330. * the look and feel specific defaults table. Other applications,
  331. * for example an application builder, may also call this method.
  332. *
  333. * @see #initialize
  334. * @see #uninitialize
  335. * @see UIManager#setLookAndFeel
  336. */
  337. public UIDefaults getDefaults() {
  338. return null;
  339. }
  340. /**
  341. * Returns a string that displays and identifies this
  342. * object's properties.
  343. *
  344. * @return a String representation of this object
  345. */
  346. public String toString() {
  347. return "[" + getDescription() + " - " + getClass().getName() + "]";
  348. }
  349. }