Save This Page
Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]
    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   }

Save This Page
Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]