Source for javax.swing.plaf.basic.BasicListUI

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