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