Source for javax.swing.plaf.basic.BasicSpinnerUI

   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:    */
  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:    */
  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:    */
 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: }