Source for javax.swing.plaf.basic.BasicListUI

   1: /* BasicListUI.java --
   2:    Copyright (C) 2002, 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 gnu.classpath.NotImplementedException;
  42: 
  43: import java.awt.Component;
  44: import java.awt.Dimension;
  45: import java.awt.Graphics;
  46: import java.awt.Insets;
  47: import java.awt.Point;
  48: import java.awt.Rectangle;
  49: import java.awt.event.ActionEvent;
  50: import java.awt.event.ActionListener;
  51: import java.awt.event.FocusEvent;
  52: import java.awt.event.FocusListener;
  53: import java.awt.event.MouseEvent;
  54: import java.beans.PropertyChangeEvent;
  55: import java.beans.PropertyChangeListener;
  56: 
  57: import javax.swing.AbstractAction;
  58: import javax.swing.ActionMap;
  59: import javax.swing.CellRendererPane;
  60: import javax.swing.DefaultListSelectionModel;
  61: import javax.swing.InputMap;
  62: import javax.swing.JComponent;
  63: import javax.swing.JList;
  64: import javax.swing.KeyStroke;
  65: import javax.swing.ListCellRenderer;
  66: import javax.swing.ListModel;
  67: import javax.swing.ListSelectionModel;
  68: import javax.swing.LookAndFeel;
  69: import javax.swing.SwingUtilities;
  70: import javax.swing.UIDefaults;
  71: import javax.swing.UIManager;
  72: import javax.swing.event.ListDataEvent;
  73: import javax.swing.event.ListDataListener;
  74: import javax.swing.event.ListSelectionEvent;
  75: import javax.swing.event.ListSelectionListener;
  76: import javax.swing.event.MouseInputListener;
  77: import javax.swing.plaf.ActionMapUIResource;
  78: import javax.swing.plaf.ComponentUI;
  79: import javax.swing.plaf.InputMapUIResource;
  80: import javax.swing.plaf.ListUI;
  81: 
  82: /**
  83:  * The Basic Look and Feel UI delegate for the 
  84:  * JList.
  85:  */
  86: public class BasicListUI extends ListUI
  87: {
  88: 
  89:   /**
  90:    * A helper class which listens for {@link FocusEvent}s
  91:    * from the JList.
  92:    */
  93:   public class FocusHandler implements FocusListener
  94:   {
  95:     /**
  96:      * Called when the JList acquires focus.
  97:      *
  98:      * @param e The FocusEvent representing focus acquisition
  99:      */
 100:     public void focusGained(FocusEvent e)
 101:     {
 102:       repaintCellFocus();
 103:     }
 104: 
 105:     /**
 106:      * Called when the JList loses focus.
 107:      *
 108:      * @param e The FocusEvent representing focus loss
 109:      */
 110:     public void focusLost(FocusEvent e)
 111:     {
 112:       repaintCellFocus();
 113:     }
 114: 
 115:     /**
 116:      * Helper method to repaint the focused cell's 
 117:      * lost or acquired focus state.
 118:      */
 119:     protected void repaintCellFocus()
 120:     {
 121:       // TODO: Implement this properly.
 122:     }
 123:   }
 124: 
 125:   /**
 126:    * A helper class which listens for {@link ListDataEvent}s generated by
 127:    * the {@link JList}'s {@link ListModel}.
 128:    *
 129:    * @see javax.swing.JList#getModel()
 130:    */
 131:   public class ListDataHandler implements ListDataListener
 132:   {
 133:     /**
 134:      * Called when a general change has happened in the model which cannot
 135:      * be represented in terms of a simple addition or deletion.
 136:      *
 137:      * @param e The event representing the change
 138:      */
 139:     public void contentsChanged(ListDataEvent e)
 140:     {
 141:       updateLayoutStateNeeded |= modelChanged;
 142:       list.revalidate();
 143:     }
 144: 
 145:     /**
 146:      * Called when an interval of objects has been added to the model.
 147:      *
 148:      * @param e The event representing the addition
 149:      */
 150:     public void intervalAdded(ListDataEvent e)
 151:     {
 152:       updateLayoutStateNeeded |= modelChanged;
 153:       list.revalidate();
 154:     }
 155: 
 156:     /**
 157:      * Called when an inteval of objects has been removed from the model.
 158:      *
 159:      * @param e The event representing the removal
 160:      */
 161:     public void intervalRemoved(ListDataEvent e)
 162:     {
 163:       updateLayoutStateNeeded |= modelChanged;
 164:       list.revalidate();
 165:     }
 166:   }
 167: 
 168:   /**
 169:    * A helper class which listens for {@link ListSelectionEvent}s
 170:    * from the {@link JList}'s {@link ListSelectionModel}.
 171:    */
 172:   public class ListSelectionHandler implements ListSelectionListener
 173:   {
 174:     /**
 175:      * Called when the list selection changes.  
 176:      *
 177:      * @param e The event representing the change
 178:      */
 179:     public void valueChanged(ListSelectionEvent e)
 180:     {
 181:       int index1 = e.getFirstIndex();
 182:       int index2 = e.getLastIndex();
 183:       Rectangle damaged = getCellBounds(list, index1, index2);
 184:       if (damaged != null)
 185:         list.repaint(damaged);
 186:     }
 187:   }
 188: 
 189:   /**
 190:    * This class is used to mimmic the behaviour of the JDK when registering
 191:    * keyboard actions.  It is the same as the private class used in JComponent
 192:    * for the same reason.  This class receives an action event and dispatches
 193:    * it to the true receiver after altering the actionCommand property of the
 194:    * event.
 195:    */
 196:   private static class ActionListenerProxy
 197:     extends AbstractAction
 198:   {
 199:     ActionListener target;
 200:     String bindingCommandName;
 201: 
 202:     public ActionListenerProxy(ActionListener li, 
 203:                                String cmd)
 204:     {
 205:       target = li;
 206:       bindingCommandName = cmd;
 207:     }
 208: 
 209:     public void actionPerformed(ActionEvent e)
 210:     {
 211:       ActionEvent derivedEvent = new ActionEvent(e.getSource(),
 212:                                                  e.getID(),
 213:                                                  bindingCommandName,
 214:                                                  e.getModifiers());
 215:       target.actionPerformed(derivedEvent);
 216:     }
 217:   }
 218:   
 219:   class ListAction extends AbstractAction
 220:   {
 221:     public void actionPerformed (ActionEvent e)
 222:     {
 223:       int lead = list.getLeadSelectionIndex();
 224:       int max = list.getModel().getSize() - 1;
 225:       DefaultListSelectionModel selModel = (DefaultListSelectionModel)list.getSelectionModel();
 226:       String command = e.getActionCommand();
 227:       // Do nothing if list is empty
 228:       if (max == -1)
 229:         return;
 230:       
 231:       if (command.equals("selectNextRow"))
 232:         {
 233:           selectNextIndex();
 234:         }
 235:       else if (command.equals("selectPreviousRow"))
 236:         {
 237:           selectPreviousIndex();
 238:         }
 239:       else if (command.equals("clearSelection"))
 240:         {
 241:           list.clearSelection();
 242:         }
 243:       else if (command.equals("selectAll"))
 244:         {
 245:           list.setSelectionInterval(0, max);
 246:           // this next line is to restore the lead selection index to the old
 247:           // position, because select-all should not change the lead index
 248:           list.addSelectionInterval(lead, lead);
 249:         }
 250:       else if (command.equals("selectLastRow"))
 251:         {
 252:           list.setSelectedIndex(list.getModel().getSize() - 1); 
 253:         }
 254:       else if (command.equals("selectLastRowChangeLead"))
 255:         {
 256:           selModel.moveLeadSelectionIndex(list.getModel().getSize() - 1);
 257:         }
 258:       else if (command.equals("scrollDownExtendSelection"))
 259:         {
 260:           int target;
 261:           if (lead == list.getLastVisibleIndex())
 262:             {
 263:               target = Math.min
 264:                 (max, lead + (list.getLastVisibleIndex() -
 265:                     list.getFirstVisibleIndex() + 1));
 266:             }
 267:           else
 268:             target = list.getLastVisibleIndex();
 269:           selModel.setLeadSelectionIndex(target);
 270:         }
 271:       else if (command.equals("scrollDownChangeLead"))
 272:         {
 273:           int target;
 274:           if (lead == list.getLastVisibleIndex())
 275:             {
 276:               target = Math.min
 277:                 (max, lead + (list.getLastVisibleIndex() -
 278:                     list.getFirstVisibleIndex() + 1));
 279:             }
 280:           else
 281:             target = list.getLastVisibleIndex();
 282:           selModel.moveLeadSelectionIndex(target);
 283:         }
 284:       else if (command.equals("scrollUpExtendSelection"))
 285:         {
 286:           int target;
 287:           if (lead == list.getFirstVisibleIndex())
 288:             {
 289:               target = Math.max 
 290:                 (0, lead - (list.getLastVisibleIndex() - 
 291:                     list.getFirstVisibleIndex() + 1));
 292:             }
 293:           else
 294:             target = list.getFirstVisibleIndex();
 295:           selModel.setLeadSelectionIndex(target);
 296:         }
 297:       else if (command.equals("scrollUpChangeLead"))
 298:         {
 299:           int target;
 300:           if (lead == list.getFirstVisibleIndex())
 301:             {
 302:               target = Math.max 
 303:                 (0, lead - (list.getLastVisibleIndex() - 
 304:                     list.getFirstVisibleIndex() + 1));
 305:             }
 306:           else
 307:             target = list.getFirstVisibleIndex();
 308:           selModel.moveLeadSelectionIndex(target);
 309:         }
 310:       else if (command.equals("selectNextRowExtendSelection"))
 311:         {
 312:           selModel.setLeadSelectionIndex(Math.min(lead + 1,max));
 313:         }
 314:       else if (command.equals("selectFirstRow"))
 315:         {
 316:           list.setSelectedIndex(0);
 317:         }
 318:       else if (command.equals("selectFirstRowChangeLead"))
 319:           {
 320:             selModel.moveLeadSelectionIndex(0);
 321:           }
 322:       else if (command.equals("selectFirstRowExtendSelection"))
 323:         {
 324:           selModel.setLeadSelectionIndex(0);
 325:         }
 326:       else if (command.equals("selectPreviousRowExtendSelection"))
 327:         {
 328:           selModel.setLeadSelectionIndex(Math.max(0,lead - 1));
 329:         }
 330:       else if (command.equals("scrollUp"))
 331:         {
 332:           int target;
 333:           if (lead == list.getFirstVisibleIndex())
 334:             {
 335:               target = Math.max 
 336:                 (0, lead - (list.getLastVisibleIndex() - 
 337:                     list.getFirstVisibleIndex() + 1));
 338:             }
 339:           else
 340:             target = list.getFirstVisibleIndex();
 341:           list.setSelectedIndex(target);          
 342:         }
 343:       else if (command.equals("selectLastRowExtendSelection"))
 344:         {
 345:           selModel.setLeadSelectionIndex(list.getModel().getSize() - 1);
 346:         }
 347:       else if (command.equals("scrollDown"))
 348:         {
 349:           int target;
 350:           if (lead == list.getLastVisibleIndex())
 351:             {
 352:               target = Math.min
 353:                 (max, lead + (list.getLastVisibleIndex() -
 354:                     list.getFirstVisibleIndex() + 1));
 355:             }
 356:           else
 357:             target = list.getLastVisibleIndex();
 358:           list.setSelectedIndex(target);
 359:         }
 360:       else if (command.equals("selectNextRowChangeLead"))
 361:           {
 362:             if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
 363:               selectNextIndex();
 364:             else
 365:               {
 366:                 selModel.moveLeadSelectionIndex(Math.min(max, lead + 1));
 367:               }
 368:           }
 369:       else if (command.equals("selectPreviousRowChangeLead"))
 370:         {
 371:           if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
 372:             selectPreviousIndex();
 373:           else
 374:             {
 375:               selModel.moveLeadSelectionIndex(Math.max(0, lead - 1));
 376:             }
 377:         }      
 378:       else if (command.equals("addToSelection"))
 379:         {
 380:           list.addSelectionInterval(lead, lead);
 381:         }
 382:       else if (command.equals("extendTo"))
 383:         {
 384:           selModel.setSelectionInterval(selModel.getAnchorSelectionIndex(),
 385:                                         lead);
 386:         }
 387:       else if (command.equals("toggleAndAnchor"))
 388:         {
 389:           if (!list.isSelectedIndex(lead))
 390:             list.addSelectionInterval(lead, lead);
 391:           else
 392:             list.removeSelectionInterval(lead, lead);
 393:           selModel.setAnchorSelectionIndex(lead);
 394:         }
 395:       else 
 396:         {
 397:           // DEBUG: uncomment the following line to print out 
 398:           // key bindings that aren't implemented yet
 399:           
 400:           // System.out.println ("not implemented: "+e.getActionCommand());
 401:         }
 402:       
 403:       list.ensureIndexIsVisible(list.getLeadSelectionIndex());
 404:     }
 405:   }
 406:      
 407:   /**
 408:    * A helper class which listens for {@link MouseEvent}s 
 409:    * from the {@link JList}.
 410:    */
 411:   public class MouseInputHandler implements MouseInputListener
 412:   {
 413:     /**
 414:      * Called when a mouse button press/release cycle completes
 415:      * on the {@link JList}
 416:      *
 417:      * @param event The event representing the mouse click
 418:      */
 419:     public void mouseClicked(MouseEvent event)
 420:     {
 421:       Point click = event.getPoint();
 422:       int index = locationToIndex(list, click);
 423:       if (index == -1)
 424:         return;
 425:       if (event.isShiftDown())
 426:         {
 427:           if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)
 428:             list.setSelectedIndex(index);
 429:           else if (list.getSelectionMode() == 
 430:                    ListSelectionModel.SINGLE_INTERVAL_SELECTION)
 431:             // COMPAT: the IBM VM is compatible with the following line of code.
 432:             // However, compliance with Sun's VM would correspond to replacing 
 433:             // getAnchorSelectionIndex() with getLeadSelectionIndex().This is 
 434:             // both unnatural and contradictory to the way they handle other 
 435:             // similar UI interactions.
 436:             list.setSelectionInterval(list.getAnchorSelectionIndex(), index);
 437:           else
 438:             // COMPAT: both Sun and IBM are compatible instead with:
 439:             // list.setSelectionInterval
 440:             //     (list.getLeadSelectionIndex(),index);
 441:             // Note that for IBM this is contradictory to what they did in 
 442:             // the above situation for SINGLE_INTERVAL_SELECTION.  
 443:             // The most natural thing to do is the following:
 444:             if (list.isSelectedIndex(list.getAnchorSelectionIndex()))
 445:               list.getSelectionModel().setLeadSelectionIndex(index);
 446:             else
 447:               list.addSelectionInterval(list.getAnchorSelectionIndex(), index);
 448:         }
 449:       else if (event.isControlDown())
 450:         {
 451:           if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)
 452:             list.setSelectedIndex(index);
 453:           else if (list.isSelectedIndex(index))
 454:             list.removeSelectionInterval(index,index);
 455:           else
 456:             list.addSelectionInterval(index,index);
 457:         }
 458:       else
 459:         list.setSelectedIndex(index);
 460:       
 461:       list.ensureIndexIsVisible(list.getLeadSelectionIndex());
 462:     }
 463: 
 464:     /**
 465:      * Called when a mouse button is pressed down on the
 466:      * {@link JList}.
 467:      *
 468:      * @param event The event representing the mouse press
 469:      */
 470:     public void mousePressed(MouseEvent event)
 471:     {
 472:       // TODO: What should be done here, if anything?
 473:     }
 474: 
 475:     /**
 476:      * Called when a mouse button is released on
 477:      * the {@link JList}
 478:      *
 479:      * @param event The event representing the mouse press
 480:      */
 481:     public void mouseReleased(MouseEvent event)
 482:     {
 483:       // TODO: What should be done here, if anything?
 484:     }
 485: 
 486:     /**
 487:      * Called when the mouse pointer enters the area bounded
 488:      * by the {@link JList}
 489:      *
 490:      * @param event The event representing the mouse entry
 491:      */
 492:     public void mouseEntered(MouseEvent event)
 493:     {
 494:       // TODO: What should be done here, if anything?
 495:     }
 496: 
 497:     /**
 498:      * Called when the mouse pointer leaves the area bounded
 499:      * by the {@link JList}
 500:      *
 501:      * @param event The event representing the mouse exit
 502:      */
 503:     public void mouseExited(MouseEvent event)
 504:     {
 505:       // TODO: What should be done here, if anything?
 506:     }
 507: 
 508:     /**
 509:      * Called when the mouse pointer moves over the area bounded
 510:      * by the {@link JList} while a button is held down.
 511:      *
 512:      * @param event The event representing the mouse drag
 513:      */
 514:     public void mouseDragged(MouseEvent event)
 515:     {
 516:       Point click = event.getPoint();
 517:       int index = locationToIndex(list, click);
 518:       if (index == -1)
 519:         return;
 520:       if (!event.isShiftDown() && !event.isControlDown())
 521:         list.setSelectedIndex(index);
 522:       
 523:       list.ensureIndexIsVisible(list.getLeadSelectionIndex());
 524:     }
 525: 
 526:     /**
 527:      * Called when the mouse pointer moves over the area bounded
 528:      * by the {@link JList}.
 529:      *
 530:      * @param event The event representing the mouse move
 531:      */
 532:     public void mouseMoved(MouseEvent event)
 533:     {
 534:       // TODO: What should be done here, if anything?
 535:     }
 536:   }
 537: 
 538:   /**
 539:    * Helper class which listens to {@link PropertyChangeEvent}s
 540:    * from the {@link JList}.
 541:    */
 542:   public class PropertyChangeHandler implements PropertyChangeListener
 543:   {
 544:     /**
 545:      * Called when the {@link JList} changes one of its bound properties.
 546:      *
 547:      * @param e The event representing the property change
 548:      */
 549:     public void propertyChange(PropertyChangeEvent e)
 550:     {
 551:       if (e.getPropertyName().equals("model"))
 552:         {
 553:           if (e.getOldValue() != null && e.getOldValue() instanceof ListModel)
 554:             {
 555:               ListModel oldModel = (ListModel) e.getOldValue();
 556:               oldModel.removeListDataListener(listDataListener);
 557:             }
 558:           if (e.getNewValue() != null && e.getNewValue() instanceof ListModel)
 559:             {
 560:               ListModel newModel = (ListModel) e.getNewValue();
 561:               newModel.addListDataListener(BasicListUI.this.listDataListener);
 562:             }
 563: 
 564:           updateLayoutStateNeeded |= modelChanged;
 565:         }
 566:       else if (e.getPropertyName().equals("selectionModel"))
 567:         updateLayoutStateNeeded |= selectionModelChanged;
 568:       else if (e.getPropertyName().equals("font"))
 569:         updateLayoutStateNeeded |= fontChanged;
 570:       else if (e.getPropertyName().equals("fixedCellWidth"))
 571:         updateLayoutStateNeeded |= fixedCellWidthChanged;
 572:       else if (e.getPropertyName().equals("fixedCellHeight"))
 573:         updateLayoutStateNeeded |= fixedCellHeightChanged;
 574:       else if (e.getPropertyName().equals("prototypeCellValue"))
 575:         updateLayoutStateNeeded |= prototypeCellValueChanged;
 576:       else if (e.getPropertyName().equals("cellRenderer"))
 577:         updateLayoutStateNeeded |= cellRendererChanged;
 578:     }
 579:   }
 580: 
 581:   /**
 582:    * A constant to indicate that the model has changed.
 583:    */
 584:   protected static final int modelChanged = 1;
 585: 
 586:   /**
 587:    * A constant to indicate that the selection model has changed.
 588:    */
 589:   protected static final int selectionModelChanged = 2;
 590: 
 591:   /**
 592:    * A constant to indicate that the font has changed.
 593:    */
 594:   protected static final int fontChanged = 4;
 595: 
 596:   /**
 597:    * A constant to indicate that the fixedCellWidth has changed.
 598:    */
 599:   protected static final int fixedCellWidthChanged = 8;
 600: 
 601:   /**
 602:    * A constant to indicate that the fixedCellHeight has changed.
 603:    */
 604:   protected static final int fixedCellHeightChanged = 16;
 605: 
 606:   /**
 607:    * A constant to indicate that the prototypeCellValue has changed.
 608:    */
 609:   protected static final int prototypeCellValueChanged = 32;
 610: 
 611:   /**
 612:    * A constant to indicate that the cellRenderer has changed.
 613:    */
 614:   protected static final int cellRendererChanged = 64;
 615: 
 616:   /**
 617:    * Creates a new BasicListUI for the component.
 618:    *
 619:    * @param c The component to create a UI for
 620:    *
 621:    * @return A new UI
 622:    */
 623:   public static ComponentUI createUI(final JComponent c)
 624:   {
 625:     return new BasicListUI();
 626:   }
 627: 
 628:   /** The current focus listener. */
 629:   protected FocusListener focusListener;
 630: 
 631:   /** The data listener listening to the model. */
 632:   protected ListDataListener listDataListener;
 633: 
 634:   /** The selection listener listening to the selection model. */
 635:   protected ListSelectionListener listSelectionListener;
 636: 
 637:   /** The mouse listener listening to the list. */
 638:   protected MouseInputListener mouseInputListener;
 639: 
 640:   /** The property change listener listening to the list. */
 641:   protected PropertyChangeListener propertyChangeListener;
 642: 
 643:   /** Saved reference to the list this UI was created for. */
 644:   protected JList list;
 645: 
 646:   /**
 647:    * The height of a single cell in the list. This field is used when the
 648:    * fixedCellHeight property of the list is set. Otherwise this field is
 649:    * set to <code>-1</code> and {@link #cellHeights} is used instead.
 650:    */
 651:   protected int cellHeight;
 652: 
 653:   /** The width of a single cell in the list. */
 654:   protected int cellWidth;
 655: 
 656:   /** 
 657:    * An array of varying heights of cells in the list, in cases where each
 658:    * cell might have a different height. This field is used when the
 659:    * <code>fixedCellHeight</code> property of the list is not set. Otherwise
 660:    * this field is <code>null</code> and {@link #cellHeight} is used.
 661:    */
 662:   protected int[] cellHeights;
 663: 
 664:   /**
 665:    * A bitmask that indicates which properties of the JList have changed.
 666:    * When nonzero, indicates that the UI class is out of
 667:    * date with respect to the underlying list, and must recalculate the
 668:    * list layout before painting or performing size calculations.
 669:    *
 670:    * @see #modelChanged
 671:    * @see #selectionModelChanged
 672:    * @see #fontChanged
 673:    * @see #fixedCellWidthChanged
 674:    * @see #fixedCellHeightChanged
 675:    * @see #prototypeCellValueChanged
 676:    * @see #cellRendererChanged
 677:    */
 678:   protected int updateLayoutStateNeeded;
 679: 
 680:   /**
 681:    * The {@link CellRendererPane} that is used for painting.
 682:    */
 683:   protected CellRendererPane rendererPane;
 684:   
 685:   /** The action bound to KeyStrokes. */
 686:   ListAction action;
 687: 
 688:   /**
 689:    * Calculate the height of a particular row. If there is a fixed {@link
 690:    * #cellHeight}, return it; otherwise return the specific row height
 691:    * requested from the {@link #cellHeights} array. If the requested row
 692:    * is invalid, return <code>-1</code>.
 693:    *
 694:    * @param row The row to get the height of
 695:    *
 696:    * @return The height, in pixels, of the specified row
 697:    */
 698:   protected int getRowHeight(int row)
 699:   {
 700:     int height;
 701:     if (cellHeights == null)
 702:       height = cellHeight;
 703:     else
 704:       {
 705:         if (row < 0 || row >= cellHeights.length)
 706:           height = -1;
 707:         else
 708:           height = cellHeights[row];
 709:       }
 710:     return height;
 711:   }
 712: 
 713:   /**
 714:    * Calculate the bounds of a particular cell, considering the upper left
 715:    * corner of the list as the origin position <code>(0,0)</code>.
 716:    *
 717:    * @param l Ignored; calculates over <code>this.list</code>
 718:    * @param index1 The first row to include in the bounds
 719:    * @param index2 The last row to incude in the bounds
 720:    *
 721:    * @return A rectangle encompassing the range of rows between 
 722:    * <code>index1</code> and <code>index2</code> inclusive, or null
 723:    * such a rectangle couldn't be calculated for the given indexes.
 724:    */
 725:   public Rectangle getCellBounds(JList l, int index1, int index2)
 726:   {
 727:     maybeUpdateLayoutState();
 728: 
 729:     if (l != list || cellWidth == -1)
 730:       return null;
 731: 
 732:     int minIndex = Math.min(index1, index2);
 733:     int maxIndex = Math.max(index1, index2);
 734:     Point loc = indexToLocation(list, minIndex);
 735: 
 736:     // When the layoutOrientation is VERTICAL, then the width == the list
 737:     // width. Otherwise the cellWidth field is used.
 738:     int width = cellWidth;
 739:     if (l.getLayoutOrientation() == JList.VERTICAL)
 740:       width = l.getWidth();
 741: 
 742:     Rectangle bounds = new Rectangle(loc.x, loc.y, width,
 743:                                      getCellHeight(minIndex));
 744:     for (int i = minIndex + 1; i <= maxIndex; i++)
 745:       {
 746:         Point hiLoc = indexToLocation(list, i);
 747:         bounds = SwingUtilities.computeUnion(hiLoc.x, hiLoc.y, width,
 748:                                              getCellHeight(i), bounds);
 749:       }
 750: 
 751:     return bounds;
 752:   }
 753: 
 754:   /**
 755:    * Calculates the maximum cell height.
 756:    *
 757:    * @param index the index of the cell
 758:    *
 759:    * @return the maximum cell height
 760:    */
 761:   private int getCellHeight(int index)
 762:   {
 763:     int height = cellHeight;
 764:     if (height <= 0)
 765:       {
 766:         if (list.getLayoutOrientation() == JList.VERTICAL)
 767:           height = getRowHeight(index);
 768:         else
 769:           {
 770:             for (int j = 0; j < cellHeights.length; j++)
 771:               height = Math.max(height, cellHeights[j]);
 772:           }
 773:       }
 774:     return height;
 775:   }
 776: 
 777:   /**
 778:    * Calculate the Y coordinate of the upper edge of a particular row,
 779:    * considering the Y coordinate <code>0</code> to occur at the top of the
 780:    * list.
 781:    *
 782:    * @param row The row to calculate the Y coordinate of
 783:    *
 784:    * @return The Y coordinate of the specified row, or <code>-1</code> if
 785:    * the specified row number is invalid
 786:    */
 787:   protected int convertRowToY(int row)
 788:   {
 789:     int y = 0;
 790:     for (int i = 0; i < row; ++i)
 791:       {
 792:         int h = getRowHeight(i);
 793:         if (h == -1)
 794:           return -1;
 795:         y += h;
 796:       }
 797:     return y;
 798:   }
 799: 
 800:   /**
 801:    * Calculate the row number containing a particular Y coordinate,
 802:    * considering the Y coodrinate <code>0</code> to occur at the top of the
 803:    * list.
 804:    *
 805:    * @param y0 The Y coordinate to calculate the row number for
 806:    *
 807:    * @return The row number containing the specified Y value, or <code>-1</code>
 808:    *         if the list model is empty
 809:    *
 810:    * @specnote This method is specified to return -1 for an invalid Y
 811:    *           coordinate. However, some simple tests show that the behaviour
 812:    *           is to return the index of the last list element for an Y
 813:    *           coordinate that lies outside of the list bounds (even for
 814:    *           negative indices). <code>-1</code>
 815:    *           is only returned if the list model is empty.
 816:    */
 817:   protected int convertYToRow(int y0)
 818:   {
 819:     if (list.getModel().getSize() == 0)
 820:       return -1;
 821: 
 822:     // When y0 < 0, then the JDK returns the maximum row index of the list. So
 823:     // do we.
 824:     if (y0 < 0)
 825:       return list.getModel().getSize() - 1;
 826: 
 827:     // Update the layout if necessary.
 828:     maybeUpdateLayoutState();
 829: 
 830:     int index = list.getModel().getSize() - 1;
 831: 
 832:     // If a fixed cell height is set, then we can work more efficient.
 833:     if (cellHeight > 0)
 834:       index = Math.min(y0 / cellHeight, index);
 835:     // If we have no fixed cell height, we must add up each cell height up
 836:     // to y0.
 837:     else
 838:       {
 839:         int h = 0;
 840:         for (int row = 0; row < cellHeights.length; ++row)
 841:           {
 842:             h += cellHeights[row];
 843:             if (y0 < h)
 844:               {
 845:                 index = row;
 846:                 break;
 847:               }
 848:           }
 849:       }
 850:     return index;
 851:   }
 852: 
 853:   /**
 854:    * Recomputes the {@link #cellHeights}, {@link #cellHeight}, and {@link
 855:    * #cellWidth} properties by examining the variouis properties of the
 856:    * {@link JList}.
 857:    */
 858:   protected void updateLayoutState()
 859:   {
 860:     int nrows = list.getModel().getSize();
 861:     cellHeight = -1;
 862:     cellWidth = -1;
 863:     if (cellHeights == null || cellHeights.length != nrows)
 864:       cellHeights = new int[nrows];
 865:     ListCellRenderer rend = list.getCellRenderer();
 866:     // Update the cellHeight(s) fields.
 867:     int fixedCellHeight = list.getFixedCellHeight();
 868:     if (fixedCellHeight > 0)
 869:       {
 870:         cellHeight = fixedCellHeight;
 871:         cellHeights = null;
 872:       }
 873:     else
 874:       {
 875:         cellHeight = -1;
 876:         for (int i = 0; i < nrows; ++i)
 877:           {
 878:             Component flyweight =
 879:               rend.getListCellRendererComponent(list,
 880:                       list.getModel().getElementAt(i),
 881:                       i, list.isSelectedIndex(i),
 882:                       list.getSelectionModel().getAnchorSelectionIndex() == i);
 883:             Dimension dim = flyweight.getPreferredSize();
 884:             cellHeights[i] = dim.height;
 885:           }
 886:       }
 887: 
 888:     // Update the cellWidth field.
 889:     int fixedCellWidth = list.getFixedCellWidth();
 890:     if (fixedCellWidth > 0)
 891:       cellWidth = fixedCellWidth;
 892:     else
 893:       {
 894:         for (int i = 0; i < nrows; ++i)
 895:           {
 896:             Component flyweight =
 897:               rend.getListCellRendererComponent(list,
 898:                                                 list.getModel().getElementAt(i),
 899:                                                 i, list.isSelectedIndex(i),
 900:                                                 list.getSelectionModel().getAnchorSelectionIndex() == i);
 901:             Dimension dim = flyweight.getPreferredSize();
 902:             cellWidth = Math.max(cellWidth, dim.width);
 903:           }
 904:       }
 905:   }
 906: 
 907:   /**
 908:    * Calls {@link #updateLayoutState} if {@link #updateLayoutStateNeeded}
 909:    * is nonzero, then resets {@link #updateLayoutStateNeeded} to zero.
 910:    */
 911:   protected void maybeUpdateLayoutState()
 912:   {
 913:     if (updateLayoutStateNeeded != 0)
 914:       {
 915:         updateLayoutState();
 916:         updateLayoutStateNeeded = 0;
 917:       }
 918:   }
 919: 
 920:   /**
 921:    * Creates a new BasicListUI object.
 922:    */
 923:   public BasicListUI()
 924:   {
 925:     updateLayoutStateNeeded = 1;
 926:     rendererPane = new CellRendererPane();
 927:   }
 928: 
 929:   /**
 930:    * Installs various default settings (mostly colors) from the {@link
 931:    * UIDefaults} into the {@link JList}
 932:    *
 933:    * @see #uninstallDefaults
 934:    */
 935:   protected void installDefaults()
 936:   {
 937:     LookAndFeel.installColorsAndFont(list, "List.background",
 938:                                      "List.foreground", "List.font");
 939:     list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
 940:     list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
 941:     list.setOpaque(true);
 942:   }
 943: 
 944:   /**
 945:    * Resets to <code>null</code> those defaults which were installed in 
 946:    * {@link #installDefaults}
 947:    */
 948:   protected void uninstallDefaults()
 949:   {
 950:     list.setForeground(null);
 951:     list.setBackground(null);
 952:     list.setSelectionForeground(null);
 953:     list.setSelectionBackground(null);
 954:   }
 955: 
 956:   /**
 957:    * Attaches all the listeners we have in the UI class to the {@link
 958:    * JList}, its model and its selection model.
 959:    *
 960:    * @see #uninstallListeners
 961:    */
 962:   protected void installListeners()
 963:   {
 964:     if (focusListener == null)
 965:       focusListener = createFocusListener();
 966:     list.addFocusListener(focusListener);
 967:     if (listDataListener == null)
 968:       listDataListener = createListDataListener();
 969:     list.getModel().addListDataListener(listDataListener);
 970:     if (listSelectionListener == null)
 971:       listSelectionListener = createListSelectionListener();
 972:     list.addListSelectionListener(listSelectionListener);
 973:     if (mouseInputListener == null)
 974:       mouseInputListener = createMouseInputListener();
 975:     list.addMouseListener(mouseInputListener);
 976:     list.addMouseMotionListener(mouseInputListener);
 977:     if (propertyChangeListener == null)
 978:       propertyChangeListener = createPropertyChangeListener();
 979:     list.addPropertyChangeListener(propertyChangeListener);
 980:   }
 981: 
 982:   /**
 983:    * Detaches all the listeners we attached in {@link #installListeners}.
 984:    */
 985:   protected void uninstallListeners()
 986:   {
 987:     list.removeFocusListener(focusListener);
 988:     list.getModel().removeListDataListener(listDataListener);
 989:     list.removeListSelectionListener(listSelectionListener);
 990:     list.removeMouseListener(mouseInputListener);
 991:     list.removeMouseMotionListener(mouseInputListener);
 992:     list.removePropertyChangeListener(propertyChangeListener);
 993:   }
 994:   
 995:   /**
 996:    * Installs keyboard actions for this UI in the {@link JList}.
 997:    */
 998:   protected void installKeyboardActions()
 999:   {
1000:     InputMap focusInputMap = (InputMap) UIManager.get("List.focusInputMap");
1001:     InputMapUIResource parentInputMap = new InputMapUIResource();
1002:     // FIXME: The JDK uses a LazyActionMap for parentActionMap
1003:     ActionMap parentActionMap = new ActionMapUIResource();
1004:     action = new ListAction();
1005:     Object keys[] = focusInputMap.allKeys();
1006:     // Register key bindings in the UI InputMap-ActionMap pair
1007:     for (int i = 0; i < keys.length; i++)
1008:       {
1009:         KeyStroke stroke = (KeyStroke)keys[i];
1010:         String actionString = (String) focusInputMap.get(stroke);
1011:         parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(),
1012:                                                   stroke.getModifiers()),
1013:                            actionString);
1014: 
1015:         parentActionMap.put (actionString, 
1016:                              new ActionListenerProxy(action, actionString));
1017:       }
1018:     // Register the new InputMap-ActionMap as the parents of the list's
1019:     // InputMap and ActionMap
1020:     parentInputMap.setParent(list.getInputMap().getParent());
1021:     parentActionMap.setParent(list.getActionMap().getParent());
1022:     list.getInputMap().setParent(parentInputMap);
1023:     list.getActionMap().setParent(parentActionMap);
1024:   }
1025: 
1026:   /**
1027:    * Uninstalls keyboard actions for this UI in the {@link JList}.
1028:    */
1029:   protected void uninstallKeyboardActions()
1030:     throws NotImplementedException
1031:   {
1032:     // TODO: Implement this properly.
1033:   }
1034: 
1035:   /**
1036:    * Installs the various aspects of the UI in the {@link JList}. In
1037:    * particular, calls {@link #installDefaults}, {@link #installListeners}
1038:    * and {@link #installKeyboardActions}. Also saves a reference to the
1039:    * provided component, cast to a {@link JList}.
1040:    *
1041:    * @param c The {@link JList} to install the UI into
1042:    */
1043:   public void installUI(final JComponent c)
1044:   {
1045:     super.installUI(c);
1046:     list = (JList) c;
1047:     installDefaults();
1048:     installListeners();
1049:     installKeyboardActions();
1050:     maybeUpdateLayoutState();
1051:   }
1052: 
1053:   /**
1054:    * Uninstalls all the aspects of the UI which were installed in {@link
1055:    * #installUI}. When finished uninstalling, drops the saved reference to
1056:    * the {@link JList}.
1057:    *
1058:    * @param c Ignored; the UI is uninstalled from the {@link JList}
1059:    * reference saved during the call to {@link #installUI}
1060:    */
1061:   public void uninstallUI(final JComponent c)
1062:   {
1063:     uninstallKeyboardActions();
1064:     uninstallListeners();
1065:     uninstallDefaults();
1066:     list = null;
1067:   }
1068: 
1069:   /**
1070:    * Gets the size this list would prefer to assume. This is calculated by
1071:    * calling {@link #getCellBounds} over the entire list.
1072:    *
1073:    * @param c Ignored; uses the saved {@link JList} reference 
1074:    *
1075:    * @return DOCUMENT ME!
1076:    */
1077:   public Dimension getPreferredSize(JComponent c)
1078:   {
1079:     maybeUpdateLayoutState();
1080:     int size = list.getModel().getSize();
1081:     int visibleRows = list.getVisibleRowCount();
1082:     int layoutOrientation = list.getLayoutOrientation();
1083: 
1084:     int h;
1085:     int w;
1086:     int maxCellHeight = cellHeight;
1087:     if (maxCellHeight <= 0)
1088:       {
1089:         for (int i = 0; i < cellHeights.length; i++)
1090:           maxCellHeight = Math.max(maxCellHeight, cellHeights[i]);
1091:       }
1092:     if (layoutOrientation == JList.HORIZONTAL_WRAP)
1093:       {
1094:         if (visibleRows > 0)
1095:           {
1096:             // We cast to double here to force double divisions.
1097:             double modelSize = size;
1098:             int neededColumns = (int) Math.ceil(modelSize / visibleRows); 
1099:             int adjustedRows = (int) Math.ceil(modelSize / neededColumns);
1100:             h = maxCellHeight * adjustedRows;
1101:             w = cellWidth * neededColumns;
1102:           }
1103:         else
1104:           {
1105:             int neededColumns = Math.min(1, list.getWidth() / cellWidth);
1106:             h = size / neededColumns * maxCellHeight;
1107:             w = neededColumns * cellWidth;
1108:           }
1109:       }
1110:     else if (layoutOrientation == JList.VERTICAL_WRAP)
1111:       {
1112:         if (visibleRows > 0)
1113:           h = visibleRows * maxCellHeight;
1114:         else
1115:           h = Math.max(list.getHeight(), maxCellHeight);
1116:         int neededColumns = h / maxCellHeight;
1117:         w = cellWidth * neededColumns;
1118:       }
1119:     else
1120:       {
1121:         if (list.getFixedCellWidth() > 0)
1122:           w = list.getFixedCellWidth();
1123:         else
1124:           w = cellWidth;
1125:         if (list.getFixedCellHeight() > 0)
1126:           // FIXME: We need to add some cellVerticalMargins here, according
1127:           // to the specs.
1128:           h = list.getFixedCellHeight() * size;
1129:         else
1130:           h = maxCellHeight * size;
1131:       }
1132:     Insets insets = list.getInsets();
1133:     Dimension retVal = new Dimension(w + insets.left + insets.right,
1134:                                      h + insets.top + insets.bottom);
1135:     return retVal;
1136:   }
1137: 
1138:   /**
1139:    * Paints a single cell in the list.
1140:    *
1141:    * @param g The graphics context to paint in
1142:    * @param row The row number to paint
1143:    * @param bounds The bounds of the cell to paint, assuming a coordinate
1144:    * system beginning at <code>(0,0)</code> in the upper left corner of the
1145:    * list
1146:    * @param rend A cell renderer to paint with
1147:    * @param data The data to provide to the cell renderer
1148:    * @param sel A selection model to provide to the cell renderer
1149:    * @param lead The lead selection index of the list
1150:    */
1151:   protected void paintCell(Graphics g, int row, Rectangle bounds,
1152:                  ListCellRenderer rend, ListModel data,
1153:                  ListSelectionModel sel, int lead)
1154:   {
1155:     boolean isSel = list.isSelectedIndex(row);
1156:     boolean hasFocus = (list.getLeadSelectionIndex() == row) && BasicListUI.this.list.hasFocus();
1157:     Component comp = rend.getListCellRendererComponent(list,
1158:                                                        data.getElementAt(row),
1159:                                                        0, isSel, hasFocus);
1160:     rendererPane.paintComponent(g, comp, list, bounds);
1161:   }
1162: 
1163:   /**
1164:    * Paints the list by repeatedly calling {@link #paintCell} for each visible
1165:    * cell in the list.
1166:    *
1167:    * @param g The graphics context to paint with
1168:    * @param c Ignored; uses the saved {@link JList} reference 
1169:    */
1170:   public void paint(Graphics g, JComponent c)
1171:   {
1172:     int nrows = list.getModel().getSize();
1173:     if (nrows == 0)
1174:       return;
1175: 
1176:     maybeUpdateLayoutState();
1177:     ListCellRenderer render = list.getCellRenderer();
1178:     ListModel model = list.getModel();
1179:     ListSelectionModel sel = list.getSelectionModel();
1180:     int lead = sel.getLeadSelectionIndex();
1181:     Rectangle clip = g.getClipBounds();
1182: 
1183:     int startIndex = locationToIndex(list, new Point(clip.x, clip.y));
1184:     int endIndex = locationToIndex(list, new Point(clip.x + clip.width,
1185:                                              clip.y + clip.height));
1186:     
1187:     for (int row = startIndex; row <= endIndex; ++row)
1188:       {
1189:         Rectangle bounds = getCellBounds(list, row, row);
1190:         if (bounds != null && bounds.intersects(clip))
1191:           paintCell(g, row, bounds, render, model, sel, lead);
1192:       }
1193:   }
1194: 
1195:   /**
1196:    * Computes the index of a list cell given a point within the list. If the
1197:    * location lies outside the bounds of the list, the greatest index in the
1198:    * list model is returned.
1199:    *
1200:    * @param l the list which on which the computation is based on
1201:    * @param location the coordinates
1202:    *
1203:    * @return the index of the list item that is located at the given
1204:    *         coordinates or <code>-1</code> if the list model is empty
1205:    */
1206:   public int locationToIndex(JList l, Point location)
1207:   {
1208:     int layoutOrientation = list.getLayoutOrientation();
1209:     int index = -1;
1210:     switch (layoutOrientation)
1211:       {
1212:       case JList.VERTICAL:
1213:         index = convertYToRow(location.y);
1214:         break;
1215:       case JList.HORIZONTAL_WRAP:
1216:         // determine visible rows and cells per row
1217:         int maxCellHeight = getCellHeight(0);
1218:         int visibleRows = list.getHeight() / maxCellHeight;
1219:         int cellsPerRow = -1;
1220:         int numberOfItems = list.getModel().getSize();
1221:         cellsPerRow = numberOfItems / visibleRows + 1;
1222: 
1223:         // determine index for the given location
1224:         int cellsPerColumn = numberOfItems / cellsPerRow + 1;
1225:         int gridX = Math.min(location.x / cellWidth, cellsPerRow - 1);
1226:         int gridY = Math.min(location.y / maxCellHeight, cellsPerColumn);
1227:         index = gridX + gridY * cellsPerRow;
1228:         break;
1229:       case JList.VERTICAL_WRAP:
1230:         // determine visible rows and cells per column
1231:         int maxCellHeight2 = getCellHeight(0);
1232:         int visibleRows2 = list.getHeight() / maxCellHeight2;
1233:         int numberOfItems2 = list.getModel().getSize();
1234:         int cellsPerRow2 = numberOfItems2 / visibleRows2 + 1;
1235: 
1236:         int gridX2 = Math.min(location.x / cellWidth, cellsPerRow2 - 1);
1237:         int gridY2 = Math.min(location.y / maxCellHeight2, visibleRows2);
1238:         index = gridY2 + gridX2 * visibleRows2;
1239:         break;
1240:       }
1241:     return index;
1242:   }
1243: 
1244:   public Point indexToLocation(JList l, int index)
1245:   {
1246:     int layoutOrientation = list.getLayoutOrientation();
1247:     Point loc = null;
1248:     switch (layoutOrientation)
1249:       {
1250:       case JList.VERTICAL:
1251:         loc = new Point(0, convertRowToY(index));
1252:         break;
1253:       case JList.HORIZONTAL_WRAP:
1254:         // determine visible rows and cells per row
1255:         int maxCellHeight = getCellHeight(0);
1256:         int visibleRows = list.getHeight() / maxCellHeight;
1257:         int numberOfCellsPerRow = -1;
1258:         int numberOfItems = list.getModel().getSize();
1259:         numberOfCellsPerRow = numberOfItems / visibleRows + 1;
1260: 
1261:         // compute coordinates inside the grid
1262:         int gridX = index % numberOfCellsPerRow;
1263:         int gridY = index / numberOfCellsPerRow;
1264:         int locX = gridX * cellWidth;
1265:         int locY;
1266:         locY = gridY * maxCellHeight;
1267:         loc = new Point(locX, locY);
1268:         break;
1269:       case JList.VERTICAL_WRAP:
1270:         // determine visible rows and cells per column
1271:         int maxCellHeight2 = getCellHeight(0);
1272:         int visibleRows2 = list.getHeight() / maxCellHeight2;
1273:         // compute coordinates inside the grid
1274:         if (visibleRows2 > 0)
1275:           {
1276:             int gridY2 = index % visibleRows2;
1277:             int gridX2 = index / visibleRows2;
1278:             int locX2 = gridX2 * cellWidth;
1279:             int locY2 = gridY2 * maxCellHeight2;
1280:             loc = new Point(locX2, locY2);
1281:           }
1282:         else
1283:           loc = new Point(0, convertRowToY(index));
1284:         break;
1285:       }
1286:     return loc;
1287:   }
1288: 
1289:   /**
1290:    * Creates and returns the focus listener for this UI.
1291:    *
1292:    * @return the focus listener for this UI
1293:    */
1294:   protected FocusListener createFocusListener()
1295:   {
1296:     return new FocusHandler();
1297:   }
1298: 
1299:   /**
1300:    * Creates and returns the list data listener for this UI.
1301:    *
1302:    * @return the list data listener for this UI
1303:    */
1304:   protected ListDataListener createListDataListener()
1305:   {
1306:     return new ListDataHandler();
1307:   }
1308: 
1309:   /**
1310:    * Creates and returns the list selection listener for this UI.
1311:    *
1312:    * @return the list selection listener for this UI
1313:    */
1314:   protected ListSelectionListener createListSelectionListener()
1315:   {
1316:     return new ListSelectionHandler();
1317:   }
1318: 
1319:   /**
1320:    * Creates and returns the mouse input listener for this UI.
1321:    *
1322:    * @return the mouse input listener for this UI
1323:    */
1324:   protected MouseInputListener createMouseInputListener()
1325:   {
1326:     return new MouseInputHandler();
1327:   }
1328: 
1329:   /**
1330:    * Creates and returns the property change listener for this UI.
1331:    *
1332:    * @return the property change listener for this UI
1333:    */
1334:   protected PropertyChangeListener createPropertyChangeListener()
1335:   {
1336:     return new PropertyChangeHandler();
1337:   }
1338: 
1339:   /**
1340:    * Selects the next list item and force it to be visible.
1341:    */
1342:   protected void selectNextIndex()
1343:   {
1344:     int index = list.getSelectionModel().getLeadSelectionIndex();
1345:     if (index < list.getModel().getSize() - 1)
1346:       {
1347:         index++;
1348:         list.setSelectedIndex(index);
1349:       }
1350:     list.ensureIndexIsVisible(index);
1351:   }
1352: 
1353:   /**
1354:    * Selects the previous list item and force it to be visible.
1355:    */
1356:   protected void selectPreviousIndex()
1357:   {
1358:     int index = list.getSelectionModel().getLeadSelectionIndex();
1359:     if (index > 0)
1360:       {
1361:         index--;
1362:         list.setSelectedIndex(index);
1363:       }
1364:     list.ensureIndexIsVisible(index);
1365:   }
1366: }