1. /*
  2. * @(#)SynthLookAndFeel.java 1.45 04/05/07
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.synth;
  8. import sun.swing.DefaultLookup;
  9. import java.awt.*;
  10. import java.awt.event.*;
  11. import java.beans.*;
  12. import java.io.*;
  13. import java.lang.ref.*;
  14. import java.text.*;
  15. import java.util.*;
  16. import javax.swing.*;
  17. import javax.swing.border.*;
  18. import javax.swing.plaf.*;
  19. import javax.swing.plaf.basic.*;
  20. import sun.awt.AppContext;
  21. import sun.swing.plaf.synth.*;
  22. /**
  23. * SynthLookAndFeel provides the basis for creating a customized look and
  24. * feel. SynthLookAndFeel does not directly provide a look, all painting is
  25. * delegated.
  26. * You need to either provide a configuration file, by way of the
  27. * {@link #load} method, or provide your own {@link SynthStyleFactory}
  28. * to {@link #setStyleFactory}. Refer to the
  29. * <a href="package-summary.html">package summary</a> for an example of
  30. * loading a file, and {@link javax.swing.plaf.synth.SynthStyleFactory} for
  31. * an example of providing your own <code>SynthStyleFactory</code> to
  32. * <code>setStyleFactory</code>.
  33. * <p>
  34. * <strong>Warning:</strong>
  35. * This class implements {@link Serializable} as a side effect of it
  36. * extending {@link BasicLookAndFeel}. It is not intended to be serialized.
  37. * An attempt to serialize it will
  38. * result in {@link NotSerializableException}.
  39. *
  40. * @serial exclude
  41. * @version 1.45, 05/07/04
  42. * @since 1.5
  43. * @author Scott Violet
  44. */
  45. public class SynthLookAndFeel extends BasicLookAndFeel {
  46. /**
  47. * Used in a handful of places where we need an empty Insets.
  48. */
  49. static final Insets EMPTY_UIRESOURCE_INSETS = new InsetsUIResource(
  50. 0, 0, 0, 0);
  51. /**
  52. * AppContext key to get the current SynthStyleFactory.
  53. */
  54. private static final Object STYLE_FACTORY_KEY =
  55. new StringBuffer("com.sun.java.swing.plaf.gtk.StyleCache");
  56. /**
  57. * The last SynthStyleFactory that was asked for from AppContext
  58. * <code>lastContext</code>.
  59. */
  60. private static SynthStyleFactory lastFactory;
  61. /**
  62. * If this is true it indicates there is more than one AppContext active
  63. * and that we need to make sure in getStyleCache the requesting
  64. * AppContext matches that of <code>lastContext</code> before returning
  65. * it.
  66. */
  67. private static boolean multipleApps;
  68. /**
  69. * AppContext lastLAF came from.
  70. */
  71. private static AppContext lastContext;
  72. // Refer to setSelectedUI
  73. static ComponentUI selectedUI;
  74. // Refer to setSelectedUI
  75. static int selectedUIState;
  76. /**
  77. * SynthStyleFactory for the this SynthLookAndFeel.
  78. */
  79. private SynthStyleFactory factory;
  80. /**
  81. * Map of defaults table entries. This is populated via the load
  82. * method.
  83. */
  84. private Map defaultsMap;
  85. /**
  86. * Used by the renderers. For the most part the renderers are implemented
  87. * as Labels, which is problematic in so far as they are never selected.
  88. * To accomodate this SynthLabelUI checks if the current
  89. * UI matches that of <code>selectedUI</code> (which this methods sets), if
  90. * it does, then a state as set by this method is returned. This provides
  91. * a way for labels to have a state other than selected.
  92. */
  93. static void setSelectedUI(ComponentUI uix, boolean selected,
  94. boolean focused, boolean enabled) {
  95. selectedUI = uix;
  96. selectedUIState = 0;
  97. if (selected) {
  98. selectedUIState = SynthConstants.SELECTED;
  99. if (focused) {
  100. selectedUIState |= SynthConstants.FOCUSED;
  101. }
  102. }
  103. else {
  104. selectedUIState = SynthConstants.FOCUSED;
  105. if (enabled) {
  106. selectedUIState |= SynthConstants.ENABLED;
  107. }
  108. else {
  109. selectedUIState |= SynthConstants.DISABLED;
  110. }
  111. }
  112. }
  113. /**
  114. * Clears out the selected UI that was last set in setSelectedUI.
  115. */
  116. static void resetSelectedUI() {
  117. selectedUI = null;
  118. }
  119. /**
  120. * Sets the SynthStyleFactory that the UI classes provided by
  121. * synth will use to obtain a SynthStyle.
  122. *
  123. * @param cache SynthStyleFactory the UIs should use.
  124. */
  125. public static void setStyleFactory(SynthStyleFactory cache) {
  126. // We assume the setter is called BEFORE the getter has been invoked
  127. // for a particular AppContext.
  128. synchronized(SynthLookAndFeel.class) {
  129. AppContext context = AppContext.getAppContext();
  130. if (!multipleApps && context != lastContext &&
  131. lastContext != null) {
  132. multipleApps = true;
  133. }
  134. lastFactory = cache;
  135. lastContext = context;
  136. context.put(STYLE_FACTORY_KEY, cache);
  137. }
  138. }
  139. /**
  140. * Returns the current SynthStyleFactory.
  141. *
  142. * @return SynthStyleFactory
  143. */
  144. public static SynthStyleFactory getStyleFactory() {
  145. synchronized(SynthLookAndFeel.class) {
  146. if (!multipleApps) {
  147. return lastFactory;
  148. }
  149. AppContext context = AppContext.getAppContext();
  150. if (lastContext == context) {
  151. return lastFactory;
  152. }
  153. lastContext = context;
  154. lastFactory = (SynthStyleFactory)AppContext.getAppContext().get
  155. (STYLE_FACTORY_KEY);
  156. return lastFactory;
  157. }
  158. }
  159. /**
  160. * Returns the component state for the specified component. This should
  161. * only be used for Components that don't have any special state beyond
  162. * that of ENABLED, DISABLED or FOCUSED. For example, buttons shouldn't
  163. * call into this method.
  164. */
  165. static int getComponentState(Component c) {
  166. if (c.isEnabled()) {
  167. if (c.isFocusOwner()) {
  168. return SynthUI.ENABLED | SynthUI.FOCUSED;
  169. }
  170. return SynthUI.ENABLED;
  171. }
  172. return SynthUI.DISABLED;
  173. }
  174. /**
  175. * Gets a SynthStyle for the specified region of the specified component.
  176. * This is not for general consumption, only custom UIs should call this
  177. * method.
  178. *
  179. * @param c JComponent to get the SynthStyle for
  180. * @param region Identifies the region of the specified component
  181. * @return SynthStyle to use.
  182. */
  183. public static SynthStyle getStyle(JComponent c, Region region) {
  184. return getStyleFactory().getStyle(c, region);
  185. }
  186. /**
  187. * Returns true if the Style should be updated in response to the
  188. * specified PropertyChangeEvent. This forwards to
  189. * <code>shouldUpdateStyleOnAncestorChanged</code> as necessary.
  190. */
  191. static boolean shouldUpdateStyle(PropertyChangeEvent event) {
  192. String eName = event.getPropertyName();
  193. if ("name" == eName) {
  194. // Always update on a name change
  195. return true;
  196. }
  197. if ("ancestor" == eName && event.getNewValue() != null) {
  198. // Only update on an ancestor change when getting a valid
  199. // parent and the LookAndFeel wants this.
  200. LookAndFeel laf = UIManager.getLookAndFeel();
  201. return (laf instanceof SynthLookAndFeel &&
  202. ((SynthLookAndFeel)laf).
  203. shouldUpdateStyleOnAncestorChanged());
  204. }
  205. return false;
  206. }
  207. /**
  208. * A convience method that will reset the Style of StyleContext if
  209. * necessary.
  210. *
  211. * @return newStyle
  212. */
  213. static SynthStyle updateStyle(SynthContext context, SynthUI ui) {
  214. SynthStyle newStyle = getStyle(context.getComponent(),
  215. context.getRegion());
  216. SynthStyle oldStyle = context.getStyle();
  217. if (newStyle != oldStyle) {
  218. if (oldStyle != null) {
  219. oldStyle.uninstallDefaults(context);
  220. }
  221. context.setStyle(newStyle);
  222. newStyle.installDefaults(context, ui);
  223. }
  224. return newStyle;
  225. }
  226. /**
  227. * Updates the style associated with <code>c</code>, and all its children.
  228. * This is a lighter version of
  229. * <code>SwingUtilities.updateComponentTreeUI</code>.
  230. *
  231. * @param c Component to update style for.
  232. */
  233. public static void updateStyles(Component c) {
  234. _updateStyles(c);
  235. c.repaint();
  236. }
  237. // Implementation for updateStyles
  238. private static void _updateStyles(Component c) {
  239. if (c instanceof JComponent) {
  240. // Yes, this is hacky. A better solution is to get the UI
  241. // and cast, but JComponent doesn't expose a getter for the UI
  242. // (each of the UIs do), making that approach impractical.
  243. String name = c.getName();
  244. c.setName(null);
  245. if (name != null) {
  246. c.setName(name);
  247. }
  248. ((JComponent)c).revalidate();
  249. }
  250. Component[] children = null;
  251. if (c instanceof JMenu) {
  252. children = ((JMenu)c).getMenuComponents();
  253. }
  254. else if (c instanceof Container) {
  255. children = ((Container)c).getComponents();
  256. }
  257. if (children != null) {
  258. for(int i = 0; i < children.length; i++) {
  259. updateStyles(children[i]);
  260. }
  261. }
  262. }
  263. /**
  264. * Returns the Region for the JComponent <code>c</code>.
  265. *
  266. * @param c JComponent to fetch the Region for
  267. * @return Region corresponding to <code>c</code>
  268. */
  269. public static Region getRegion(JComponent c) {
  270. return Region.getRegion(c);
  271. }
  272. /**
  273. * A convenience method to return where the foreground should be
  274. * painted for the Component identified by the passed in
  275. * AbstractSynthContext.
  276. */
  277. static Insets getPaintingInsets(SynthContext state, Insets insets) {
  278. if (state.isSubregion()) {
  279. insets = state.getStyle().getInsets(state, insets);
  280. }
  281. else {
  282. insets = state.getComponent().getInsets(insets);
  283. }
  284. return insets;
  285. }
  286. /**
  287. * A convenience method that handles painting of the background.
  288. * All SynthUI implementations should override update and invoke
  289. * this method.
  290. */
  291. static void update(SynthContext state, Graphics g) {
  292. paintRegion(state, g, null);
  293. }
  294. /**
  295. * A convenience method that handles painting of the background for
  296. * subregions. All SynthUI's that have subregions should invoke
  297. * this method, than paint the foreground.
  298. */
  299. static void updateSubregion(SynthContext state, Graphics g,
  300. Rectangle bounds) {
  301. paintRegion(state, g, bounds);
  302. }
  303. private static void paintRegion(SynthContext state, Graphics g,
  304. Rectangle bounds) {
  305. JComponent c = state.getComponent();
  306. SynthStyle style = state.getStyle();
  307. int x, y, width, height;
  308. if (bounds == null) {
  309. x = 0;
  310. y = 0;
  311. width = c.getWidth();
  312. height = c.getHeight();
  313. }
  314. else {
  315. x = bounds.x;
  316. y = bounds.y;
  317. width = bounds.width;
  318. height = bounds.height;
  319. }
  320. // Fill in the background, if necessary.
  321. boolean subregion = state.isSubregion();
  322. if ((subregion && style.isOpaque(state)) ||
  323. (!subregion && c.isOpaque())) {
  324. g.setColor(style.getColor(state, ColorType.BACKGROUND));
  325. g.fillRect(x, y, width, height);
  326. }
  327. }
  328. static boolean isLeftToRight(Component c) {
  329. return c.getComponentOrientation().isLeftToRight();
  330. }
  331. /**
  332. * Returns the ui that is of type <code>klass</code>, or null if
  333. * one can not be found.
  334. */
  335. static Object getUIOfType(ComponentUI ui, Class klass) {
  336. if (klass.isInstance(ui)) {
  337. return ui;
  338. }
  339. return null;
  340. }
  341. /**
  342. * Creates the Synth look and feel <code>ComponentUI</code> for
  343. * the passed in <code>JComponent</code>.
  344. *
  345. * @param c JComponent to create the <code>ComponentUI</code> for
  346. * @return ComponentUI to use for <code>c</code>
  347. */
  348. public static ComponentUI createUI(JComponent c) {
  349. String key = c.getUIClassID().intern();
  350. if (key == "ButtonUI") {
  351. return SynthButtonUI.createUI(c);
  352. }
  353. else if (key == "CheckBoxUI") {
  354. return SynthCheckBoxUI.createUI(c);
  355. }
  356. else if (key == "CheckBoxMenuItemUI") {
  357. return SynthCheckBoxMenuItemUI.createUI(c);
  358. }
  359. else if (key == "ColorChooserUI") {
  360. return SynthColorChooserUI.createUI(c);
  361. }
  362. else if (key == "ComboBoxUI") {
  363. return SynthComboBoxUI.createUI(c);
  364. }
  365. else if (key == "DesktopPaneUI") {
  366. return SynthDesktopPaneUI.createUI(c);
  367. }
  368. else if (key == "DesktopIconUI") {
  369. return SynthDesktopIconUI.createUI(c);
  370. }
  371. else if (key == "EditorPaneUI") {
  372. return SynthEditorPaneUI.createUI(c);
  373. }
  374. else if (key == "FileChooserUI") {
  375. return SynthFileChooserUI.createUI(c);
  376. }
  377. else if (key == "FormattedTextFieldUI") {
  378. return SynthFormattedTextFieldUI.createUI(c);
  379. }
  380. else if (key == "InternalFrameUI") {
  381. return SynthInternalFrameUI.createUI(c);
  382. }
  383. else if (key == "LabelUI") {
  384. return SynthLabelUI.createUI(c);
  385. }
  386. else if (key == "ListUI") {
  387. return SynthListUI.createUI(c);
  388. }
  389. else if (key == "MenuBarUI") {
  390. return SynthMenuBarUI.createUI(c);
  391. }
  392. else if (key == "MenuUI") {
  393. return SynthMenuUI.createUI(c);
  394. }
  395. else if (key == "MenuItemUI") {
  396. return SynthMenuItemUI.createUI(c);
  397. }
  398. else if (key == "OptionPaneUI") {
  399. return SynthOptionPaneUI.createUI(c);
  400. }
  401. else if (key == "PanelUI") {
  402. return SynthPanelUI.createUI(c);
  403. }
  404. else if (key == "PasswordFieldUI") {
  405. return SynthPasswordFieldUI.createUI(c);
  406. }
  407. else if (key == "PopupMenuSeparatorUI") {
  408. return SynthSeparatorUI.createUI(c);
  409. }
  410. else if (key == "PopupMenuUI") {
  411. return SynthPopupMenuUI.createUI(c);
  412. }
  413. else if (key == "ProgressBarUI") {
  414. return SynthProgressBarUI.createUI(c);
  415. }
  416. else if (key == "RadioButtonUI") {
  417. return SynthRadioButtonUI.createUI(c);
  418. }
  419. else if (key == "RadioButtonMenuItemUI") {
  420. return SynthRadioButtonMenuItemUI.createUI(c);
  421. }
  422. else if (key == "RootPaneUI") {
  423. return SynthRootPaneUI.createUI(c);
  424. }
  425. else if (key == "ScrollBarUI") {
  426. return SynthScrollBarUI.createUI(c);
  427. }
  428. else if (key == "ScrollPaneUI") {
  429. return SynthScrollPaneUI.createUI(c);
  430. }
  431. else if (key == "SeparatorUI") {
  432. return SynthSeparatorUI.createUI(c);
  433. }
  434. else if (key == "SliderUI") {
  435. return SynthSliderUI.createUI(c);
  436. }
  437. else if (key == "SpinnerUI") {
  438. return SynthSpinnerUI.createUI(c);
  439. }
  440. else if (key == "SplitPaneUI") {
  441. return SynthSplitPaneUI.createUI(c);
  442. }
  443. else if (key == "TabbedPaneUI") {
  444. return SynthTabbedPaneUI.createUI(c);
  445. }
  446. else if (key == "TableUI") {
  447. return SynthTableUI.createUI(c);
  448. }
  449. else if (key == "TableHeaderUI") {
  450. return SynthTableHeaderUI.createUI(c);
  451. }
  452. else if (key == "TextAreaUI") {
  453. return SynthTextAreaUI.createUI(c);
  454. }
  455. else if (key == "TextFieldUI") {
  456. return SynthTextFieldUI.createUI(c);
  457. }
  458. else if (key == "TextPaneUI") {
  459. return SynthTextPaneUI.createUI(c);
  460. }
  461. else if (key == "ToggleButtonUI") {
  462. return SynthToggleButtonUI.createUI(c);
  463. }
  464. else if (key == "ToolBarSeparatorUI") {
  465. return SynthSeparatorUI.createUI(c);
  466. }
  467. else if (key == "ToolBarUI") {
  468. return SynthToolBarUI.createUI(c);
  469. }
  470. else if (key == "ToolTipUI") {
  471. return SynthToolTipUI.createUI(c);
  472. }
  473. else if (key == "TreeUI") {
  474. return SynthTreeUI.createUI(c);
  475. }
  476. else if (key == "ViewportUI") {
  477. return SynthViewportUI.createUI(c);
  478. }
  479. return null;
  480. }
  481. /**
  482. * Creates a SynthLookAndFeel.
  483. * <p>
  484. * For the returned <code>SynthLookAndFeel</code> to be useful you need to
  485. * invoke <code>load</code> to specify the set of
  486. * <code>SynthStyle</code>s, or invoke <code>setStyleFactory</code>.
  487. *
  488. * @see #load
  489. * @see #setStyleFactory
  490. */
  491. public SynthLookAndFeel() {
  492. factory = new DefaultSynthStyleFactory();
  493. }
  494. /**
  495. * Loads the set of <code>SynthStyle</code>s that will be used by
  496. * this <code>SynthLookAndFeel</code>. <code>resourceBase</code> is
  497. * used to resolve any path based resources, for example an
  498. * <code>Image</code> would be resolved by
  499. * <code>resourceBase.getResource(path)</code>. Refer to
  500. * <a href="doc-files/synthFileFormat.html">Synth File Format</a>
  501. * for more information.
  502. *
  503. * @param input InputStream to load from
  504. * @param resourceBase Used to resolve any images or other resources
  505. * @throws ParseException If there is an error in parsing
  506. * @throws IllegalArgumentException if input or resourceBase is null
  507. */
  508. public void load(InputStream input, Class<?> resourceBase) throws
  509. ParseException, IllegalArgumentException {
  510. if (defaultsMap == null) {
  511. defaultsMap = new HashMap();
  512. }
  513. new SynthParser().parse(input, (DefaultSynthStyleFactory)factory,
  514. resourceBase, defaultsMap);
  515. }
  516. /**
  517. * Called by UIManager when this look and feel is installed.
  518. */
  519. public void initialize() {
  520. super.initialize();
  521. DefaultLookup.setDefaultLookup(new SynthDefaultLookup());
  522. setStyleFactory(factory);
  523. }
  524. /**
  525. * Called by UIManager when this look and feel is uninstalled.
  526. */
  527. public void uninitialize() {
  528. // We should uninstall the StyleFactory here, but unfortunately
  529. // there are a handful of things that retain references to the
  530. // LookAndFeel and expect things to work
  531. super.uninitialize();
  532. }
  533. /**
  534. * Returns the defaults for this SynthLookAndFeel.
  535. *
  536. * @return Defaults able.
  537. */
  538. public UIDefaults getDefaults() {
  539. UIDefaults table = new UIDefaults();
  540. Region.registerUIs(table);
  541. table.setDefaultLocale(Locale.getDefault());
  542. table.addResourceBundle(
  543. "com.sun.swing.internal.plaf.basic.resources.basic" );
  544. table.addResourceBundle("com.sun.swing.internal.plaf.synth.resources.synth");
  545. // These need to be defined for JColorChooser to work.
  546. table.put("ColorChooser.swatchesRecentSwatchSize",
  547. new Dimension(10, 10));
  548. table.put("ColorChooser.swatchesDefaultRecentColor", Color.RED);
  549. table.put("ColorChooser.swatchesSwatchSize", new Dimension(10, 10));
  550. // These are needed for PopupMenu.
  551. table.put("PopupMenu.selectedWindowInputMapBindings", new Object[] {
  552. "ESCAPE", "cancel",
  553. "DOWN", "selectNext",
  554. "KP_DOWN", "selectNext",
  555. "UP", "selectPrevious",
  556. "KP_UP", "selectPrevious",
  557. "LEFT", "selectParent",
  558. "KP_LEFT", "selectParent",
  559. "RIGHT", "selectChild",
  560. "KP_RIGHT", "selectChild",
  561. "ENTER", "return",
  562. "SPACE", "return"
  563. });
  564. table.put("PopupMenu.selectedWindowInputMapBindings.RightToLeft",
  565. new Object[] {
  566. "LEFT", "selectChild",
  567. "KP_LEFT", "selectChild",
  568. "RIGHT", "selectParent",
  569. "KP_RIGHT", "selectParent",
  570. });
  571. if (defaultsMap != null) {
  572. table.putAll(defaultsMap);
  573. }
  574. return table;
  575. }
  576. /**
  577. * Returns true, SynthLookAndFeel is always supported.
  578. *
  579. * @return true.
  580. */
  581. public boolean isSupportedLookAndFeel() {
  582. return true;
  583. }
  584. /**
  585. * Returns false, SynthLookAndFeel is not a native look and feel.
  586. *
  587. * @return true.
  588. */
  589. public boolean isNativeLookAndFeel() {
  590. return false;
  591. }
  592. /**
  593. * Returns a textual description of SynthLookAndFeel.
  594. *
  595. * @return textual description of synth.
  596. */
  597. public String getDescription() {
  598. return "Synth look and feel";
  599. }
  600. /**
  601. * Return a short string that identifies this look and feel.
  602. *
  603. * @return a short string identifying this look and feel.
  604. */
  605. public String getName() {
  606. return "Synth look and feel";
  607. }
  608. /**
  609. * Return a string that identifies this look and feel.
  610. *
  611. * @return a short string identifying this look and feel.
  612. */
  613. public String getID() {
  614. return "Synth";
  615. }
  616. /**
  617. * Returns whether or not the UIs should update their
  618. * <code>SynthStyles</code> from the <code>SynthStyleFactory</code>
  619. * when the ancestor of the <code>JComponent</code> changes. A subclass
  620. * that provided a <code>SynthStyleFactory</code> that based the
  621. * return value from <code>getStyle</code> off the containment hierarchy
  622. * would override this method to return true.
  623. *
  624. * @return whether or not the UIs should update their
  625. * <code>SynthStyles</code> from the <code>SynthStyleFactory</code>
  626. * when the ancestor changed.
  627. */
  628. public boolean shouldUpdateStyleOnAncestorChanged() {
  629. return false;
  630. }
  631. private void writeObject(java.io.ObjectOutputStream out)
  632. throws IOException {
  633. throw new NotSerializableException(this.getClass().getName());
  634. }
  635. }