/* |
|
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
package javax.swing.plaf.synth; |
|
import java.awt.*; |
|
import java.awt.event.*; |
|
import javax.swing.*; |
|
import javax.swing.plaf.*; |
|
import javax.swing.plaf.basic.BasicSpinnerUI; |
|
import java.beans.*; |
|
/** |
|
* Provides the Synth L&F UI delegate for |
|
* {@link javax.swing.JSpinner}. |
|
* |
|
* @author Hans Muller |
|
* @author Joshua Outwater |
|
* @since 1.7 |
|
*/ |
|
public class SynthSpinnerUI extends BasicSpinnerUI |
|
implements PropertyChangeListener, SynthUI { |
|
private SynthStyle style; |
|
/** |
|
* A FocusListener implementation which causes the entire spinner to be |
|
* repainted whenever the editor component (typically a text field) becomes |
|
* focused, or loses focus. This is necessary because since SynthSpinnerUI |
|
* is composed of an editor and two buttons, it is necessary that all three |
|
* components indicate that they are "focused" so that they can be drawn |
|
* appropriately. The repaint is used to ensure that the buttons are drawn |
|
* in the new focused or unfocused state, mirroring that of the editor. |
|
*/ |
|
private EditorFocusHandler editorFocusHandler = new EditorFocusHandler(); |
|
/** |
|
* Returns a new instance of SynthSpinnerUI. |
|
* |
|
* @param c the JSpinner (not used) |
|
* @see ComponentUI#createUI |
|
* @return a new SynthSpinnerUI object |
|
*/ |
|
public static ComponentUI createUI(JComponent c) { |
|
return new SynthSpinnerUI(); |
|
} |
|
/** |
|
* {@inheritDoc} |
|
*/ |
|
@Override |
|
protected void installListeners() { |
|
super.installListeners(); |
|
spinner.addPropertyChangeListener(this); |
|
JComponent editor = spinner.getEditor(); |
|
if (editor instanceof JSpinner.DefaultEditor) { |
|
JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField(); |
|
if (tf != null) { |
|
tf.addFocusListener(editorFocusHandler); |
|
} |
|
} |
|
} |
|
/** |
|
* {@inheritDoc} |
|
*/ |
|
@Override |
|
protected void uninstallListeners() { |
|
super.uninstallListeners(); |
|
spinner.removePropertyChangeListener(this); |
|
JComponent editor = spinner.getEditor(); |
|
if (editor instanceof JSpinner.DefaultEditor) { |
|
JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField(); |
|
if (tf != null) { |
|
tf.removeFocusListener(editorFocusHandler); |
|
} |
|
} |
|
} |
|
/** |
|
* Initializes the <code>JSpinner</code> <code>border</code>, |
|
* <code>foreground</code>, and <code>background</code>, properties |
|
* based on the corresponding "Spinner.*" properties from defaults table. |
|
* The <code>JSpinners</code> layout is set to the value returned by |
|
* <code>createLayout</code>. This method is called by <code>installUI</code>. |
|
* |
|
* @see #uninstallDefaults |
|
* @see #installUI |
|
* @see #createLayout |
|
* @see LookAndFeel#installBorder |
|
* @see LookAndFeel#installColors |
|
*/ |
|
@Override |
|
protected void installDefaults() { |
|
LayoutManager layout = spinner.getLayout(); |
|
if (layout == null || layout instanceof UIResource) { |
|
spinner.setLayout(createLayout()); |
|
} |
|
updateStyle(spinner); |
|
} |
|
private void updateStyle(JSpinner c) { |
|
SynthContext context = getContext(c, ENABLED); |
|
SynthStyle oldStyle = style; |
|
style = SynthLookAndFeel.updateStyle(context, this); |
|
if (style != oldStyle) { |
|
if (oldStyle != null) { |
|
// Only call installKeyboardActions as uninstall is not |
|
// public. |
|
installKeyboardActions(); |
|
} |
|
} |
|
context.dispose(); |
|
} |
|
/** |
|
* Sets the <code>JSpinner's</code> layout manager to null. This |
|
* method is called by <code>uninstallUI</code>. |
|
* |
|
* @see #installDefaults |
|
* @see #uninstallUI |
|
*/ |
|
@Override |
|
protected void uninstallDefaults() { |
|
if (spinner.getLayout() instanceof UIResource) { |
|
spinner.setLayout(null); |
|
} |
|
SynthContext context = getContext(spinner, ENABLED); |
|
style.uninstallDefaults(context); |
|
context.dispose(); |
|
style = null; |
|
} |
|
/** |
|
* {@inheritDoc} |
|
*/ |
|
@Override |
|
protected LayoutManager createLayout() { |
|
return new SpinnerLayout(); |
|
} |
|
/** |
|
* {@inheritDoc} |
|
*/ |
|
@Override |
|
protected Component createPreviousButton() { |
|
JButton b = new SynthArrowButton(SwingConstants.SOUTH); |
|
b.setName("Spinner.previousButton"); |
|
installPreviousButtonListeners(b); |
|
return b; |
|
} |
|
/** |
|
* {@inheritDoc} |
|
*/ |
|
@Override |
|
protected Component createNextButton() { |
|
JButton b = new SynthArrowButton(SwingConstants.NORTH); |
|
b.setName("Spinner.nextButton"); |
|
installNextButtonListeners(b); |
|
return b; |
|
} |
|
/** |
|
* This method is called by installUI to get the editor component |
|
* of the <code>JSpinner</code>. By default it just returns |
|
* <code>JSpinner.getEditor()</code>. Subclasses can override |
|
* <code>createEditor</code> to return a component that contains |
|
* the spinner's editor or null, if they're going to handle adding |
|
* the editor to the <code>JSpinner</code> in an |
|
* <code>installUI</code> override. |
|
* <p> |
|
* Typically this method would be overridden to wrap the editor |
|
* with a container with a custom border, since one can't assume |
|
* that the editors border can be set directly. |
|
* <p> |
|
* The <code>replaceEditor</code> method is called when the spinners |
|
* editor is changed with <code>JSpinner.setEditor</code>. If you've |
|
* overriden this method, then you'll probably want to override |
|
* <code>replaceEditor</code> as well. |
|
* |
|
* @return the JSpinners editor JComponent, spinner.getEditor() by default |
|
* @see #installUI |
|
* @see #replaceEditor |
|
* @see JSpinner#getEditor |
|
*/ |
|
@Override |
|
protected JComponent createEditor() { |
|
JComponent editor = spinner.getEditor(); |
|
editor.setName("Spinner.editor"); |
|
updateEditorAlignment(editor); |
|
return editor; |
|
} |
|
/** |
|
* Called by the <code>PropertyChangeListener</code> when the |
|
* <code>JSpinner</code> editor property changes. It's the responsibility |
|
* of this method to remove the old editor and add the new one. By |
|
* default this operation is just: |
|
* <pre> |
|
* spinner.remove(oldEditor); |
|
* spinner.add(newEditor, "Editor"); |
|
* </pre> |
|
* The implementation of <code>replaceEditor</code> should be coordinated |
|
* with the <code>createEditor</code> method. |
|
* |
|
* @see #createEditor |
|
* @see #createPropertyChangeListener |
|
*/ |
|
@Override |
|
protected void replaceEditor(JComponent oldEditor, JComponent newEditor) { |
|
spinner.remove(oldEditor); |
|
spinner.add(newEditor, "Editor"); |
|
if (oldEditor instanceof JSpinner.DefaultEditor) { |
|
JTextField tf = ((JSpinner.DefaultEditor)oldEditor).getTextField(); |
|
if (tf != null) { |
|
tf.removeFocusListener(editorFocusHandler); |
|
} |
|
} |
|
if (newEditor instanceof JSpinner.DefaultEditor) { |
|
JTextField tf = ((JSpinner.DefaultEditor)newEditor).getTextField(); |
|
if (tf != null) { |
|
tf.addFocusListener(editorFocusHandler); |
|
} |
|
} |
|
} |
|
private void updateEditorAlignment(JComponent editor) { |
|
if (editor instanceof JSpinner.DefaultEditor) { |
|
SynthContext context = getContext(spinner); |
|
Integer alignment = (Integer)context.getStyle().get( |
|
context, "Spinner.editorAlignment"); |
|
JTextField text = ((JSpinner.DefaultEditor)editor).getTextField(); |
|
if (alignment != null) { |
|
text.setHorizontalAlignment(alignment); |
|
} |
|
// copy across the sizeVariant property to the editor |
|
text.putClientProperty("JComponent.sizeVariant", |
|
spinner.getClientProperty("JComponent.sizeVariant")); |
|
} |
|
} |
|
/** |
|
* {@inheritDoc} |
|
*/ |
|
@Override |
|
public SynthContext getContext(JComponent c) { |
|
return getContext(c, SynthLookAndFeel.getComponentState(c)); |
|
} |
|
private SynthContext getContext(JComponent c, int state) { |
|
return SynthContext.getContext(c, style, state); |
|
} |
|
/** |
|
* Notifies this UI delegate to repaint the specified component. |
|
* This method paints the component background, then calls |
|
* the {@link #paint(SynthContext,Graphics)} method. |
|
* |
|
* <p>In general, this method does not need to be overridden by subclasses. |
|
* All Look and Feel rendering code should reside in the {@code paint} method. |
|
* |
|
* @param g the {@code Graphics} object used for painting |
|
* @param c the component being painted |
|
* @see #paint(SynthContext,Graphics) |
|
*/ |
|
@Override |
|
public void update(Graphics g, JComponent c) { |
|
SynthContext context = getContext(c); |
|
SynthLookAndFeel.update(context, g); |
|
context.getPainter().paintSpinnerBackground(context, |
|
g, 0, 0, c.getWidth(), c.getHeight()); |
|
paint(context, g); |
|
context.dispose(); |
|
} |
|
/** |
|
* Paints the specified component according to the Look and Feel. |
|
* <p>This method is not used by Synth Look and Feel. |
|
* Painting is handled by the {@link #paint(SynthContext,Graphics)} method. |
|
* |
|
* @param g the {@code Graphics} object used for painting |
|
* @param c the component being painted |
|
* @see #paint(SynthContext,Graphics) |
|
*/ |
|
@Override |
|
public void paint(Graphics g, JComponent c) { |
|
SynthContext context = getContext(c); |
|
paint(context, g); |
|
context.dispose(); |
|
} |
|
/** |
|
* Paints the specified component. This implementation does nothing. |
|
* |
|
* @param context context for the component being painted |
|
* @param g the {@code Graphics} object used for painting |
|
* @see #update(Graphics,JComponent) |
|
*/ |
|
protected void paint(SynthContext context, Graphics g) { |
|
} |
|
/** |
|
* {@inheritDoc} |
|
*/ |
|
@Override |
|
public void paintBorder(SynthContext context, Graphics g, int x, |
|
int y, int w, int h) { |
|
context.getPainter().paintSpinnerBorder(context, g, x, y, w, h); |
|
} |
|
/** |
|
* A simple layout manager for the editor and the next/previous buttons. |
|
* See the SynthSpinnerUI javadoc for more information about exactly |
|
* how the components are arranged. |
|
*/ |
|
private static class SpinnerLayout implements LayoutManager, UIResource |
|
{ |
|
private Component nextButton = null; |
|
private Component previousButton = null; |
|
private Component editor = null; |
|
public void addLayoutComponent(String name, Component c) { |
|
if ("Next".equals(name)) { |
|
nextButton = c; |
|
} |
|
else if ("Previous".equals(name)) { |
|
previousButton = c; |
|
} |
|
else if ("Editor".equals(name)) { |
|
editor = c; |
|
} |
|
} |
|
public void removeLayoutComponent(Component c) { |
|
if (c == nextButton) { |
|
nextButton = null; |
|
} |
|
else if (c == previousButton) { |
|
previousButton = null; |
|
} |
|
else if (c == editor) { |
|
editor = null; |
|
} |
|
} |
|
private Dimension preferredSize(Component c) { |
|
return (c == null) ? new Dimension(0, 0) : c.getPreferredSize(); |
|
} |
|
public Dimension preferredLayoutSize(Container parent) { |
|
Dimension nextD = preferredSize(nextButton); |
|
Dimension previousD = preferredSize(previousButton); |
|
Dimension editorD = preferredSize(editor); |
|
/* Force the editors height to be a multiple of 2 |
|
*/ |
|
editorD.height = ((editorD.height + 1) / 2) * 2; |
|
Dimension size = new Dimension(editorD.width, editorD.height); |
|
size.width += Math.max(nextD.width, previousD.width); |
|
Insets insets = parent.getInsets(); |
|
size.width += insets.left + insets.right; |
|
size.height += insets.top + insets.bottom; |
|
return size; |
|
} |
|
public Dimension minimumLayoutSize(Container parent) { |
|
return preferredLayoutSize(parent); |
|
} |
|
private void setBounds(Component c, int x, int y, int width, int height) { |
|
if (c != null) { |
|
c.setBounds(x, y, width, height); |
|
} |
|
} |
|
public void layoutContainer(Container parent) { |
|
Insets insets = parent.getInsets(); |
|
int availWidth = parent.getWidth() - (insets.left + insets.right); |
|
int availHeight = parent.getHeight() - (insets.top + insets.bottom); |
|
Dimension nextD = preferredSize(nextButton); |
|
Dimension previousD = preferredSize(previousButton); |
|
int nextHeight = availHeight / 2; |
|
int previousHeight = availHeight - nextHeight; |
|
int buttonsWidth = Math.max(nextD.width, previousD.width); |
|
int editorWidth = availWidth - buttonsWidth; |
|
/* Deal with the spinners componentOrientation property. |
|
*/ |
|
int editorX, buttonsX; |
|
if (parent.getComponentOrientation().isLeftToRight()) { |
|
editorX = insets.left; |
|
buttonsX = editorX + editorWidth; |
|
} |
|
else { |
|
buttonsX = insets.left; |
|
editorX = buttonsX + buttonsWidth; |
|
} |
|
int previousY = insets.top + nextHeight; |
|
setBounds(editor, editorX, insets.top, editorWidth, availHeight); |
|
setBounds(nextButton, buttonsX, insets.top, buttonsWidth, nextHeight); |
|
setBounds(previousButton, buttonsX, previousY, buttonsWidth, previousHeight); |
|
} |
|
} |
|
/** |
|
* {@inheritDoc} |
|
*/ |
|
@Override |
|
public void propertyChange(PropertyChangeEvent e) { |
|
JSpinner spinner = (JSpinner)(e.getSource()); |
|
SpinnerUI spinnerUI = spinner.getUI(); |
|
if (spinnerUI instanceof SynthSpinnerUI) { |
|
SynthSpinnerUI ui = (SynthSpinnerUI)spinnerUI; |
|
if (SynthLookAndFeel.shouldUpdateStyle(e)) { |
|
ui.updateStyle(spinner); |
|
} |
|
} |
|
} |
|
/** Listen to editor text field focus changes and repaint whole spinner */ |
|
private class EditorFocusHandler implements FocusListener{ |
|
/** Invoked when a editor text field gains the keyboard focus. */ |
|
@Override public void focusGained(FocusEvent e) { |
|
spinner.repaint(); |
|
} |
|
/** Invoked when a editor text field loses the keyboard focus. */ |
|
@Override public void focusLost(FocusEvent e) { |
|
spinner.repaint(); |
|
} |
|
} |
|
} |