diff -r 836adbf7a2cd -r 3317bb8137f4 jdk/src/java.desktop/share/classes/javax/swing/plaf/synth/SynthSpinnerUI.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/synth/SynthSpinnerUI.java Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,469 @@
+/*
+ * 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 JSpinner
border
,
+ * foreground
, and background
, properties
+ * based on the corresponding "Spinner.*" properties from defaults table.
+ * The JSpinners
layout is set to the value returned by
+ * createLayout
. This method is called by installUI
.
+ *
+ * @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 JSpinner's
layout manager to null. This
+ * method is called by uninstallUI
.
+ *
+ * @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 JSpinner
. By default it just returns
+ * JSpinner.getEditor()
. Subclasses can override
+ * createEditor
to return a component that contains
+ * the spinner's editor or null, if they're going to handle adding
+ * the editor to the JSpinner
in an
+ * installUI
override.
+ *
+ * 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. + *
+ * The replaceEditor
method is called when the spinners
+ * editor is changed with JSpinner.setEditor
. If you've
+ * overriden this method, then you'll probably want to override
+ * replaceEditor
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 PropertyChangeListener
when the
+ * JSpinner
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:
+ *
+ * spinner.remove(oldEditor); + * spinner.add(newEditor, "Editor"); + *+ * The implementation of
replaceEditor
should be coordinated
+ * with the createEditor
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.
+ *
+ * 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. + *
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(); + } + } +}