jdk/src/share/classes/javax/swing/plaf/basic/BasicSpinnerUI.java
changeset 2 90ce3da70b43
child 4394 92a8ec883f5d
equal deleted inserted replaced
0:fd16c54261b3 2:90ce3da70b43
       
     1 /*
       
     2  * Copyright 2000-2007 Sun Microsystems, Inc.  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.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 
       
    26 package javax.swing.plaf.basic;
       
    27 
       
    28 import java.awt.*;
       
    29 import java.awt.event.*;
       
    30 import java.text.ParseException;
       
    31 
       
    32 import javax.swing.*;
       
    33 import javax.swing.border.*;
       
    34 import javax.swing.event.*;
       
    35 import javax.swing.plaf.*;
       
    36 import javax.swing.text.*;
       
    37 
       
    38 import java.beans.*;
       
    39 import java.text.*;
       
    40 import java.util.*;
       
    41 import sun.swing.DefaultLookup;
       
    42 
       
    43 
       
    44 /**
       
    45  * The default Spinner UI delegate.
       
    46  *
       
    47  * @author Hans Muller
       
    48  * @since 1.4
       
    49  */
       
    50 public class BasicSpinnerUI extends SpinnerUI
       
    51 {
       
    52     /**
       
    53      * The spinner that we're a UI delegate for.  Initialized by
       
    54      * the <code>installUI</code> method, and reset to null
       
    55      * by <code>uninstallUI</code>.
       
    56      *
       
    57      * @see #installUI
       
    58      * @see #uninstallUI
       
    59      */
       
    60     protected JSpinner spinner;
       
    61     private Handler handler;
       
    62 
       
    63 
       
    64     /**
       
    65      * The mouse/action listeners that are added to the spinner's
       
    66      * arrow buttons.  These listeners are shared by all
       
    67      * spinner arrow buttons.
       
    68      *
       
    69      * @see #createNextButton
       
    70      * @see #createPreviousButton
       
    71      */
       
    72     private static final ArrowButtonHandler nextButtonHandler = new ArrowButtonHandler("increment", true);
       
    73     private static final ArrowButtonHandler previousButtonHandler = new ArrowButtonHandler("decrement", false);
       
    74     private PropertyChangeListener propertyChangeListener;
       
    75 
       
    76 
       
    77     /**
       
    78      * Used by the default LayoutManager class - SpinnerLayout for
       
    79      * missing (null) editor/nextButton/previousButton children.
       
    80      */
       
    81     private static final Dimension zeroSize = new Dimension(0, 0);
       
    82 
       
    83 
       
    84     /**
       
    85      * Returns a new instance of BasicSpinnerUI.  SpinnerListUI
       
    86      * delegates are allocated one per JSpinner.
       
    87      *
       
    88      * @param c the JSpinner (not used)
       
    89      * @see ComponentUI#createUI
       
    90      * @return a new BasicSpinnerUI object
       
    91      */
       
    92     public static ComponentUI createUI(JComponent c) {
       
    93         return new BasicSpinnerUI();
       
    94     }
       
    95 
       
    96 
       
    97     private void maybeAdd(Component c, String s) {
       
    98         if (c != null) {
       
    99             spinner.add(c, s);
       
   100         }
       
   101     }
       
   102 
       
   103 
       
   104     /**
       
   105      * Calls <code>installDefaults</code>, <code>installListeners</code>,
       
   106      * and then adds the components returned by <code>createNextButton</code>,
       
   107      * <code>createPreviousButton</code>, and <code>createEditor</code>.
       
   108      *
       
   109      * @param c the JSpinner
       
   110      * @see #installDefaults
       
   111      * @see #installListeners
       
   112      * @see #createNextButton
       
   113      * @see #createPreviousButton
       
   114      * @see #createEditor
       
   115      */
       
   116     public void installUI(JComponent c) {
       
   117         this.spinner = (JSpinner)c;
       
   118         installDefaults();
       
   119         installListeners();
       
   120         maybeAdd(createNextButton(), "Next");
       
   121         maybeAdd(createPreviousButton(), "Previous");
       
   122         maybeAdd(createEditor(), "Editor");
       
   123         updateEnabledState();
       
   124         installKeyboardActions();
       
   125     }
       
   126 
       
   127 
       
   128     /**
       
   129      * Calls <code>uninstallDefaults</code>, <code>uninstallListeners</code>,
       
   130      * and then removes all of the spinners children.
       
   131      *
       
   132      * @param c the JSpinner (not used)
       
   133      */
       
   134     public void uninstallUI(JComponent c) {
       
   135         uninstallDefaults();
       
   136         uninstallListeners();
       
   137         this.spinner = null;
       
   138         c.removeAll();
       
   139     }
       
   140 
       
   141 
       
   142     /**
       
   143      * Initializes <code>PropertyChangeListener</code> with
       
   144      * a shared object that delegates interesting PropertyChangeEvents
       
   145      * to protected methods.
       
   146      * <p>
       
   147      * This method is called by <code>installUI</code>.
       
   148      *
       
   149      * @see #replaceEditor
       
   150      * @see #uninstallListeners
       
   151      */
       
   152     protected void installListeners() {
       
   153         propertyChangeListener = createPropertyChangeListener();
       
   154         spinner.addPropertyChangeListener(propertyChangeListener);
       
   155         if (DefaultLookup.getBoolean(spinner, this,
       
   156             "Spinner.disableOnBoundaryValues", false)) {
       
   157             spinner.addChangeListener(getHandler());
       
   158         }
       
   159         JComponent editor = spinner.getEditor();
       
   160         if (editor != null && editor instanceof JSpinner.DefaultEditor) {
       
   161             JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField();
       
   162             if (tf != null) {
       
   163                 tf.addFocusListener(nextButtonHandler);
       
   164                 tf.addFocusListener(previousButtonHandler);
       
   165             }
       
   166         }
       
   167     }
       
   168 
       
   169 
       
   170     /**
       
   171      * Removes the <code>PropertyChangeListener</code> added
       
   172      * by installListeners.
       
   173      * <p>
       
   174      * This method is called by <code>uninstallUI</code>.
       
   175      *
       
   176      * @see #installListeners
       
   177      */
       
   178     protected void uninstallListeners() {
       
   179         spinner.removePropertyChangeListener(propertyChangeListener);
       
   180         spinner.removeChangeListener(handler);
       
   181         JComponent editor = spinner.getEditor();
       
   182         removeEditorBorderListener(editor);
       
   183         if (editor instanceof JSpinner.DefaultEditor) {
       
   184             JTextField tf = ((JSpinner.DefaultEditor)editor).getTextField();
       
   185             if (tf != null) {
       
   186                 tf.removeFocusListener(nextButtonHandler);
       
   187                 tf.removeFocusListener(previousButtonHandler);
       
   188             }
       
   189         }
       
   190         propertyChangeListener = null;
       
   191         handler = null;
       
   192     }
       
   193 
       
   194 
       
   195     /**
       
   196      * Initialize the <code>JSpinner</code> <code>border</code>,
       
   197      * <code>foreground</code>, and <code>background</code>, properties
       
   198      * based on the corresponding "Spinner.*" properties from defaults table.
       
   199      * The <code>JSpinners</code> layout is set to the value returned by
       
   200      * <code>createLayout</code>.  This method is called by <code>installUI</code>.
       
   201      *
       
   202      * @see #uninstallDefaults
       
   203      * @see #installUI
       
   204      * @see #createLayout
       
   205      * @see LookAndFeel#installBorder
       
   206      * @see LookAndFeel#installColors
       
   207      */
       
   208     protected void installDefaults() {
       
   209         spinner.setLayout(createLayout());
       
   210         LookAndFeel.installBorder(spinner, "Spinner.border");
       
   211         LookAndFeel.installColorsAndFont(spinner, "Spinner.background", "Spinner.foreground", "Spinner.font");
       
   212         LookAndFeel.installProperty(spinner, "opaque", Boolean.TRUE);
       
   213     }
       
   214 
       
   215 
       
   216     /**
       
   217      * Sets the <code>JSpinner's</code> layout manager to null.  This
       
   218      * method is called by <code>uninstallUI</code>.
       
   219      *
       
   220      * @see #installDefaults
       
   221      * @see #uninstallUI
       
   222      */
       
   223     protected void uninstallDefaults() {
       
   224         spinner.setLayout(null);
       
   225     }
       
   226 
       
   227 
       
   228     private Handler getHandler() {
       
   229         if (handler == null) {
       
   230             handler = new Handler();
       
   231         }
       
   232         return handler;
       
   233     }
       
   234 
       
   235 
       
   236     /**
       
   237      * Installs the necessary listeners on the next button, <code>c</code>,
       
   238      * to update the <code>JSpinner</code> in response to a user gesture.
       
   239      *
       
   240      * @param c Component to install the listeners on
       
   241      * @throws NullPointerException if <code>c</code> is null.
       
   242      * @see #createNextButton
       
   243      * @since 1.5
       
   244      */
       
   245     protected void installNextButtonListeners(Component c) {
       
   246         installButtonListeners(c, nextButtonHandler);
       
   247     }
       
   248 
       
   249     /**
       
   250      * Installs the necessary listeners on the previous button, <code>c</code>,
       
   251      * to update the <code>JSpinner</code> in response to a user gesture.
       
   252      *
       
   253      * @param c Component to install the listeners on.
       
   254      * @throws NullPointerException if <code>c</code> is null.
       
   255      * @see #createPreviousButton
       
   256      * @since 1.5
       
   257      */
       
   258     protected void installPreviousButtonListeners(Component c) {
       
   259         installButtonListeners(c, previousButtonHandler);
       
   260     }
       
   261 
       
   262     private void installButtonListeners(Component c,
       
   263                                         ArrowButtonHandler handler) {
       
   264         if (c instanceof JButton) {
       
   265             ((JButton)c).addActionListener(handler);
       
   266         }
       
   267         c.addMouseListener(handler);
       
   268     }
       
   269 
       
   270     /**
       
   271      * Create a <code>LayoutManager</code> that manages the <code>editor</code>,
       
   272      * <code>nextButton</code>, and <code>previousButton</code>
       
   273      * children of the JSpinner.  These three children must be
       
   274      * added with a constraint that identifies their role:
       
   275      * "Editor", "Next", and "Previous". The default layout manager
       
   276      * can handle the absence of any of these children.
       
   277      *
       
   278      * @return a LayoutManager for the editor, next button, and previous button.
       
   279      * @see #createNextButton
       
   280      * @see #createPreviousButton
       
   281      * @see #createEditor
       
   282      */
       
   283     protected LayoutManager createLayout() {
       
   284         return getHandler();
       
   285     }
       
   286 
       
   287 
       
   288     /**
       
   289      * Create a <code>PropertyChangeListener</code> that can be
       
   290      * added to the JSpinner itself.  Typically, this listener
       
   291      * will call replaceEditor when the "editor" property changes,
       
   292      * since it's the <code>SpinnerUI's</code> responsibility to
       
   293      * add the editor to the JSpinner (and remove the old one).
       
   294      * This method is called by <code>installListeners</code>.
       
   295      *
       
   296      * @return A PropertyChangeListener for the JSpinner itself
       
   297      * @see #installListeners
       
   298      */
       
   299     protected PropertyChangeListener createPropertyChangeListener() {
       
   300         return getHandler();
       
   301     }
       
   302 
       
   303 
       
   304     /**
       
   305      * Create a component that will replace the spinner models value
       
   306      * with the object returned by <code>spinner.getPreviousValue</code>.
       
   307      * By default the <code>previousButton</code> is a JButton. This
       
   308      * method invokes <code>installPreviousButtonListeners</code> to
       
   309      * install the necessary listeners to update the <code>JSpinner</code>'s
       
   310      * model in response to a user gesture. If a previousButton isn't needed
       
   311      * (in a subclass) then override this method to return null.
       
   312      *
       
   313      * @return a component that will replace the spinners model with the
       
   314      *     next value in the sequence, or null
       
   315      * @see #installUI
       
   316      * @see #createNextButton
       
   317      * @see #installPreviousButtonListeners
       
   318      */
       
   319     protected Component createPreviousButton() {
       
   320         Component c = createArrowButton(SwingConstants.SOUTH);
       
   321         c.setName("Spinner.previousButton");
       
   322         installPreviousButtonListeners(c);
       
   323         return c;
       
   324     }
       
   325 
       
   326 
       
   327     /**
       
   328      * Create a component that will replace the spinner models value
       
   329      * with the object returned by <code>spinner.getNextValue</code>.
       
   330      * By default the <code>nextButton</code> is a JButton
       
   331      * who's <code>ActionListener</code> updates it's <code>JSpinner</code>
       
   332      * ancestors model.  If a nextButton isn't needed (in a subclass)
       
   333      * then override this method to return null.
       
   334      *
       
   335      * @return a component that will replace the spinners model with the
       
   336      *     next value in the sequence, or null
       
   337      * @see #installUI
       
   338      * @see #createPreviousButton
       
   339      * @see #installNextButtonListeners
       
   340      */
       
   341     protected Component createNextButton() {
       
   342         Component c = createArrowButton(SwingConstants.NORTH);
       
   343         c.setName("Spinner.nextButton");
       
   344         installNextButtonListeners(c);
       
   345         return c;
       
   346     }
       
   347 
       
   348     private Component createArrowButton(int direction) {
       
   349         JButton b = new BasicArrowButton(direction);
       
   350         Border buttonBorder = UIManager.getBorder("Spinner.arrowButtonBorder");
       
   351         if (buttonBorder instanceof UIResource) {
       
   352             // Wrap the border to avoid having the UIResource be replaced by
       
   353             // the ButtonUI. This is the opposite of using BorderUIResource.
       
   354             b.setBorder(new CompoundBorder(buttonBorder, null));
       
   355         } else {
       
   356             b.setBorder(buttonBorder);
       
   357         }
       
   358         b.setInheritsPopupMenu(true);
       
   359         return b;
       
   360     }
       
   361 
       
   362 
       
   363     /**
       
   364      * This method is called by installUI to get the editor component
       
   365      * of the <code>JSpinner</code>.  By default it just returns
       
   366      * <code>JSpinner.getEditor()</code>.  Subclasses can override
       
   367      * <code>createEditor</code> to return a component that contains
       
   368      * the spinner's editor or null, if they're going to handle adding
       
   369      * the editor to the <code>JSpinner</code> in an
       
   370      * <code>installUI</code> override.
       
   371      * <p>
       
   372      * Typically this method would be overridden to wrap the editor
       
   373      * with a container with a custom border, since one can't assume
       
   374      * that the editors border can be set directly.
       
   375      * <p>
       
   376      * The <code>replaceEditor</code> method is called when the spinners
       
   377      * editor is changed with <code>JSpinner.setEditor</code>.  If you've
       
   378      * overriden this method, then you'll probably want to override
       
   379      * <code>replaceEditor</code> as well.
       
   380      *
       
   381      * @return the JSpinners editor JComponent, spinner.getEditor() by default
       
   382      * @see #installUI
       
   383      * @see #replaceEditor
       
   384      * @see JSpinner#getEditor
       
   385      */
       
   386     protected JComponent createEditor() {
       
   387         JComponent editor = spinner.getEditor();
       
   388         maybeRemoveEditorBorder(editor);
       
   389         installEditorBorderListener(editor);
       
   390         editor.setInheritsPopupMenu(true);
       
   391         updateEditorAlignment(editor);
       
   392         return editor;
       
   393     }
       
   394 
       
   395 
       
   396     /**
       
   397      * Called by the <code>PropertyChangeListener</code> when the
       
   398      * <code>JSpinner</code> editor property changes.  It's the responsibility
       
   399      * of this method to remove the old editor and add the new one.  By
       
   400      * default this operation is just:
       
   401      * <pre>
       
   402      * spinner.remove(oldEditor);
       
   403      * spinner.add(newEditor, "Editor");
       
   404      * </pre>
       
   405      * The implementation of <code>replaceEditor</code> should be coordinated
       
   406      * with the <code>createEditor</code> method.
       
   407      *
       
   408      * @see #createEditor
       
   409      * @see #createPropertyChangeListener
       
   410      */
       
   411     protected void replaceEditor(JComponent oldEditor, JComponent newEditor) {
       
   412         spinner.remove(oldEditor);
       
   413         maybeRemoveEditorBorder(newEditor);
       
   414         installEditorBorderListener(newEditor);
       
   415         newEditor.setInheritsPopupMenu(true);
       
   416         spinner.add(newEditor, "Editor");
       
   417     }
       
   418 
       
   419     private void updateEditorAlignment(JComponent editor) {
       
   420         if (editor instanceof JSpinner.DefaultEditor) {
       
   421             // if editor alignment isn't set in LAF, we get 0 (CENTER) here
       
   422             int alignment = UIManager.getInt("Spinner.editorAlignment");
       
   423             JTextField text = ((JSpinner.DefaultEditor)editor).getTextField();
       
   424             text.setHorizontalAlignment(alignment);
       
   425         }
       
   426     }
       
   427 
       
   428     /**
       
   429      * Remove the border around the inner editor component for LaFs
       
   430      * that install an outside border around the spinner,
       
   431      */
       
   432     private void maybeRemoveEditorBorder(JComponent editor) {
       
   433         if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
       
   434             if (editor instanceof JPanel &&
       
   435                 editor.getBorder() == null &&
       
   436                 editor.getComponentCount() > 0) {
       
   437 
       
   438                 editor = (JComponent)editor.getComponent(0);
       
   439             }
       
   440 
       
   441             if (editor != null && editor.getBorder() instanceof UIResource) {
       
   442                 editor.setBorder(null);
       
   443             }
       
   444         }
       
   445     }
       
   446 
       
   447     /**
       
   448      * Remove the border around the inner editor component for LaFs
       
   449      * that install an outside border around the spinner,
       
   450      */
       
   451     private void installEditorBorderListener(JComponent editor) {
       
   452         if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
       
   453             if (editor instanceof JPanel &&
       
   454                 editor.getBorder() == null &&
       
   455                 editor.getComponentCount() > 0) {
       
   456 
       
   457                 editor = (JComponent)editor.getComponent(0);
       
   458             }
       
   459             if (editor != null &&
       
   460                 (editor.getBorder() == null ||
       
   461                  editor.getBorder() instanceof UIResource)) {
       
   462                 editor.addPropertyChangeListener(getHandler());
       
   463             }
       
   464         }
       
   465     }
       
   466 
       
   467     private void removeEditorBorderListener(JComponent editor) {
       
   468         if (!UIManager.getBoolean("Spinner.editorBorderPainted")) {
       
   469             if (editor instanceof JPanel &&
       
   470                 editor.getComponentCount() > 0) {
       
   471 
       
   472                 editor = (JComponent)editor.getComponent(0);
       
   473             }
       
   474             if (editor != null) {
       
   475                 editor.removePropertyChangeListener(getHandler());
       
   476             }
       
   477         }
       
   478     }
       
   479 
       
   480 
       
   481     /**
       
   482      * Updates the enabled state of the children Components based on the
       
   483      * enabled state of the <code>JSpinner</code>.
       
   484      */
       
   485     private void updateEnabledState() {
       
   486         updateEnabledState(spinner, spinner.isEnabled());
       
   487     }
       
   488 
       
   489 
       
   490     /**
       
   491      * Recursively updates the enabled state of the child
       
   492      * <code>Component</code>s of <code>c</code>.
       
   493      */
       
   494     private void updateEnabledState(Container c, boolean enabled) {
       
   495         for (int counter = c.getComponentCount() - 1; counter >= 0;counter--) {
       
   496             Component child = c.getComponent(counter);
       
   497 
       
   498             if (DefaultLookup.getBoolean(spinner, this,
       
   499                 "Spinner.disableOnBoundaryValues", false)) {
       
   500                 SpinnerModel model = spinner.getModel();
       
   501                 if (child.getName() == "Spinner.nextButton" &&
       
   502                     model.getNextValue() == null) {
       
   503                     child.setEnabled(false);
       
   504                 }
       
   505                 else if (child.getName() == "Spinner.previousButton" &&
       
   506                          model.getPreviousValue() == null) {
       
   507                     child.setEnabled(false);
       
   508                 }
       
   509                 else {
       
   510                     child.setEnabled(enabled);
       
   511                 }
       
   512             }
       
   513             else {
       
   514                 child.setEnabled(enabled);
       
   515             }
       
   516             if (child instanceof Container) {
       
   517                 updateEnabledState((Container)child, enabled);
       
   518             }
       
   519         }
       
   520     }
       
   521 
       
   522 
       
   523     /**
       
   524      * Installs the keyboard Actions onto the JSpinner.
       
   525      *
       
   526      * @since 1.5
       
   527      */
       
   528     protected void installKeyboardActions() {
       
   529         InputMap iMap = getInputMap(JComponent.
       
   530                                    WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
       
   531 
       
   532         SwingUtilities.replaceUIInputMap(spinner, JComponent.
       
   533                                          WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
       
   534                                          iMap);
       
   535 
       
   536         LazyActionMap.installLazyActionMap(spinner, BasicSpinnerUI.class,
       
   537                 "Spinner.actionMap");
       
   538     }
       
   539 
       
   540     /**
       
   541      * Returns the InputMap to install for <code>condition</code>.
       
   542      */
       
   543     private InputMap getInputMap(int condition) {
       
   544         if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
       
   545             return (InputMap)DefaultLookup.get(spinner, this,
       
   546                     "Spinner.ancestorInputMap");
       
   547         }
       
   548         return null;
       
   549     }
       
   550 
       
   551     static void loadActionMap(LazyActionMap map) {
       
   552         map.put("increment", nextButtonHandler);
       
   553         map.put("decrement", previousButtonHandler);
       
   554     }
       
   555 
       
   556     /**
       
   557      * Returns the baseline.
       
   558      *
       
   559      * @throws NullPointerException {@inheritDoc}
       
   560      * @throws IllegalArgumentException {@inheritDoc}
       
   561      * @see javax.swing.JComponent#getBaseline(int, int)
       
   562      * @since 1.6
       
   563      */
       
   564     public int getBaseline(JComponent c, int width, int height) {
       
   565         super.getBaseline(c, width, height);
       
   566         JComponent editor = spinner.getEditor();
       
   567         Insets insets = spinner.getInsets();
       
   568         width = width - insets.left - insets.right;
       
   569         height = height - insets.top - insets.bottom;
       
   570         if (width >= 0 && height >= 0) {
       
   571             int baseline = editor.getBaseline(width, height);
       
   572             if (baseline >= 0) {
       
   573                 return insets.top + baseline;
       
   574             }
       
   575         }
       
   576         return -1;
       
   577     }
       
   578 
       
   579     /**
       
   580      * Returns an enum indicating how the baseline of the component
       
   581      * changes as the size changes.
       
   582      *
       
   583      * @throws NullPointerException {@inheritDoc}
       
   584      * @see javax.swing.JComponent#getBaseline(int, int)
       
   585      * @since 1.6
       
   586      */
       
   587     public Component.BaselineResizeBehavior getBaselineResizeBehavior(
       
   588             JComponent c) {
       
   589         super.getBaselineResizeBehavior(c);
       
   590         return spinner.getEditor().getBaselineResizeBehavior();
       
   591     }
       
   592 
       
   593     /**
       
   594      * A handler for spinner arrow button mouse and action events.  When
       
   595      * a left mouse pressed event occurs we look up the (enabled) spinner
       
   596      * that's the source of the event and start the autorepeat timer.  The
       
   597      * timer fires action events until any button is released at which
       
   598      * point the timer is stopped and the reference to the spinner cleared.
       
   599      * The timer doesn't start until after a 300ms delay, so often the
       
   600      * source of the initial (and final) action event is just the button
       
   601      * logic for mouse released - which means that we're relying on the fact
       
   602      * that our mouse listener runs after the buttons mouse listener.
       
   603      * <p>
       
   604      * Note that one instance of this handler is shared by all slider previous
       
   605      * arrow buttons and likewise for all of the next buttons,
       
   606      * so it doesn't have any state that persists beyond the limits
       
   607      * of a single button pressed/released gesture.
       
   608      */
       
   609     private static class ArrowButtonHandler extends AbstractAction
       
   610                                             implements FocusListener, MouseListener, UIResource {
       
   611         final javax.swing.Timer autoRepeatTimer;
       
   612         final boolean isNext;
       
   613         JSpinner spinner = null;
       
   614         JButton arrowButton = null;
       
   615 
       
   616         ArrowButtonHandler(String name, boolean isNext) {
       
   617             super(name);
       
   618             this.isNext = isNext;
       
   619             autoRepeatTimer = new javax.swing.Timer(60, this);
       
   620             autoRepeatTimer.setInitialDelay(300);
       
   621         }
       
   622 
       
   623         private JSpinner eventToSpinner(AWTEvent e) {
       
   624             Object src = e.getSource();
       
   625             while ((src instanceof Component) && !(src instanceof JSpinner)) {
       
   626                 src = ((Component)src).getParent();
       
   627             }
       
   628             return (src instanceof JSpinner) ? (JSpinner)src : null;
       
   629         }
       
   630 
       
   631         public void actionPerformed(ActionEvent e) {
       
   632             JSpinner spinner = this.spinner;
       
   633 
       
   634             if (!(e.getSource() instanceof javax.swing.Timer)) {
       
   635                 // Most likely resulting from being in ActionMap.
       
   636                 spinner = eventToSpinner(e);
       
   637                 if (e.getSource() instanceof JButton) {
       
   638                     arrowButton = (JButton)e.getSource();
       
   639                 }
       
   640             } else {
       
   641                 if (arrowButton!=null && !arrowButton.getModel().isPressed()
       
   642                     && autoRepeatTimer.isRunning()) {
       
   643                     autoRepeatTimer.stop();
       
   644                     spinner = null;
       
   645                     arrowButton = null;
       
   646                 }
       
   647             }
       
   648             if (spinner != null) {
       
   649                 try {
       
   650                     int calendarField = getCalendarField(spinner);
       
   651                     spinner.commitEdit();
       
   652                     if (calendarField != -1) {
       
   653                         ((SpinnerDateModel)spinner.getModel()).
       
   654                                  setCalendarField(calendarField);
       
   655                     }
       
   656                     Object value = (isNext) ? spinner.getNextValue() :
       
   657                                spinner.getPreviousValue();
       
   658                     if (value != null) {
       
   659                         spinner.setValue(value);
       
   660                         select(spinner);
       
   661                     }
       
   662                 } catch (IllegalArgumentException iae) {
       
   663                     UIManager.getLookAndFeel().provideErrorFeedback(spinner);
       
   664                 } catch (ParseException pe) {
       
   665                     UIManager.getLookAndFeel().provideErrorFeedback(spinner);
       
   666                 }
       
   667             }
       
   668         }
       
   669 
       
   670         /**
       
   671          * If the spinner's editor is a DateEditor, this selects the field
       
   672          * associated with the value that is being incremented.
       
   673          */
       
   674         private void select(JSpinner spinner) {
       
   675             JComponent editor = spinner.getEditor();
       
   676 
       
   677             if (editor instanceof JSpinner.DateEditor) {
       
   678                 JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
       
   679                 JFormattedTextField ftf = dateEditor.getTextField();
       
   680                 Format format = dateEditor.getFormat();
       
   681                 Object value;
       
   682 
       
   683                 if (format != null && (value = spinner.getValue()) != null) {
       
   684                     SpinnerDateModel model = dateEditor.getModel();
       
   685                     DateFormat.Field field = DateFormat.Field.ofCalendarField(
       
   686                         model.getCalendarField());
       
   687 
       
   688                     if (field != null) {
       
   689                         try {
       
   690                             AttributedCharacterIterator iterator = format.
       
   691                                 formatToCharacterIterator(value);
       
   692                             if (!select(ftf, iterator, field) &&
       
   693                                        field == DateFormat.Field.HOUR0) {
       
   694                                 select(ftf, iterator, DateFormat.Field.HOUR1);
       
   695                             }
       
   696                         }
       
   697                         catch (IllegalArgumentException iae) {}
       
   698                     }
       
   699                 }
       
   700             }
       
   701         }
       
   702 
       
   703         /**
       
   704          * Selects the passed in field, returning true if it is found,
       
   705          * false otherwise.
       
   706          */
       
   707         private boolean select(JFormattedTextField ftf,
       
   708                                AttributedCharacterIterator iterator,
       
   709                                DateFormat.Field field) {
       
   710             int max = ftf.getDocument().getLength();
       
   711 
       
   712             iterator.first();
       
   713             do {
       
   714                 Map attrs = iterator.getAttributes();
       
   715 
       
   716                 if (attrs != null && attrs.containsKey(field)){
       
   717                     int start = iterator.getRunStart(field);
       
   718                     int end = iterator.getRunLimit(field);
       
   719 
       
   720                     if (start != -1 && end != -1 && start <= max &&
       
   721                                        end <= max) {
       
   722                         ftf.select(start, end);
       
   723                     }
       
   724                     return true;
       
   725                 }
       
   726             } while (iterator.next() != CharacterIterator.DONE);
       
   727             return false;
       
   728         }
       
   729 
       
   730         /**
       
   731          * Returns the calendarField under the start of the selection, or
       
   732          * -1 if there is no valid calendar field under the selection (or
       
   733          * the spinner isn't editing dates.
       
   734          */
       
   735         private int getCalendarField(JSpinner spinner) {
       
   736             JComponent editor = spinner.getEditor();
       
   737 
       
   738             if (editor instanceof JSpinner.DateEditor) {
       
   739                 JSpinner.DateEditor dateEditor = (JSpinner.DateEditor)editor;
       
   740                 JFormattedTextField ftf = dateEditor.getTextField();
       
   741                 int start = ftf.getSelectionStart();
       
   742                 JFormattedTextField.AbstractFormatter formatter =
       
   743                                     ftf.getFormatter();
       
   744 
       
   745                 if (formatter instanceof InternationalFormatter) {
       
   746                     Format.Field[] fields = ((InternationalFormatter)
       
   747                                              formatter).getFields(start);
       
   748 
       
   749                     for (int counter = 0; counter < fields.length; counter++) {
       
   750                         if (fields[counter] instanceof DateFormat.Field) {
       
   751                             int calendarField;
       
   752 
       
   753                             if (fields[counter] == DateFormat.Field.HOUR1) {
       
   754                                 calendarField = Calendar.HOUR;
       
   755                             }
       
   756                             else {
       
   757                                 calendarField = ((DateFormat.Field)
       
   758                                         fields[counter]).getCalendarField();
       
   759                             }
       
   760                             if (calendarField != -1) {
       
   761                                 return calendarField;
       
   762                             }
       
   763                         }
       
   764                     }
       
   765                 }
       
   766             }
       
   767             return -1;
       
   768         }
       
   769 
       
   770         public void mousePressed(MouseEvent e) {
       
   771             if (SwingUtilities.isLeftMouseButton(e) && e.getComponent().isEnabled()) {
       
   772                 spinner = eventToSpinner(e);
       
   773                 autoRepeatTimer.start();
       
   774 
       
   775                 focusSpinnerIfNecessary();
       
   776             }
       
   777         }
       
   778 
       
   779         public void mouseReleased(MouseEvent e) {
       
   780             autoRepeatTimer.stop();
       
   781             arrowButton = null;
       
   782             spinner = null;
       
   783         }
       
   784 
       
   785         public void mouseClicked(MouseEvent e) {
       
   786         }
       
   787 
       
   788         public void mouseEntered(MouseEvent e) {
       
   789             if (spinner != null && !autoRepeatTimer.isRunning() && spinner == eventToSpinner(e)) {
       
   790                 autoRepeatTimer.start();
       
   791             }
       
   792         }
       
   793 
       
   794         public void mouseExited(MouseEvent e) {
       
   795             if (autoRepeatTimer.isRunning()) {
       
   796                 autoRepeatTimer.stop();
       
   797             }
       
   798         }
       
   799 
       
   800         /**
       
   801          * Requests focus on a child of the spinner if the spinner doesn't
       
   802          * have focus.
       
   803          */
       
   804         private void focusSpinnerIfNecessary() {
       
   805             Component fo = KeyboardFocusManager.
       
   806                               getCurrentKeyboardFocusManager().getFocusOwner();
       
   807             if (spinner.isRequestFocusEnabled() && (
       
   808                         fo == null ||
       
   809                         !SwingUtilities.isDescendingFrom(fo, spinner))) {
       
   810                 Container root = spinner;
       
   811 
       
   812                 if (!root.isFocusCycleRoot()) {
       
   813                     root = root.getFocusCycleRootAncestor();
       
   814                 }
       
   815                 if (root != null) {
       
   816                     FocusTraversalPolicy ftp = root.getFocusTraversalPolicy();
       
   817                     Component child = ftp.getComponentAfter(root, spinner);
       
   818 
       
   819                     if (child != null && SwingUtilities.isDescendingFrom(
       
   820                                                         child, spinner)) {
       
   821                         child.requestFocus();
       
   822                     }
       
   823                 }
       
   824             }
       
   825         }
       
   826 
       
   827         public void focusGained(FocusEvent e) {
       
   828         }
       
   829 
       
   830         public void focusLost(FocusEvent e) {
       
   831             if (spinner == eventToSpinner(e)) {
       
   832                 if (autoRepeatTimer.isRunning()) {
       
   833                     autoRepeatTimer.stop();
       
   834                 }
       
   835                 spinner = null;
       
   836                 if (arrowButton != null) {
       
   837                     ButtonModel model = arrowButton.getModel();
       
   838                     model.setPressed(false);
       
   839                     model.setArmed(false);
       
   840                     arrowButton = null;
       
   841                 }
       
   842             }
       
   843         }
       
   844     }
       
   845 
       
   846 
       
   847     private static class Handler implements LayoutManager,
       
   848             PropertyChangeListener, ChangeListener {
       
   849         //
       
   850         // LayoutManager
       
   851         //
       
   852         private Component nextButton = null;
       
   853         private Component previousButton = null;
       
   854         private Component editor = null;
       
   855 
       
   856         public void addLayoutComponent(String name, Component c) {
       
   857             if ("Next".equals(name)) {
       
   858                 nextButton = c;
       
   859             }
       
   860             else if ("Previous".equals(name)) {
       
   861                 previousButton = c;
       
   862             }
       
   863             else if ("Editor".equals(name)) {
       
   864                 editor = c;
       
   865             }
       
   866         }
       
   867 
       
   868         public void removeLayoutComponent(Component c) {
       
   869             if (c == nextButton) {
       
   870                 nextButton = null;
       
   871             }
       
   872             else if (c == previousButton) {
       
   873                 previousButton = null;
       
   874             }
       
   875             else if (c == editor) {
       
   876                 editor = null;
       
   877             }
       
   878         }
       
   879 
       
   880         private Dimension preferredSize(Component c) {
       
   881             return (c == null) ? zeroSize : c.getPreferredSize();
       
   882         }
       
   883 
       
   884         public Dimension preferredLayoutSize(Container parent) {
       
   885             Dimension nextD = preferredSize(nextButton);
       
   886             Dimension previousD = preferredSize(previousButton);
       
   887             Dimension editorD = preferredSize(editor);
       
   888 
       
   889             /* Force the editors height to be a multiple of 2
       
   890              */
       
   891             editorD.height = ((editorD.height + 1) / 2) * 2;
       
   892 
       
   893             Dimension size = new Dimension(editorD.width, editorD.height);
       
   894             size.width += Math.max(nextD.width, previousD.width);
       
   895             Insets insets = parent.getInsets();
       
   896             size.width += insets.left + insets.right;
       
   897             size.height += insets.top + insets.bottom;
       
   898             return size;
       
   899         }
       
   900 
       
   901         public Dimension minimumLayoutSize(Container parent) {
       
   902             return preferredLayoutSize(parent);
       
   903         }
       
   904 
       
   905         private void setBounds(Component c, int x, int y, int width, int height) {
       
   906             if (c != null) {
       
   907                 c.setBounds(x, y, width, height);
       
   908             }
       
   909         }
       
   910 
       
   911         public void layoutContainer(Container parent) {
       
   912             int width  = parent.getWidth();
       
   913             int height = parent.getHeight();
       
   914 
       
   915             Insets insets = parent.getInsets();
       
   916             Dimension nextD = preferredSize(nextButton);
       
   917             Dimension previousD = preferredSize(previousButton);
       
   918             int buttonsWidth = Math.max(nextD.width, previousD.width);
       
   919             int editorHeight = height - (insets.top + insets.bottom);
       
   920 
       
   921             // The arrowButtonInsets value is used instead of the JSpinner's
       
   922             // insets if not null. Defining this to be (0, 0, 0, 0) causes the
       
   923             // buttons to be aligned with the outer edge of the spinner's
       
   924             // border, and leaving it as "null" places the buttons completely
       
   925             // inside the spinner's border.
       
   926             Insets buttonInsets = UIManager.getInsets("Spinner.arrowButtonInsets");
       
   927             if (buttonInsets == null) {
       
   928                 buttonInsets = insets;
       
   929             }
       
   930 
       
   931             /* Deal with the spinner's componentOrientation property.
       
   932              */
       
   933             int editorX, editorWidth, buttonsX;
       
   934             if (parent.getComponentOrientation().isLeftToRight()) {
       
   935                 editorX = insets.left;
       
   936                 editorWidth = width - insets.left - buttonsWidth - buttonInsets.right;
       
   937                 buttonsX = width - buttonsWidth - buttonInsets.right;
       
   938             } else {
       
   939                 buttonsX = buttonInsets.left;
       
   940                 editorX = buttonsX + buttonsWidth;
       
   941                 editorWidth = width - buttonInsets.left - buttonsWidth - insets.right;
       
   942             }
       
   943 
       
   944             int nextY = buttonInsets.top;
       
   945             int nextHeight = (height / 2) + (height % 2) - nextY;
       
   946             int previousY = buttonInsets.top + nextHeight;
       
   947             int previousHeight = height - previousY - buttonInsets.bottom;
       
   948 
       
   949             setBounds(editor,         editorX,  insets.top, editorWidth, editorHeight);
       
   950             setBounds(nextButton,     buttonsX, nextY,      buttonsWidth, nextHeight);
       
   951             setBounds(previousButton, buttonsX, previousY,  buttonsWidth, previousHeight);
       
   952         }
       
   953 
       
   954 
       
   955         //
       
   956         // PropertyChangeListener
       
   957         //
       
   958         public void propertyChange(PropertyChangeEvent e)
       
   959         {
       
   960             String propertyName = e.getPropertyName();
       
   961             if (e.getSource() instanceof JSpinner) {
       
   962                 JSpinner spinner = (JSpinner)(e.getSource());
       
   963                 SpinnerUI spinnerUI = spinner.getUI();
       
   964 
       
   965                 if (spinnerUI instanceof BasicSpinnerUI) {
       
   966                     BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
       
   967 
       
   968                     if ("editor".equals(propertyName)) {
       
   969                         JComponent oldEditor = (JComponent)e.getOldValue();
       
   970                         JComponent newEditor = (JComponent)e.getNewValue();
       
   971                         ui.replaceEditor(oldEditor, newEditor);
       
   972                         ui.updateEnabledState();
       
   973                         if (oldEditor instanceof JSpinner.DefaultEditor) {
       
   974                             JTextField tf =
       
   975                                 ((JSpinner.DefaultEditor)oldEditor).getTextField();
       
   976                             if (tf != null) {
       
   977                                 tf.removeFocusListener(nextButtonHandler);
       
   978                                 tf.removeFocusListener(previousButtonHandler);
       
   979                             }
       
   980                         }
       
   981                         if (newEditor instanceof JSpinner.DefaultEditor) {
       
   982                             JTextField tf =
       
   983                                 ((JSpinner.DefaultEditor)newEditor).getTextField();
       
   984                             if (tf != null) {
       
   985                                 if (tf.getFont() instanceof UIResource) {
       
   986                                     tf.setFont(spinner.getFont());
       
   987                                 }
       
   988                                 tf.addFocusListener(nextButtonHandler);
       
   989                                 tf.addFocusListener(previousButtonHandler);
       
   990                             }
       
   991                         }
       
   992                     }
       
   993                     else if ("enabled".equals(propertyName) ||
       
   994                              "model".equals(propertyName)) {
       
   995                         ui.updateEnabledState();
       
   996                     }
       
   997                 else if ("font".equals(propertyName)) {
       
   998                     JComponent editor = spinner.getEditor();
       
   999                     if (editor!=null && editor instanceof JSpinner.DefaultEditor) {
       
  1000                         JTextField tf =
       
  1001                             ((JSpinner.DefaultEditor)editor).getTextField();
       
  1002                         if (tf != null) {
       
  1003                             if (tf.getFont() instanceof UIResource) {
       
  1004                                 tf.setFont(spinner.getFont());
       
  1005                             }
       
  1006                         }
       
  1007                     }
       
  1008                 }
       
  1009                 else if (JComponent.TOOL_TIP_TEXT_KEY.equals(propertyName)) {
       
  1010                     updateToolTipTextForChildren(spinner);
       
  1011                 }
       
  1012                 }
       
  1013             } else if (e.getSource() instanceof JComponent) {
       
  1014                 JComponent c = (JComponent)e.getSource();
       
  1015                 if ((c.getParent() instanceof JPanel) &&
       
  1016                     (c.getParent().getParent() instanceof JSpinner) &&
       
  1017                     "border".equals(propertyName)) {
       
  1018 
       
  1019                     JSpinner spinner = (JSpinner)c.getParent().getParent();
       
  1020                     SpinnerUI spinnerUI = spinner.getUI();
       
  1021                     if (spinnerUI instanceof BasicSpinnerUI) {
       
  1022                         BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
       
  1023                         ui.maybeRemoveEditorBorder(c);
       
  1024                     }
       
  1025                 }
       
  1026             }
       
  1027         }
       
  1028 
       
  1029         // Syncronizes the ToolTip text for the components within the spinner
       
  1030         // to be the same value as the spinner ToolTip text.
       
  1031         private void updateToolTipTextForChildren(JComponent spinner) {
       
  1032             String toolTipText = spinner.getToolTipText();
       
  1033             Component[] children = spinner.getComponents();
       
  1034             for (int i = 0; i < children.length; i++) {
       
  1035                 if (children[i] instanceof JSpinner.DefaultEditor) {
       
  1036                     JTextField tf = ((JSpinner.DefaultEditor)children[i]).getTextField();
       
  1037                     if (tf != null) {
       
  1038                         tf.setToolTipText(toolTipText);
       
  1039                     }
       
  1040                 } else if (children[i] instanceof JComponent) {
       
  1041                     ((JComponent)children[i]).setToolTipText( spinner.getToolTipText() );
       
  1042                 }
       
  1043             }
       
  1044         }
       
  1045 
       
  1046         public void stateChanged(ChangeEvent e) {
       
  1047             if (e.getSource() instanceof JSpinner) {
       
  1048                 JSpinner spinner = (JSpinner)e.getSource();
       
  1049                 SpinnerUI spinnerUI = spinner.getUI();
       
  1050                 if (DefaultLookup.getBoolean(spinner, spinnerUI,
       
  1051                     "Spinner.disableOnBoundaryValues", false) &&
       
  1052                     spinnerUI instanceof BasicSpinnerUI) {
       
  1053                     BasicSpinnerUI ui = (BasicSpinnerUI)spinnerUI;
       
  1054                     ui.updateEnabledState();
       
  1055                 }
       
  1056             }
       
  1057         }
       
  1058     }
       
  1059 }