1. /*
  2. * @(#)SynthSpinnerUI.java 1.11 03/12/19
  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 java.awt.*;
  9. import java.awt.event.*;
  10. import java.text.ParseException;
  11. import javax.swing.*;
  12. import javax.swing.event.*;
  13. import javax.swing.plaf.*;
  14. import javax.swing.plaf.basic.BasicSpinnerUI;
  15. import javax.swing.text.*;
  16. import java.beans.*;
  17. import java.text.*;
  18. import java.util.*;
  19. import sun.swing.plaf.synth.SynthUI;
  20. /**
  21. * Synth's SpinnerUI.
  22. *
  23. * @version 1.11, 12/19/03
  24. * @author Hans Muller
  25. * @author Joshua Outwater
  26. */
  27. class SynthSpinnerUI extends BasicSpinnerUI implements PropertyChangeListener,
  28. SynthUI {
  29. private SynthStyle style;
  30. /**
  31. * Returns a new instance of SynthSpinnerUI.
  32. *
  33. * @param c the JSpinner (not used)
  34. * @see ComponentUI#createUI
  35. * @return a new SynthSpinnerUI object
  36. */
  37. public static ComponentUI createUI(JComponent c) {
  38. return new SynthSpinnerUI();
  39. }
  40. protected void installListeners() {
  41. spinner.addPropertyChangeListener(this);
  42. }
  43. /**
  44. * Removes the <code>propertyChangeListener</code> added
  45. * by installListeners.
  46. * <p>
  47. * This method is called by <code>uninstallUI</code>.
  48. *
  49. * @see #installListeners
  50. */
  51. protected void uninstallListeners() {
  52. spinner.removePropertyChangeListener(this);
  53. }
  54. /**
  55. * Initialize the <code>JSpinner</code> <code>border</code>,
  56. * <code>foreground</code>, and <code>background</code>, properties
  57. * based on the corresponding "Spinner.*" properties from defaults table.
  58. * The <code>JSpinners</code> layout is set to the value returned by
  59. * <code>createLayout</code>. This method is called by <code>installUI</code>.
  60. *
  61. * @see #uninstallDefaults
  62. * @see #installUI
  63. * @see #createLayout
  64. * @see LookAndFeel#installBorder
  65. * @see LookAndFeel#installColors
  66. */
  67. protected void installDefaults() {
  68. LayoutManager layout = spinner.getLayout();
  69. if (layout == null || layout instanceof UIResource) {
  70. spinner.setLayout(createLayout());
  71. }
  72. updateStyle(spinner);
  73. }
  74. private void updateStyle(JSpinner c) {
  75. SynthContext context = getContext(c, ENABLED);
  76. SynthStyle oldStyle = style;
  77. style = SynthLookAndFeel.updateStyle(context, this);
  78. if (style != oldStyle) {
  79. if (oldStyle != null) {
  80. // Only call installKeyboardActions as uninstall is not
  81. // public.
  82. installKeyboardActions();
  83. }
  84. }
  85. context.dispose();
  86. }
  87. /**
  88. * Sets the <code>JSpinner's</code> layout manager to null. This
  89. * method is called by <code>uninstallUI</code>.
  90. *
  91. * @see #installDefaults
  92. * @see #uninstallUI
  93. */
  94. protected void uninstallDefaults() {
  95. if (spinner.getLayout() instanceof UIResource) {
  96. spinner.setLayout(null);
  97. }
  98. SynthContext context = getContext(spinner, ENABLED);
  99. style.uninstallDefaults(context);
  100. context.dispose();
  101. style = null;
  102. }
  103. protected LayoutManager createLayout() {
  104. return new SpinnerLayout();
  105. }
  106. // Not used since we overload install/uninstallListeners.
  107. protected PropertyChangeListener createPropertyChangeListener() {
  108. return this;
  109. }
  110. /**
  111. * Create a component that will replace the spinner models value
  112. * with the object returned by <code>spinner.getPreviousValue</code>.
  113. * By default the <code>previousButton</code> is a JButton
  114. * who's <code>ActionListener</code> updates it's <code>JSpinner</code>
  115. * ancestors model. If a previousButton isn't needed (in a subclass)
  116. * then override this method to return null.
  117. *
  118. * @return a component that will replace the spinners model with the
  119. * next value in the sequence, or null
  120. * @see #installUI
  121. * @see #createNextButton
  122. */
  123. protected Component createPreviousButton() {
  124. JButton b = new SynthArrowButton(SwingConstants.SOUTH);
  125. b.setName("Spinner.previousButton");
  126. installPreviousButtonListeners(b);
  127. return b;
  128. }
  129. /**
  130. * Create a component that will replace the spinner models value
  131. * with the object returned by <code>spinner.getNextValue</code>.
  132. * By default the <code>nextButton</code> is a JButton
  133. * who's <code>ActionListener</code> updates it's <code>JSpinner</code>
  134. * ancestors model. If a nextButton isn't needed (in a subclass)
  135. * then override this method to return null.
  136. *
  137. * @return a component that will replace the spinners model with the
  138. * next value in the sequence, or null
  139. * @see #installUI
  140. * @see #createPreviousButton
  141. */
  142. protected Component createNextButton() {
  143. JButton b = new SynthArrowButton(SwingConstants.NORTH);
  144. b.setName("Spinner.nextButton");
  145. installNextButtonListeners(b);
  146. return b;
  147. }
  148. /**
  149. * This method is called by installUI to get the editor component
  150. * of the <code>JSpinner</code>. By default it just returns
  151. * <code>JSpinner.getEditor()</code>. Subclasses can override
  152. * <code>createEditor</code> to return a component that contains
  153. * the spinner's editor or null, if they're going to handle adding
  154. * the editor to the <code>JSpinner</code> in an
  155. * <code>installUI</code> override.
  156. * <p>
  157. * Typically this method would be overridden to wrap the editor
  158. * with a container with a custom border, since one can't assume
  159. * that the editors border can be set directly.
  160. * <p>
  161. * The <code>replaceEditor</code> method is called when the spinners
  162. * editor is changed with <code>JSpinner.setEditor</code>. If you've
  163. * overriden this method, then you'll probably want to override
  164. * <code>replaceEditor</code> as well.
  165. *
  166. * @return the JSpinners editor JComponent, spinner.getEditor() by default
  167. * @see #installUI
  168. * @see #replaceEditor
  169. * @see JSpinner#getEditor
  170. */
  171. protected JComponent createEditor() {
  172. JComponent editor = spinner.getEditor();
  173. editor.setName("Spinner.editor");
  174. return editor;
  175. }
  176. /**
  177. * Called by the <code>PropertyChangeListener</code> when the
  178. * <code>JSpinner</code> editor property changes. It's the responsibility
  179. * of this method to remove the old editor and add the new one. By
  180. * default this operation is just:
  181. * <pre>
  182. * spinner.remove(oldEditor);
  183. * spinner.add(newEditor, "Editor");
  184. * </pre>
  185. * The implementation of <code>replaceEditor</code> should be coordinated
  186. * with the <code>createEditor</code> method.
  187. *
  188. * @see #createEditor
  189. * @see #createPropertyChangeListener
  190. */
  191. protected void replaceEditor(JComponent oldEditor, JComponent newEditor) {
  192. spinner.remove(oldEditor);
  193. spinner.add(newEditor, "Editor");
  194. }
  195. /**
  196. * Updates the enabled state of the children Components based on the
  197. * enabled state of the <code>JSpinner</code>.
  198. */
  199. private void updateEnabledState() {
  200. updateEnabledState(spinner, spinner.isEnabled());
  201. }
  202. /**
  203. * Recursively updates the enabled state of the child
  204. * <code>Component</code>s of <code>c</code>.
  205. */
  206. private void updateEnabledState(Container c, boolean enabled) {
  207. for (int counter = c.getComponentCount() - 1; counter >= 0;counter--) {
  208. Component child = c.getComponent(counter);
  209. child.setEnabled(enabled);
  210. if (child instanceof Container) {
  211. updateEnabledState((Container)child, enabled);
  212. }
  213. }
  214. }
  215. public SynthContext getContext(JComponent c) {
  216. return getContext(c, getComponentState(c));
  217. }
  218. private SynthContext getContext(JComponent c, int state) {
  219. return SynthContext.getContext(SynthContext.class, c,
  220. SynthLookAndFeel.getRegion(c), style, state);
  221. }
  222. private Region getRegion(JComponent c) {
  223. return SynthLookAndFeel.getRegion(c);
  224. }
  225. private int getComponentState(JComponent c) {
  226. return SynthLookAndFeel.getComponentState(c);
  227. }
  228. public void update(Graphics g, JComponent c) {
  229. SynthContext context = getContext(c);
  230. SynthLookAndFeel.update(context, g);
  231. context.getPainter().paintSpinnerBackground(context,
  232. g, 0, 0, c.getWidth(), c.getHeight());
  233. paint(context, g);
  234. context.dispose();
  235. }
  236. public void paint(Graphics g, JComponent c) {
  237. SynthContext context = getContext(c);
  238. paint(context, g);
  239. context.dispose();
  240. }
  241. protected void paint(SynthContext context, Graphics g) {
  242. }
  243. public void paintBorder(SynthContext context, Graphics g, int x,
  244. int y, int w, int h) {
  245. context.getPainter().paintSpinnerBorder(context, g, x, y, w, h);
  246. }
  247. /**
  248. * A simple layout manager for the editor and the next/previous buttons.
  249. * See the SynthSpinnerUI javadoc for more information about exactly
  250. * how the components are arranged.
  251. */
  252. private static class SpinnerLayout implements LayoutManager, UIResource
  253. {
  254. private Component nextButton = null;
  255. private Component previousButton = null;
  256. private Component editor = null;
  257. public void addLayoutComponent(String name, Component c) {
  258. if ("Next".equals(name)) {
  259. nextButton = c;
  260. }
  261. else if ("Previous".equals(name)) {
  262. previousButton = c;
  263. }
  264. else if ("Editor".equals(name)) {
  265. editor = c;
  266. }
  267. }
  268. public void removeLayoutComponent(Component c) {
  269. if (c == nextButton) {
  270. c = null;
  271. }
  272. else if (c == previousButton) {
  273. previousButton = null;
  274. }
  275. else if (c == editor) {
  276. editor = null;
  277. }
  278. }
  279. private Dimension preferredSize(Component c) {
  280. return (c == null) ? new Dimension(0, 0) : c.getPreferredSize();
  281. }
  282. public Dimension preferredLayoutSize(Container parent) {
  283. Dimension nextD = preferredSize(nextButton);
  284. Dimension previousD = preferredSize(previousButton);
  285. Dimension editorD = preferredSize(editor);
  286. /* Force the editors height to be a multiple of 2
  287. */
  288. editorD.height = ((editorD.height + 1) / 2) * 2;
  289. Dimension size = new Dimension(editorD.width, editorD.height);
  290. size.width += Math.max(nextD.width, previousD.width);
  291. Insets insets = parent.getInsets();
  292. size.width += insets.left + insets.right;
  293. size.height += insets.top + insets.bottom;
  294. return size;
  295. }
  296. public Dimension minimumLayoutSize(Container parent) {
  297. return preferredLayoutSize(parent);
  298. }
  299. private void setBounds(Component c, int x, int y, int width, int height) {
  300. if (c != null) {
  301. c.setBounds(x, y, width, height);
  302. }
  303. }
  304. public void layoutContainer(Container parent) {
  305. Insets insets = parent.getInsets();
  306. int availWidth = parent.getWidth() - (insets.left + insets.right);
  307. int availHeight = parent.getHeight() - (insets.top + insets.bottom);
  308. Dimension nextD = preferredSize(nextButton);
  309. Dimension previousD = preferredSize(previousButton);
  310. int nextHeight = availHeight / 2;
  311. int previousHeight = availHeight - nextHeight;
  312. int buttonsWidth = Math.max(nextD.width, previousD.width);
  313. int editorWidth = availWidth - buttonsWidth;
  314. /* Deal with the spinners componentOrientation property.
  315. */
  316. int editorX, buttonsX;
  317. if (parent.getComponentOrientation().isLeftToRight()) {
  318. editorX = insets.left;
  319. buttonsX = editorX + editorWidth;
  320. }
  321. else {
  322. buttonsX = insets.left;
  323. editorX = buttonsX + buttonsWidth;
  324. }
  325. int previousY = insets.top + nextHeight;
  326. setBounds(editor, editorX, insets.top, editorWidth, availHeight);
  327. setBounds(nextButton, buttonsX, insets.top, buttonsWidth, nextHeight);
  328. setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight);
  329. }
  330. }
  331. public void propertyChange(PropertyChangeEvent e) {
  332. String propertyName = e.getPropertyName();
  333. JSpinner spinner = (JSpinner)(e.getSource());
  334. SpinnerUI spinnerUI = spinner.getUI();
  335. if (spinnerUI instanceof SynthSpinnerUI) {
  336. SynthSpinnerUI ui = (SynthSpinnerUI)spinnerUI;
  337. if (SynthLookAndFeel.shouldUpdateStyle(e)) {
  338. ui.updateStyle(spinner);
  339. }
  340. if ("editor".equals(propertyName)) {
  341. JComponent oldEditor = (JComponent)e.getOldValue();
  342. JComponent newEditor = (JComponent)e.getNewValue();
  343. ui.replaceEditor(oldEditor, newEditor);
  344. ui.updateEnabledState();
  345. }
  346. else if ("enabled".equals(propertyName)) {
  347. ui.updateEnabledState();
  348. }
  349. }
  350. }
  351. }