jdk/src/java.desktop/share/classes/javax/swing/plaf/synth/SynthSpinnerUI.java
changeset 25859 3317bb8137f4
parent 25100 d527cc827d7d
child 37698 4d798c873df0
equal deleted inserted replaced
25858:836adbf7a2cd 25859:3317bb8137f4
       
     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 }