Source for javax.swing.plaf.basic.BasicSpinnerUI

   1: /* SpinnerUI.java --
   2:    Copyright (C) 2003, 2004, 2005  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.Timer;
  57: import javax.swing.UIDefaults;
  58: import javax.swing.UIManager;
  59: import javax.swing.plaf.ComponentUI;
  60: import javax.swing.plaf.SpinnerUI;
  61: 
  62: /**
  63:  * DOCUMENT ME!
  64:  *
  65:  * @author Ka-Hing Cheung
  66:  *
  67:  * @see javax.swing.JSpinner
  68:  * @since 1.4
  69:  */
  70: public class BasicSpinnerUI extends SpinnerUI
  71: {
  72:   /**
  73:    * Creates a new <code>ComponentUI</code> for the specified
  74:    * <code>JComponent</code>
  75:    *
  76:    * @param c DOCUMENT ME!
  77:    *
  78:    * @return a ComponentUI
  79:    */
  80:   public static ComponentUI createUI(JComponent c)
  81:   {
  82:     return new BasicSpinnerUI();
  83:   }
  84: 
  85:   /**
  86:    * Creates an editor component. Really, it just returns
  87:    * <code>JSpinner.getEditor()</code>
  88:    *
  89:    * @return a JComponent as an editor
  90:    *
  91:    * @see javax.swing.JSpinner#getEditor
  92:    */
  93:   protected JComponent createEditor()
  94:   {
  95:     return spinner.getEditor();
  96:   }
  97: 
  98:   /**
  99:    * Creates a <code>LayoutManager</code> that layouts the sub components. The
 100:    * subcomponents are identifies by the constraint "Next", "Previous" and
 101:    * "Editor"
 102:    *
 103:    * @return a LayoutManager
 104:    *
 105:    * @see java.awt.LayoutManager
 106:    */
 107:   protected LayoutManager createLayout()
 108:   {
 109:     return new DefaultLayoutManager();
 110:   }
 111: 
 112:   /**
 113:    * Creates the "Next" button
 114:    *
 115:    * @return the next button component
 116:    */
 117:   protected Component createNextButton()
 118:   {
 119:     JButton button = new BasicArrowButton(BasicArrowButton.NORTH);
 120:     return button;
 121:   }
 122: 
 123:   /**
 124:    * Creates the "Previous" button
 125:    *
 126:    * @return the previous button component
 127:    */
 128:   protected Component createPreviousButton()
 129:   {
 130:     JButton button = new BasicArrowButton(BasicArrowButton.SOUTH);
 131:     return button;
 132:   }
 133: 
 134:   /**
 135:    * Creates the <code>PropertyChangeListener</code> that will be attached by
 136:    * <code>installListeners</code>. It should watch for the "editor"
 137:    * property, when it's changed, replace the old editor with the new one,
 138:    * probably by calling <code>replaceEditor</code>
 139:    *
 140:    * @return a PropertyChangeListener
 141:    *
 142:    * @see #replaceEditor
 143:    */
 144:   protected PropertyChangeListener createPropertyChangeListener()
 145:   {
 146:     return new PropertyChangeListener()
 147:       {
 148:     public void propertyChange(PropertyChangeEvent evt)
 149:     {
 150:       // FIXME: Add check for enabled property change. Need to
 151:       // disable the buttons.
 152:       if ("editor".equals(evt.getPropertyName()))
 153:         BasicSpinnerUI.this.replaceEditor((JComponent) evt.getOldValue(),
 154:                                           (JComponent) evt.getNewValue());
 155:     }
 156:       };
 157:   }
 158: 
 159:   /**
 160:    * Called by <code>installUI</code>. This should set various defaults
 161:    * obtained from <code>UIManager.getLookAndFeelDefaults</code>, as well as
 162:    * set the layout obtained from <code>createLayout</code>
 163:    *
 164:    * @see javax.swing.UIManager#getLookAndFeelDefaults
 165:    * @see #createLayout
 166:    * @see #installUI
 167:    */
 168:   protected void installDefaults()
 169:   {
 170:     /* most of it copied from BasicLabelUI, I don't know what keys are
 171:        available, so someone may want to update this. Hence: TODO
 172:     */
 173:     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 174:     /*
 175:     spinner.setForeground(defaults.getColor("Spinner.foreground"));
 176:     spinner.setBackground(defaults.getColor("Spinner.background"));
 177:     spinner.setFont(defaults.getFont("Spinner.font"));
 178:     spinner.setBorder(defaults.getBorder("Spinner.border"));
 179:     */
 180:     spinner.setLayout(createLayout());
 181:     spinner.setOpaque(true);
 182:   }
 183: 
 184:   /*
 185:    * Called by <code>installUI</code>, which basically adds the
 186:    * <code>PropertyChangeListener</code> created by
 187:    * <code>createPropertyChangeListener</code>
 188:    *
 189:    * @see #createPropertyChangeListener
 190:    * @see #installUI
 191:    */
 192:   protected void installListeners()
 193:   {
 194:     spinner.addPropertyChangeListener(listener);
 195:   }
 196: 
 197:   /*
 198:    * Install listeners to the next button so that it increments the model
 199:    */
 200:   protected void installNextButtonListeners(Component c)
 201:   {
 202:     c.addMouseListener(new MouseAdapter()
 203:         {
 204:       public void mousePressed(MouseEvent evt)
 205:       {
 206:         if (! spinner.isEnabled())
 207:           return;
 208:         increment();
 209:         timer.setInitialDelay(500);
 210:         timer.start();
 211:       }
 212: 
 213:       public void mouseReleased(MouseEvent evt)
 214:       {
 215:         timer.stop();
 216:       }
 217: 
 218:       void increment()
 219:       {
 220:         Object next = BasicSpinnerUI.this.spinner.getNextValue();
 221:         if (next != null)
 222:           BasicSpinnerUI.this.spinner.getModel().setValue(next);
 223:       }
 224: 
 225:       volatile boolean mouseDown = false;
 226:       Timer timer = new Timer(50,
 227:                               new ActionListener()
 228:           {
 229:         public void actionPerformed(ActionEvent event)
 230:         {
 231:           increment();
 232:         }
 233:           });
 234:         });
 235:   }
 236: 
 237:   /*
 238:    * Install listeners to the previous button so that it decrements the model
 239:    */
 240:   protected void installPreviousButtonListeners(Component c)
 241:   {
 242:     c.addMouseListener(new MouseAdapter()
 243:         {
 244:       public void mousePressed(MouseEvent evt)
 245:       {
 246:         if (! spinner.isEnabled())
 247:           return;
 248:         decrement();
 249:         timer.setInitialDelay(500);
 250:         timer.start();
 251:       }
 252: 
 253:       public void mouseReleased(MouseEvent evt)
 254:       {
 255:         timer.stop();
 256:       }
 257: 
 258:       void decrement()
 259:       {
 260:         Object prev = BasicSpinnerUI.this.spinner.getPreviousValue();
 261:         if (prev != null)
 262:           BasicSpinnerUI.this.spinner.getModel().setValue(prev);
 263:       }
 264: 
 265:       volatile boolean mouseDown = false;
 266:       Timer timer = new Timer(50,
 267:                               new ActionListener()
 268:           {
 269:         public void actionPerformed(ActionEvent event)
 270:         {
 271:           decrement();
 272:         }
 273:           });
 274:         });
 275:   }
 276: 
 277:   /**
 278:    * Install this UI to the <code>JComponent</code>, which in reality, is a
 279:    * <code>JSpinner</code>. Calls <code>installDefaults</code>,
 280:    * <code>installListeners</code>, and also adds the buttons and editor.
 281:    *
 282:    * @param c DOCUMENT ME!
 283:    *
 284:    * @see #installDefaults
 285:    * @see #installListeners
 286:    * @see #createNextButton
 287:    * @see #createPreviousButton
 288:    * @see #createEditor
 289:    */
 290:   public void installUI(JComponent c)
 291:   {
 292:     super.installUI(c);
 293: 
 294:     spinner = (JSpinner) c;
 295: 
 296:     installDefaults();
 297:     installListeners();
 298: 
 299:     Component next = createNextButton();
 300:     Component previous = createPreviousButton();
 301: 
 302:     installNextButtonListeners(next);
 303:     installPreviousButtonListeners(previous);
 304: 
 305:     c.add(createEditor(), "Editor");
 306:     c.add(next, "Next");
 307:     c.add(previous, "Previous");
 308:   }
 309: 
 310:   /**
 311:    * Replace the old editor with the new one
 312:    *
 313:    * @param oldEditor the old editor
 314:    * @param newEditor the new one to replace with
 315:    */
 316:   protected void replaceEditor(JComponent oldEditor, JComponent newEditor)
 317:   {
 318:     spinner.remove(oldEditor);
 319:     spinner.add(newEditor);
 320:   }
 321: 
 322:   /**
 323:    * The reverse of <code>installDefaults</code>. Called by
 324:    * <code>uninstallUI</code>
 325:    */
 326:   protected void uninstallDefaults()
 327:   {
 328:     spinner.setLayout(null);
 329:   }
 330: 
 331:   /**
 332:    * The reverse of <code>installListeners</code>, called by
 333:    * <code>uninstallUI</code>
 334:    */
 335:   protected void uninstallListeners()
 336:   {
 337:     spinner.removePropertyChangeListener(listener);
 338:   }
 339: 
 340:   /**
 341:    * Called when the current L&F is replaced with another one, should call
 342:    * <code>uninstallDefaults</code> and <code>uninstallListeners</code> as
 343:    * well as remove the next/previous buttons and the editor
 344:    *
 345:    * @param c DOCUMENT ME!
 346:    */
 347:   public void uninstallUI(JComponent c)
 348:   {
 349:     super.uninstallUI(c);
 350: 
 351:     uninstallDefaults();
 352:     uninstallListeners();
 353:     c.removeAll();
 354:   }
 355: 
 356:   /** The spinner for this UI */
 357:   protected JSpinner spinner;
 358: 
 359:   /** DOCUMENT ME! */
 360:   private PropertyChangeListener listener = createPropertyChangeListener();
 361: 
 362:   /**
 363:    * DOCUMENT ME!
 364:    */
 365:   private class DefaultLayoutManager implements LayoutManager
 366:   {
 367:     /**
 368:      * DOCUMENT ME!
 369:      *
 370:      * @param parent DOCUMENT ME!
 371:      */
 372:     public void layoutContainer(Container parent)
 373:     {
 374:       synchronized (parent.getTreeLock())
 375:         {
 376:       Insets i = parent.getInsets();
 377:       boolean l2r = parent.getComponentOrientation().isLeftToRight();
 378:       /*
 379:         --------------    --------------
 380:         |        | n |    | n |        |
 381:         |   e    | - | or | - |   e    |
 382:         |        | p |    | p |        |
 383:         --------------    --------------
 384:       */
 385:       Dimension e = minSize(editor);
 386:       Dimension n = minSize(next);
 387:       Dimension p = minSize(previous);
 388:       Dimension s = spinner.getPreferredSize();
 389: 
 390:       int x = l2r ? i.left : i.right;
 391:       int y = i.top;
 392:       int w = Math.max(p.width, n.width);
 393:       int h = Math.max(p.height, n.height);
 394:       h = Math.max(h, e.height / 2);
 395:       int e_width = s.width - w;
 396: 
 397:       if (l2r)
 398:         {
 399:           setBounds(editor, x, y + (s.height - e.height) / 2, e_width,
 400:                     e.height);
 401:           x += e_width;
 402: 
 403:           setBounds(next, x, y, w, h);
 404:           y += h;
 405: 
 406:           setBounds(previous, x, y, w, h);
 407:         }
 408:       else
 409:         {
 410:           setBounds(next, x, y + (s.height - e.height) / 2, w, h);
 411:           y += h;
 412: 
 413:           setBounds(previous, x, y, w, h);
 414:           x += w;
 415:           y -= h;
 416: 
 417:           setBounds(editor, x, y, e_width, e.height);
 418:         }
 419:         }
 420:     }
 421: 
 422:     /**
 423:      * DOCUMENT ME!
 424:      *
 425:      * @param parent DOCUMENT ME!
 426:      *
 427:      * @return DOCUMENT ME!
 428:      */
 429:     public Dimension minimumLayoutSize(Container parent)
 430:     {
 431:       Dimension d = new Dimension();
 432: 
 433:       if (editor != null)
 434:         {
 435:       Dimension tmp = editor.getMinimumSize();
 436:       d.width += tmp.width;
 437:       d.height = tmp.height;
 438:         }
 439: 
 440:       int nextWidth = 0;
 441:       int previousWidth = 0;
 442:       int otherHeight = 0;
 443: 
 444:       if (next != null)
 445:         {
 446:       Dimension tmp = next.getMinimumSize();
 447:       nextWidth = tmp.width;
 448:       otherHeight += tmp.height;
 449:         }
 450:       if (previous != null)
 451:         {
 452:       Dimension tmp = previous.getMinimumSize();
 453:       previousWidth = tmp.width;
 454:       otherHeight += tmp.height;
 455:         }
 456: 
 457:       d.height = Math.max(d.height, otherHeight);
 458:       d.width += Math.max(nextWidth, previousWidth);
 459: 
 460:       return d;
 461:     }
 462: 
 463:     /**
 464:      * DOCUMENT ME!
 465:      *
 466:      * @param parent DOCUMENT ME!
 467:      *
 468:      * @return DOCUMENT ME!
 469:      */
 470:     public Dimension preferredLayoutSize(Container parent)
 471:     {
 472:       Dimension d = new Dimension();
 473: 
 474:       if (editor != null)
 475:         {
 476:       Dimension tmp = editor.getPreferredSize();
 477:       d.width += Math.max(tmp.width, 40);
 478:       d.height = tmp.height;
 479:         }
 480: 
 481:       int nextWidth = 0;
 482:       int previousWidth = 0;
 483:       int otherHeight = 0;
 484: 
 485:       if (next != null)
 486:         {
 487:       Dimension tmp = next.getPreferredSize();
 488:       nextWidth = tmp.width;
 489:       otherHeight += tmp.height;
 490:         }
 491:       if (previous != null)
 492:         {
 493:       Dimension tmp = previous.getPreferredSize();
 494:       previousWidth = tmp.width;
 495:       otherHeight += tmp.height;
 496:         }
 497: 
 498:       d.height = Math.max(d.height, otherHeight);
 499:       d.width += Math.max(nextWidth, previousWidth);
 500: 
 501:       return d;
 502:     }
 503: 
 504:     /**
 505:      * DOCUMENT ME!
 506:      *
 507:      * @param child DOCUMENT ME!
 508:      */
 509:     public void removeLayoutComponent(Component child)
 510:     {
 511:       if (child == editor)
 512:     editor = null;
 513:       else if (child == next)
 514:     next = null;
 515:       else if (previous == child)
 516:     previous = null;
 517:     }
 518: 
 519:     /**
 520:      * DOCUMENT ME!
 521:      *
 522:      * @param name DOCUMENT ME!
 523:      * @param child DOCUMENT ME!
 524:      */
 525:     public void addLayoutComponent(String name, Component child)
 526:     {
 527:       if ("Editor".equals(name))
 528:     editor = child;
 529:       else if ("Next".equals(name))
 530:     next = child;
 531:       else if ("Previous".equals(name))
 532:     previous = child;
 533:     }
 534: 
 535:     /**
 536:      * DOCUMENT ME!
 537:      *
 538:      * @param c DOCUMENT ME!
 539:      *
 540:      * @return DOCUMENT ME!
 541:      */
 542:     private Dimension minSize(Component c)
 543:     {
 544:       if (c == null)
 545:     return new Dimension();
 546:       else
 547:     return c.getMinimumSize();
 548:     }
 549: 
 550:     /**
 551:      * DOCUMENT ME!
 552:      *
 553:      * @param c DOCUMENT ME!
 554:      * @param x DOCUMENT ME!
 555:      * @param y DOCUMENT ME!
 556:      * @param w DOCUMENT ME!
 557:      * @param h DOCUMENT ME!
 558:      */
 559:     private void setBounds(Component c, int x, int y, int w, int h)
 560:     {
 561:       if (c != null)
 562:     c.setBounds(x, y, w, h);
 563:     }
 564: 
 565:     /** DOCUMENT ME! */
 566:     private Component editor;
 567: 
 568:     /** DOCUMENT ME! */
 569:     private Component next;
 570: 
 571:     /** DOCUMENT ME! */
 572:     private Component previous;
 573:   }
 574: }