001    /* BasicSpinnerUI.java --
002       Copyright (C) 2003, 2004, 2005, 2006,  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.basic;
040    
041    import java.awt.Component;
042    import java.awt.Container;
043    import java.awt.Dimension;
044    import java.awt.Insets;
045    import java.awt.LayoutManager;
046    import java.awt.event.ActionEvent;
047    import java.awt.event.ActionListener;
048    import java.awt.event.MouseAdapter;
049    import java.awt.event.MouseEvent;
050    import java.beans.PropertyChangeEvent;
051    import java.beans.PropertyChangeListener;
052    
053    import javax.swing.JButton;
054    import javax.swing.JComponent;
055    import javax.swing.JSpinner;
056    import javax.swing.LookAndFeel;
057    import javax.swing.Timer;
058    import javax.swing.plaf.ComponentUI;
059    import javax.swing.plaf.SpinnerUI;
060    
061    /**
062     * A UI delegate for the {@link JSpinner} component.
063     *
064     * @author Ka-Hing Cheung
065     *
066     * @since 1.4
067     */
068    public class BasicSpinnerUI extends SpinnerUI
069    {
070      /**
071       * Creates a new <code>BasicSpinnerUI</code> for the specified
072       * <code>JComponent</code>
073       *
074       * @param c  the component (ignored).
075       *
076       * @return A new instance of {@link BasicSpinnerUI}.
077       */
078      public static ComponentUI createUI(JComponent c)
079      {
080        return new BasicSpinnerUI();
081      }
082    
083      /**
084       * Creates an editor component. Really, it just returns
085       * <code>JSpinner.getEditor()</code>
086       *
087       * @return a JComponent as an editor
088       *
089       * @see javax.swing.JSpinner#getEditor
090       */
091      protected JComponent createEditor()
092      {
093        return spinner.getEditor();
094      }
095    
096      /**
097       * Creates a <code>LayoutManager</code> that layouts the sub components. The
098       * subcomponents are identifies by the constraint "Next", "Previous" and
099       * "Editor"
100       *
101       * @return a LayoutManager
102       *
103       * @see java.awt.LayoutManager
104       */
105      protected LayoutManager createLayout()
106      {
107        return new DefaultLayoutManager();
108      }
109    
110      /**
111       * Creates the "Next" button
112       *
113       * @return the next button component
114       */
115      protected Component createNextButton()
116      {
117        JButton button = new BasicArrowButton(BasicArrowButton.NORTH);
118        return button;
119      }
120    
121      /**
122       * Creates the "Previous" button
123       *
124       * @return the previous button component
125       */
126      protected Component createPreviousButton()
127      {
128        JButton button = new BasicArrowButton(BasicArrowButton.SOUTH);
129        return button;
130      }
131    
132      /**
133       * Creates the <code>PropertyChangeListener</code> that will be attached by
134       * <code>installListeners</code>. It should watch for the "editor"
135       * property, when it's changed, replace the old editor with the new one,
136       * probably by calling <code>replaceEditor</code>
137       *
138       * @return a PropertyChangeListener
139       *
140       * @see #replaceEditor
141       */
142      protected PropertyChangeListener createPropertyChangeListener()
143      {
144        return new PropertyChangeListener()
145          {
146            public void propertyChange(PropertyChangeEvent event)
147            {
148              // FIXME: Add check for enabled property change. Need to
149              // disable the buttons.
150              if ("editor".equals(event.getPropertyName()))
151                BasicSpinnerUI.this.replaceEditor((JComponent) event.getOldValue(),
152                    (JComponent) event.getNewValue());
153              // FIXME: Handle 'font' property change
154            }
155          };
156      }
157    
158      /**
159       * Called by <code>installUI</code>. This should set various defaults
160       * obtained from <code>UIManager.getLookAndFeelDefaults</code>, as well as
161       * set the layout obtained from <code>createLayout</code>
162       *
163       * @see javax.swing.UIManager#getLookAndFeelDefaults
164       * @see #createLayout
165       * @see #installUI
166       */
167      protected void installDefaults()
168      {
169        LookAndFeel.installColorsAndFont(spinner, "Spinner.background",
170                                         "Spinner.foreground", "Spinner.font");
171        LookAndFeel.installBorder(spinner, "Spinner.border");
172        JComponent e = spinner.getEditor();
173        if (e instanceof JSpinner.DefaultEditor)
174          {
175            JSpinner.DefaultEditor de = (JSpinner.DefaultEditor) e;
176            de.getTextField().setBorder(null);  
177          }
178        spinner.setLayout(createLayout());
179        spinner.setOpaque(true);
180      }
181    
182      /*
183       * Called by <code>installUI</code>, which basically adds the
184       * <code>PropertyChangeListener</code> created by
185       * <code>createPropertyChangeListener</code>
186       *
187       * @see #createPropertyChangeListener
188       * @see #installUI
189       */
190      protected void installListeners()
191      {
192        spinner.addPropertyChangeListener(listener);
193      }
194    
195      /*
196       * Install listeners to the next button so that it increments the model
197       */
198      protected void installNextButtonListeners(Component c)
199      {
200        c.addMouseListener(new MouseAdapter()
201            {
202              public void mousePressed(MouseEvent evt)
203              {
204                if (! spinner.isEnabled())
205                  return;
206                increment();
207                timer.setInitialDelay(500);
208                timer.start();
209              }
210    
211              public void mouseReleased(MouseEvent evt)
212              {
213                timer.stop();
214              }
215    
216              void increment()
217              {
218                Object next = BasicSpinnerUI.this.spinner.getNextValue();
219                if (next != null)
220                  BasicSpinnerUI.this.spinner.getModel().setValue(next);
221              }
222    
223              volatile boolean mouseDown;
224              Timer timer = new Timer(50,
225                                      new ActionListener()
226                  {
227                    public void actionPerformed(ActionEvent event)
228                    {
229                      increment();
230                    }
231                  });
232            });
233      }
234    
235      /*
236       * Install listeners to the previous button so that it decrements the model
237       */
238      protected void installPreviousButtonListeners(Component c)
239      {
240        c.addMouseListener(new MouseAdapter()
241            {
242              public void mousePressed(MouseEvent evt)
243              {
244                if (! spinner.isEnabled())
245                  return;
246                decrement();
247                timer.setInitialDelay(500);
248                timer.start();
249              }
250    
251              public void mouseReleased(MouseEvent evt)
252              {
253                timer.stop();
254              }
255    
256              void decrement()
257              {
258                Object prev = BasicSpinnerUI.this.spinner.getPreviousValue();
259                if (prev != null)
260                  BasicSpinnerUI.this.spinner.getModel().setValue(prev);
261              }
262    
263              volatile boolean mouseDown;
264              Timer timer = new Timer(50,
265                                      new ActionListener()
266                  {
267                    public void actionPerformed(ActionEvent event)
268                    {
269                      decrement();
270                    }
271                  });
272            });
273      }
274    
275      /**
276       * Install this UI to the <code>JComponent</code>, which in reality, is a
277       * <code>JSpinner</code>. Calls <code>installDefaults</code>,
278       * <code>installListeners</code>, and also adds the buttons and editor.
279       *
280       * @param c DOCUMENT ME!
281       *
282       * @see #installDefaults
283       * @see #installListeners
284       * @see #createNextButton
285       * @see #createPreviousButton
286       * @see #createEditor
287       */
288      public void installUI(JComponent c)
289      {
290        super.installUI(c);
291    
292        spinner = (JSpinner) c;
293    
294        installDefaults();
295        installListeners();
296    
297        Component next = createNextButton();
298        Component previous = createPreviousButton();
299    
300        installNextButtonListeners(next);
301        installPreviousButtonListeners(previous);
302    
303        c.add(createEditor(), "Editor");
304        c.add(next, "Next");
305        c.add(previous, "Previous");
306      }
307    
308      /**
309       * Replace the old editor with the new one
310       *
311       * @param oldEditor the old editor
312       * @param newEditor the new one to replace with
313       */
314      protected void replaceEditor(JComponent oldEditor, JComponent newEditor)
315      {
316        spinner.remove(oldEditor);
317        spinner.add(newEditor);
318      }
319    
320      /**
321       * The reverse of <code>installDefaults</code>. Called by
322       * <code>uninstallUI</code>
323       */
324      protected void uninstallDefaults()
325      {
326        spinner.setLayout(null);
327      }
328    
329      /**
330       * The reverse of <code>installListeners</code>, called by
331       * <code>uninstallUI</code>
332       */
333      protected void uninstallListeners()
334      {
335        spinner.removePropertyChangeListener(listener);
336      }
337    
338      /**
339       * Called when the current L&F is replaced with another one, should call
340       * <code>uninstallDefaults</code> and <code>uninstallListeners</code> as
341       * well as remove the next/previous buttons and the editor
342       *
343       * @param c DOCUMENT ME!
344       */
345      public void uninstallUI(JComponent c)
346      {
347        super.uninstallUI(c);
348    
349        uninstallDefaults();
350        uninstallListeners();
351        c.removeAll();
352      }
353    
354      /** The spinner for this UI */
355      protected JSpinner spinner;
356    
357      /** DOCUMENT ME! */
358      private PropertyChangeListener listener = createPropertyChangeListener();
359    
360      /**
361       * A layout manager for the {@link JSpinner} component.  The spinner has
362       * three subcomponents: an editor, a 'next' button and a 'previous' button.
363       */
364      private class DefaultLayoutManager implements LayoutManager
365      {
366        /**
367         * Layout the spinners inner parts.
368         *
369         * @param parent The parent container
370         */
371        public void layoutContainer(Container parent)
372        {
373          synchronized (parent.getTreeLock())
374            {
375              Insets i = parent.getInsets();
376              boolean l2r = parent.getComponentOrientation().isLeftToRight();
377              /*
378                --------------    --------------
379                |        | n |    | n |        |
380                |   e    | - | or | - |   e    |
381                |        | p |    | p |        |
382                --------------    --------------
383              */
384              Dimension e = prefSize(editor);
385              Dimension n = prefSize(next);
386              Dimension p = prefSize(previous);
387              Dimension s = parent.getSize();
388    
389              int x = l2r ? i.left : i.right;
390              int y = i.top;
391              int w = Math.max(p.width, n.width);
392              int h = (s.height - i.bottom) / 2;
393              int e_width = s.width - w - i.left - i.right;
394    
395              if (l2r)
396                {
397                  setBounds(editor, x, y, e_width, 2 * h);
398                  x += e_width;
399                  setBounds(next, x, y, w, h);
400                  y += h;
401                  setBounds(previous, x, y, w, h);
402                }
403              else
404                {
405                  setBounds(next, x, y + (s.height - e.height) / 2, w, h);
406                  y += h;
407                  setBounds(previous, x, y + (s.height - e.height) / 2, w, h);
408                  x += w;
409                  y -= h;
410                  setBounds(editor, x, y, e_width, e.height);
411                }
412            }
413        }
414    
415        /**
416         * Calculates the minimum layout size.
417         *
418         * @param parent  the parent.
419         *
420         * @return The minimum layout size.
421         */
422        public Dimension minimumLayoutSize(Container parent)
423        {
424          Dimension d = new Dimension();
425    
426          if (editor != null)
427            {
428              Dimension tmp = editor.getMinimumSize();
429              d.width += tmp.width;
430              d.height = tmp.height;
431            }
432    
433          int nextWidth = 0;
434          int previousWidth = 0;
435    
436          if (next != null)
437            {
438              Dimension tmp = next.getMinimumSize();
439              nextWidth = tmp.width;
440            }
441          if (previous != null)
442            {
443              Dimension tmp = previous.getMinimumSize();
444              previousWidth = tmp.width;
445            }
446    
447          d.width += Math.max(nextWidth, previousWidth);
448    
449          return d;
450        }
451    
452        /**
453         * Returns the preferred layout size of the container.
454         *
455         * @param parent DOCUMENT ME!
456         *
457         * @return DOCUMENT ME!
458         */
459        public Dimension preferredLayoutSize(Container parent)
460        {
461          Dimension d = new Dimension();
462    
463          if (editor != null)
464            {
465              Dimension tmp = editor.getPreferredSize();
466              d.width += Math.max(tmp.width, 40);
467              d.height = tmp.height;
468            }
469    
470          int nextWidth = 0;
471          int previousWidth = 0;
472    
473          if (next != null)
474            {
475              Dimension tmp = next.getPreferredSize();
476              nextWidth = tmp.width;
477            }
478          if (previous != null)
479            {
480              Dimension tmp = previous.getPreferredSize();
481              previousWidth = tmp.width;
482            }
483    
484          d.width += Math.max(nextWidth, previousWidth);
485          Insets insets = parent.getInsets();
486          d.width = d.width + insets.left + insets.right;
487          d.height = d.height + insets.top + insets.bottom;
488          return d;
489        }
490    
491        /**
492         * DOCUMENT ME!
493         *
494         * @param child DOCUMENT ME!
495         */
496        public void removeLayoutComponent(Component child)
497        {
498          if (child == editor)
499            editor = null;
500          else if (child == next)
501            next = null;
502          else if (previous == child)
503            previous = null;
504        }
505    
506        /**
507         * DOCUMENT ME!
508         *
509         * @param name DOCUMENT ME!
510         * @param child DOCUMENT ME!
511         */
512        public void addLayoutComponent(String name, Component child)
513        {
514          if ("Editor".equals(name))
515            editor = child;
516          else if ("Next".equals(name))
517            next = child;
518          else if ("Previous".equals(name))
519            previous = child;
520        }
521    
522        /**
523         * DOCUMENT ME!
524         *
525         * @param c DOCUMENT ME!
526         *
527         * @return DOCUMENT ME!
528         */
529        private Dimension prefSize(Component c)
530        {
531          if (c == null)
532            return new Dimension();
533          else
534            return c.getPreferredSize();
535        }
536    
537        /**
538         * Sets the bounds for the specified component.
539         *
540         * @param c  the component.
541         * @param x  the x-coordinate for the top-left of the component bounds.
542         * @param y  the y-coordinate for the top-left of the component bounds.
543         * @param w  the width of the bounds.
544         * @param h  the height of the bounds.
545         */
546        private void setBounds(Component c, int x, int y, int w, int h)
547        {
548          if (c != null)
549            c.setBounds(x, y, w, h);
550        }
551    
552        /** The editor component. */
553        private Component editor;
554    
555        /** The next button. */
556        private Component next;
557    
558        /** The previous button. */
559        private Component previous;
560      }
561    }