Source for javax.swing.plaf.basic.BasicListUI

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