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