|
1 /* |
|
2 * Copyright (c) 2002, 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 javax.swing.plaf.synth; |
|
26 |
|
27 import java.awt.*; |
|
28 import java.awt.event.*; |
|
29 import javax.swing.*; |
|
30 import javax.swing.plaf.*; |
|
31 import javax.swing.plaf.basic.BasicSpinnerUI; |
|
32 import java.beans.*; |
|
33 |
|
34 /** |
|
35 * Provides the Synth L&F UI delegate for |
|
36 * {@link javax.swing.JSpinner}. |
|
37 * |
|
38 * @author Hans Muller |
|
39 * @author Joshua Outwater |
|
40 * @since 1.7 |
|
41 */ |
|
42 public class SynthSpinnerUI extends BasicSpinnerUI |
|
43 implements PropertyChangeListener, SynthUI { |
|
44 private SynthStyle style; |
|
45 /** |
|
46 * A FocusListener implementation which causes the entire spinner to be |
|
47 * repainted whenever the editor component (typically a text field) becomes |
|
48 * focused, or loses focus. This is necessary because since SynthSpinnerUI |
|
49 * is composed of an editor and two buttons, it is necessary that all three |
|
50 * components indicate that they are "focused" so that they can be drawn |
|
51 * appropriately. The repaint is used to ensure that the buttons are drawn |
|
52 * in the new focused or unfocused state, mirroring that of the editor. |
|
53 */ |
|
54 private EditorFocusHandler editorFocusHandler = new EditorFocusHandler(); |
|
55 |
|
56 /** |
|
57 * Returns a new instance of SynthSpinnerUI. |
|
58 * |
|
59 * @param c the JSpinner (not used) |
|
60 * @see ComponentUI#createUI |
|
61 * @return a new SynthSpinnerUI object |
|
62 */ |
|
63 public static ComponentUI createUI(JComponent c) { |
|
64 return new SynthSpinnerUI(); |
|
65 } |
|
66 |
|
67 /** |
|
68 * {@inheritDoc} |
|
69 */ |
|
70 @Override |
|
71 protected void installListeners() { |
|
72 super.installListeners(); |
|
73 spinner.addPropertyChangeListener(this); |
|
74 JComponent editor = spinner.getEditor(); |
|
75 if (editor instanceof JSpinner.DefaultEditor) { |
|
76 JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField(); |
|
77 if (tf != null) { |
|
78 tf.addFocusListener(editorFocusHandler); |
|
79 } |
|
80 } |
|
81 } |
|
82 |
|
83 /** |
|
84 * {@inheritDoc} |
|
85 */ |
|
86 @Override |
|
87 protected void uninstallListeners() { |
|
88 super.uninstallListeners(); |
|
89 spinner.removePropertyChangeListener(this); |
|
90 JComponent editor = spinner.getEditor(); |
|
91 if (editor instanceof JSpinner.DefaultEditor) { |
|
92 JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField(); |
|
93 if (tf != null) { |
|
94 tf.removeFocusListener(editorFocusHandler); |
|
95 } |
|
96 } |
|
97 } |
|
98 |
|
99 /** |
|
100 * Initializes the <code>JSpinner</code> <code>border</code>, |
|
101 * <code>foreground</code>, and <code>background</code>, properties |
|
102 * based on the corresponding "Spinner.*" properties from defaults table. |
|
103 * The <code>JSpinners</code> layout is set to the value returned by |
|
104 * <code>createLayout</code>. This method is called by <code>installUI</code>. |
|
105 * |
|
106 * @see #uninstallDefaults |
|
107 * @see #installUI |
|
108 * @see #createLayout |
|
109 * @see LookAndFeel#installBorder |
|
110 * @see LookAndFeel#installColors |
|
111 */ |
|
112 @Override |
|
113 protected void installDefaults() { |
|
114 LayoutManager layout = spinner.getLayout(); |
|
115 |
|
116 if (layout == null || layout instanceof UIResource) { |
|
117 spinner.setLayout(createLayout()); |
|
118 } |
|
119 updateStyle(spinner); |
|
120 } |
|
121 |
|
122 |
|
123 private void updateStyle(JSpinner c) { |
|
124 SynthContext context = getContext(c, ENABLED); |
|
125 SynthStyle oldStyle = style; |
|
126 style = SynthLookAndFeel.updateStyle(context, this); |
|
127 if (style != oldStyle) { |
|
128 if (oldStyle != null) { |
|
129 // Only call installKeyboardActions as uninstall is not |
|
130 // public. |
|
131 installKeyboardActions(); |
|
132 } |
|
133 } |
|
134 context.dispose(); |
|
135 } |
|
136 |
|
137 |
|
138 /** |
|
139 * Sets the <code>JSpinner's</code> layout manager to null. This |
|
140 * method is called by <code>uninstallUI</code>. |
|
141 * |
|
142 * @see #installDefaults |
|
143 * @see #uninstallUI |
|
144 */ |
|
145 @Override |
|
146 protected void uninstallDefaults() { |
|
147 if (spinner.getLayout() instanceof UIResource) { |
|
148 spinner.setLayout(null); |
|
149 } |
|
150 |
|
151 SynthContext context = getContext(spinner, ENABLED); |
|
152 |
|
153 style.uninstallDefaults(context); |
|
154 context.dispose(); |
|
155 style = null; |
|
156 } |
|
157 |
|
158 /** |
|
159 * {@inheritDoc} |
|
160 */ |
|
161 @Override |
|
162 protected LayoutManager createLayout() { |
|
163 return new SpinnerLayout(); |
|
164 } |
|
165 |
|
166 |
|
167 /** |
|
168 * {@inheritDoc} |
|
169 */ |
|
170 @Override |
|
171 protected Component createPreviousButton() { |
|
172 JButton b = new SynthArrowButton(SwingConstants.SOUTH); |
|
173 b.setName("Spinner.previousButton"); |
|
174 installPreviousButtonListeners(b); |
|
175 return b; |
|
176 } |
|
177 |
|
178 |
|
179 /** |
|
180 * {@inheritDoc} |
|
181 */ |
|
182 @Override |
|
183 protected Component createNextButton() { |
|
184 JButton b = new SynthArrowButton(SwingConstants.NORTH); |
|
185 b.setName("Spinner.nextButton"); |
|
186 installNextButtonListeners(b); |
|
187 return b; |
|
188 } |
|
189 |
|
190 |
|
191 /** |
|
192 * This method is called by installUI to get the editor component |
|
193 * of the <code>JSpinner</code>. By default it just returns |
|
194 * <code>JSpinner.getEditor()</code>. Subclasses can override |
|
195 * <code>createEditor</code> to return a component that contains |
|
196 * the spinner's editor or null, if they're going to handle adding |
|
197 * the editor to the <code>JSpinner</code> in an |
|
198 * <code>installUI</code> override. |
|
199 * <p> |
|
200 * Typically this method would be overridden to wrap the editor |
|
201 * with a container with a custom border, since one can't assume |
|
202 * that the editors border can be set directly. |
|
203 * <p> |
|
204 * The <code>replaceEditor</code> method is called when the spinners |
|
205 * editor is changed with <code>JSpinner.setEditor</code>. If you've |
|
206 * overriden this method, then you'll probably want to override |
|
207 * <code>replaceEditor</code> as well. |
|
208 * |
|
209 * @return the JSpinners editor JComponent, spinner.getEditor() by default |
|
210 * @see #installUI |
|
211 * @see #replaceEditor |
|
212 * @see JSpinner#getEditor |
|
213 */ |
|
214 @Override |
|
215 protected JComponent createEditor() { |
|
216 JComponent editor = spinner.getEditor(); |
|
217 editor.setName("Spinner.editor"); |
|
218 updateEditorAlignment(editor); |
|
219 return editor; |
|
220 } |
|
221 |
|
222 |
|
223 /** |
|
224 * Called by the <code>PropertyChangeListener</code> when the |
|
225 * <code>JSpinner</code> editor property changes. It's the responsibility |
|
226 * of this method to remove the old editor and add the new one. By |
|
227 * default this operation is just: |
|
228 * <pre> |
|
229 * spinner.remove(oldEditor); |
|
230 * spinner.add(newEditor, "Editor"); |
|
231 * </pre> |
|
232 * The implementation of <code>replaceEditor</code> should be coordinated |
|
233 * with the <code>createEditor</code> method. |
|
234 * |
|
235 * @see #createEditor |
|
236 * @see #createPropertyChangeListener |
|
237 */ |
|
238 @Override |
|
239 protected void replaceEditor(JComponent oldEditor, JComponent newEditor) { |
|
240 spinner.remove(oldEditor); |
|
241 spinner.add(newEditor, "Editor"); |
|
242 if (oldEditor instanceof JSpinner.DefaultEditor) { |
|
243 JTextField tf = ((JSpinner.DefaultEditor)oldEditor).getTextField(); |
|
244 if (tf != null) { |
|
245 tf.removeFocusListener(editorFocusHandler); |
|
246 } |
|
247 } |
|
248 if (newEditor instanceof JSpinner.DefaultEditor) { |
|
249 JTextField tf = ((JSpinner.DefaultEditor)newEditor).getTextField(); |
|
250 if (tf != null) { |
|
251 tf.addFocusListener(editorFocusHandler); |
|
252 } |
|
253 } |
|
254 } |
|
255 |
|
256 private void updateEditorAlignment(JComponent editor) { |
|
257 if (editor instanceof JSpinner.DefaultEditor) { |
|
258 SynthContext context = getContext(spinner); |
|
259 Integer alignment = (Integer)context.getStyle().get( |
|
260 context, "Spinner.editorAlignment"); |
|
261 JTextField text = ((JSpinner.DefaultEditor)editor).getTextField(); |
|
262 if (alignment != null) { |
|
263 text.setHorizontalAlignment(alignment); |
|
264 |
|
265 } |
|
266 // copy across the sizeVariant property to the editor |
|
267 text.putClientProperty("JComponent.sizeVariant", |
|
268 spinner.getClientProperty("JComponent.sizeVariant")); |
|
269 } |
|
270 } |
|
271 |
|
272 /** |
|
273 * {@inheritDoc} |
|
274 */ |
|
275 @Override |
|
276 public SynthContext getContext(JComponent c) { |
|
277 return getContext(c, SynthLookAndFeel.getComponentState(c)); |
|
278 } |
|
279 |
|
280 private SynthContext getContext(JComponent c, int state) { |
|
281 return SynthContext.getContext(c, style, state); |
|
282 } |
|
283 |
|
284 /** |
|
285 * Notifies this UI delegate to repaint the specified component. |
|
286 * This method paints the component background, then calls |
|
287 * the {@link #paint(SynthContext,Graphics)} method. |
|
288 * |
|
289 * <p>In general, this method does not need to be overridden by subclasses. |
|
290 * All Look and Feel rendering code should reside in the {@code paint} method. |
|
291 * |
|
292 * @param g the {@code Graphics} object used for painting |
|
293 * @param c the component being painted |
|
294 * @see #paint(SynthContext,Graphics) |
|
295 */ |
|
296 @Override |
|
297 public void update(Graphics g, JComponent c) { |
|
298 SynthContext context = getContext(c); |
|
299 |
|
300 SynthLookAndFeel.update(context, g); |
|
301 context.getPainter().paintSpinnerBackground(context, |
|
302 g, 0, 0, c.getWidth(), c.getHeight()); |
|
303 paint(context, g); |
|
304 context.dispose(); |
|
305 } |
|
306 |
|
307 |
|
308 /** |
|
309 * Paints the specified component according to the Look and Feel. |
|
310 * <p>This method is not used by Synth Look and Feel. |
|
311 * Painting is handled by the {@link #paint(SynthContext,Graphics)} method. |
|
312 * |
|
313 * @param g the {@code Graphics} object used for painting |
|
314 * @param c the component being painted |
|
315 * @see #paint(SynthContext,Graphics) |
|
316 */ |
|
317 @Override |
|
318 public void paint(Graphics g, JComponent c) { |
|
319 SynthContext context = getContext(c); |
|
320 |
|
321 paint(context, g); |
|
322 context.dispose(); |
|
323 } |
|
324 |
|
325 /** |
|
326 * Paints the specified component. This implementation does nothing. |
|
327 * |
|
328 * @param context context for the component being painted |
|
329 * @param g the {@code Graphics} object used for painting |
|
330 * @see #update(Graphics,JComponent) |
|
331 */ |
|
332 protected void paint(SynthContext context, Graphics g) { |
|
333 } |
|
334 |
|
335 /** |
|
336 * {@inheritDoc} |
|
337 */ |
|
338 @Override |
|
339 public void paintBorder(SynthContext context, Graphics g, int x, |
|
340 int y, int w, int h) { |
|
341 context.getPainter().paintSpinnerBorder(context, g, x, y, w, h); |
|
342 } |
|
343 |
|
344 /** |
|
345 * A simple layout manager for the editor and the next/previous buttons. |
|
346 * See the SynthSpinnerUI javadoc for more information about exactly |
|
347 * how the components are arranged. |
|
348 */ |
|
349 private static class SpinnerLayout implements LayoutManager, UIResource |
|
350 { |
|
351 private Component nextButton = null; |
|
352 private Component previousButton = null; |
|
353 private Component editor = null; |
|
354 |
|
355 public void addLayoutComponent(String name, Component c) { |
|
356 if ("Next".equals(name)) { |
|
357 nextButton = c; |
|
358 } |
|
359 else if ("Previous".equals(name)) { |
|
360 previousButton = c; |
|
361 } |
|
362 else if ("Editor".equals(name)) { |
|
363 editor = c; |
|
364 } |
|
365 } |
|
366 |
|
367 public void removeLayoutComponent(Component c) { |
|
368 if (c == nextButton) { |
|
369 nextButton = null; |
|
370 } |
|
371 else if (c == previousButton) { |
|
372 previousButton = null; |
|
373 } |
|
374 else if (c == editor) { |
|
375 editor = null; |
|
376 } |
|
377 } |
|
378 |
|
379 private Dimension preferredSize(Component c) { |
|
380 return (c == null) ? new Dimension(0, 0) : c.getPreferredSize(); |
|
381 } |
|
382 |
|
383 public Dimension preferredLayoutSize(Container parent) { |
|
384 Dimension nextD = preferredSize(nextButton); |
|
385 Dimension previousD = preferredSize(previousButton); |
|
386 Dimension editorD = preferredSize(editor); |
|
387 |
|
388 /* Force the editors height to be a multiple of 2 |
|
389 */ |
|
390 editorD.height = ((editorD.height + 1) / 2) * 2; |
|
391 |
|
392 Dimension size = new Dimension(editorD.width, editorD.height); |
|
393 size.width += Math.max(nextD.width, previousD.width); |
|
394 Insets insets = parent.getInsets(); |
|
395 size.width += insets.left + insets.right; |
|
396 size.height += insets.top + insets.bottom; |
|
397 return size; |
|
398 } |
|
399 |
|
400 public Dimension minimumLayoutSize(Container parent) { |
|
401 return preferredLayoutSize(parent); |
|
402 } |
|
403 |
|
404 private void setBounds(Component c, int x, int y, int width, int height) { |
|
405 if (c != null) { |
|
406 c.setBounds(x, y, width, height); |
|
407 } |
|
408 } |
|
409 |
|
410 public void layoutContainer(Container parent) { |
|
411 Insets insets = parent.getInsets(); |
|
412 int availWidth = parent.getWidth() - (insets.left + insets.right); |
|
413 int availHeight = parent.getHeight() - (insets.top + insets.bottom); |
|
414 Dimension nextD = preferredSize(nextButton); |
|
415 Dimension previousD = preferredSize(previousButton); |
|
416 int nextHeight = availHeight / 2; |
|
417 int previousHeight = availHeight - nextHeight; |
|
418 int buttonsWidth = Math.max(nextD.width, previousD.width); |
|
419 int editorWidth = availWidth - buttonsWidth; |
|
420 |
|
421 /* Deal with the spinners componentOrientation property. |
|
422 */ |
|
423 int editorX, buttonsX; |
|
424 if (parent.getComponentOrientation().isLeftToRight()) { |
|
425 editorX = insets.left; |
|
426 buttonsX = editorX + editorWidth; |
|
427 } |
|
428 else { |
|
429 buttonsX = insets.left; |
|
430 editorX = buttonsX + buttonsWidth; |
|
431 } |
|
432 |
|
433 int previousY = insets.top + nextHeight; |
|
434 setBounds(editor, editorX, insets.top, editorWidth, availHeight); |
|
435 setBounds(nextButton, buttonsX, insets.top, buttonsWidth, nextHeight); |
|
436 setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight); |
|
437 } |
|
438 } |
|
439 |
|
440 /** |
|
441 * {@inheritDoc} |
|
442 */ |
|
443 @Override |
|
444 public void propertyChange(PropertyChangeEvent e) { |
|
445 JSpinner spinner = (JSpinner)(e.getSource()); |
|
446 SpinnerUI spinnerUI = spinner.getUI(); |
|
447 |
|
448 if (spinnerUI instanceof SynthSpinnerUI) { |
|
449 SynthSpinnerUI ui = (SynthSpinnerUI)spinnerUI; |
|
450 |
|
451 if (SynthLookAndFeel.shouldUpdateStyle(e)) { |
|
452 ui.updateStyle(spinner); |
|
453 } |
|
454 } |
|
455 } |
|
456 |
|
457 /** Listen to editor text field focus changes and repaint whole spinner */ |
|
458 private class EditorFocusHandler implements FocusListener{ |
|
459 /** Invoked when a editor text field gains the keyboard focus. */ |
|
460 @Override public void focusGained(FocusEvent e) { |
|
461 spinner.repaint(); |
|
462 } |
|
463 |
|
464 /** Invoked when a editor text field loses the keyboard focus. */ |
|
465 @Override public void focusLost(FocusEvent e) { |
|
466 spinner.repaint(); |
|
467 } |
|
468 } |
|
469 } |