1 /* BasicSpinnerUI.java --
2    Copyright (C) 2003, 2004, 2005, 2006,  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package javax.swing.plaf.basic;
40 
41 import java.awt.Component;
42 import java.awt.Container;
43 import java.awt.Dimension;
44 import java.awt.Insets;
45 import java.awt.LayoutManager;
46 import java.awt.event.ActionEvent;
47 import java.awt.event.ActionListener;
48 import java.awt.event.MouseAdapter;
49 import java.awt.event.MouseEvent;
50 import java.beans.PropertyChangeEvent;
51 import java.beans.PropertyChangeListener;
52 
53 import javax.swing.JButton;
54 import javax.swing.JComponent;
55 import javax.swing.JSpinner;
56 import javax.swing.LookAndFeel;
57 import javax.swing.Timer;
58 import javax.swing.plaf.ComponentUI;
59 import javax.swing.plaf.SpinnerUI;
60 
61 /**
62  * A UI delegate for the {@link JSpinner} component.
63  *
64  * @author Ka-Hing Cheung
65  *
66  * @since 1.4
67  */
68 public class BasicSpinnerUI extends SpinnerUI
69 {
70   /**
71    * Creates a new <code>BasicSpinnerUI</code> for the specified
72    * <code>JComponent</code>
73    *
74    * @param c  the component (ignored).
75    *
76    * @return A new instance of {@link BasicSpinnerUI}.
77    */
createUI(JComponent c)78   public static ComponentUI createUI(JComponent c)
79   {
80     return new BasicSpinnerUI();
81   }
82 
83   /**
84    * Creates an editor component. Really, it just returns
85    * <code>JSpinner.getEditor()</code>
86    *
87    * @return a JComponent as an editor
88    *
89    * @see javax.swing.JSpinner#getEditor
90    */
createEditor()91   protected JComponent createEditor()
92   {
93     return spinner.getEditor();
94   }
95 
96   /**
97    * Creates a <code>LayoutManager</code> that layouts the sub components. The
98    * subcomponents are identifies by the constraint "Next", "Previous" and
99    * "Editor"
100    *
101    * @return a LayoutManager
102    *
103    * @see java.awt.LayoutManager
104    */
createLayout()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    */
createNextButton()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    */
createPreviousButton()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    */
createPropertyChangeListener()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 }
562