| /* |
| * Copyright (c) 2002, 2006, 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(SynthContext.class, c, |
| SynthLookAndFeel.getRegion(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(); |
| } |
| } |
| } |