Source for javax.swing.plaf.basic.BasicListUI

   1: /* BasicListUI.java --
   2:    Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.plaf.basic;
  40: 
  41: import java.awt.Color;
  42: import java.awt.Component;
  43: import java.awt.Dimension;
  44: import java.awt.Graphics;
  45: import java.awt.Point;
  46: import java.awt.Rectangle;
  47: import java.awt.event.ComponentAdapter;
  48: import java.awt.event.ComponentEvent;
  49: import java.awt.event.ComponentListener;
  50: import java.awt.event.FocusEvent;
  51: import java.awt.event.FocusListener;
  52: import java.awt.event.KeyAdapter;
  53: import java.awt.event.KeyEvent;
  54: import java.awt.event.MouseEvent;
  55: import java.beans.PropertyChangeEvent;
  56: import java.beans.PropertyChangeListener;
  57: 
  58: import javax.swing.CellRendererPane;
  59: import javax.swing.JComponent;
  60: import javax.swing.JList;
  61: import javax.swing.JViewport;
  62: import javax.swing.ListCellRenderer;
  63: import javax.swing.ListModel;
  64: import javax.swing.ListSelectionModel;
  65: import javax.swing.UIDefaults;
  66: import javax.swing.UIManager;
  67: import javax.swing.event.ListDataEvent;
  68: import javax.swing.event.ListDataListener;
  69: import javax.swing.event.ListSelectionEvent;
  70: import javax.swing.event.ListSelectionListener;
  71: import javax.swing.event.MouseInputListener;
  72: import javax.swing.plaf.ComponentUI;
  73: import javax.swing.plaf.ListUI;
  74: 
  75: /**
  76:  * The Basic Look and Feel UI delegate for the 
  77:  * JList.
  78:  */
  79: public class BasicListUI extends ListUI
  80: {
  81: 
  82:   /**
  83:    * A helper class which listens for {@link ComponentEvent}s from
  84:    * the JList.
  85:    */
  86:   private class ComponentHandler extends ComponentAdapter {
  87: 
  88:     /**
  89:      * Called when the component is hidden. Invalidates the internal
  90:      * layout.
  91:      */
  92:     public void componentResized(ComponentEvent ev) {
  93:       BasicListUI.this.damageLayout();
  94:     }
  95:   }
  96: 
  97:   /**
  98:    * A helper class which listens for {@link FocusEvent}s
  99:    * from the JList.
 100:    */
 101:   public class FocusHandler implements FocusListener
 102:   {
 103:     /**
 104:      * Called when the JList acquires focus.
 105:      *
 106:      * @param e The FocusEvent representing focus acquisition
 107:      */
 108:     public void focusGained(FocusEvent e)
 109:     {
 110:       repaintCellFocus();
 111:     }
 112: 
 113:     /**
 114:      * Called when the JList loses focus.
 115:      *
 116:      * @param e The FocusEvent representing focus loss
 117:      */
 118:     public void focusLost(FocusEvent e)
 119:     {
 120:       repaintCellFocus();
 121:     }
 122: 
 123:     /**
 124:      * Helper method to repaint the focused cell's 
 125:      * lost or acquired focus state.
 126:      */
 127:     void repaintCellFocus()
 128:     {
 129:     }
 130:   }
 131: 
 132:   /**
 133:    * A helper class which listens for {@link ListDataEvent}s generated by
 134:    * the {@link JList}'s {@link ListModel}.
 135:    *
 136:    * @see javax.swing.JList#getModel()
 137:    */
 138:   public class ListDataHandler implements ListDataListener
 139:   {
 140:     /**
 141:      * Called when a general change has happened in the model which cannot
 142:      * be represented in terms of a simple addition or deletion.
 143:      *
 144:      * @param e The event representing the change
 145:      */
 146:     public void contentsChanged(ListDataEvent e)
 147:     {
 148:       BasicListUI.this.damageLayout();
 149:     }
 150: 
 151:     /**
 152:      * Called when an interval of objects has been added to the model.
 153:      *
 154:      * @param e The event representing the addition
 155:      */
 156:     public void intervalAdded(ListDataEvent e)
 157:     {
 158:       BasicListUI.this.damageLayout();
 159:     }
 160: 
 161:     /**
 162:      * Called when an inteval of objects has been removed from the model.
 163:      *
 164:      * @param e The event representing the removal
 165:      */
 166:     public void intervalRemoved(ListDataEvent e)
 167:     {
 168:       BasicListUI.this.damageLayout();
 169:     }
 170:   }
 171: 
 172:   /**
 173:    * A helper class which listens for {@link ListSelectionEvent}s
 174:    * from the {@link JList}'s {@link ListSelectionModel}.
 175:    */
 176:   public class ListSelectionHandler implements ListSelectionListener
 177:   {
 178:     /**
 179:      * Called when the list selection changes.  
 180:      *
 181:      * @param e The event representing the change
 182:      */
 183:     public void valueChanged(ListSelectionEvent e)
 184:     {
 185:     }
 186:   }
 187: 
 188:   /**
 189:    * A helper class which listens for {@link KeyEvents}s 
 190:    * from the {@link JList}.
 191:    */
 192:   private class KeyHandler extends KeyAdapter
 193:   {
 194:     public KeyHandler()
 195:     {
 196:     }
 197:     
 198:     public void keyPressed( KeyEvent evt ) 
 199:     {
 200:       int lead = BasicListUI.this.list.getLeadSelectionIndex();
 201:       int max = BasicListUI.this.list.getModel().getSize() - 1;
 202:       // Do nothing if list is empty
 203:       if (max == -1)
 204:         return;
 205: 
 206:       // Process the key event.  Bindings can be found in
 207:       // javax.swing.plaf.basic.BasicLookAndFeel.java
 208:       if ((evt.getKeyCode() == KeyEvent.VK_DOWN)
 209:           || (evt.getKeyCode() == KeyEvent.VK_KP_DOWN))
 210:         {
 211:           if (!evt.isShiftDown())
 212:             {
 213:               BasicListUI.this.list.clearSelection();
 214:               BasicListUI.this.list.setSelectedIndex(Math.min(lead+1,max));
 215:             }
 216:           else 
 217:             {
 218:               BasicListUI.this.list.getSelectionModel().
 219:                 setLeadSelectionIndex(Math.min(lead+1,max));
 220:             }
 221:         }
 222:       else if ((evt.getKeyCode() == KeyEvent.VK_UP)
 223:                || (evt.getKeyCode() == KeyEvent.VK_KP_UP))
 224:         {
 225:           if (!evt.isShiftDown())
 226:             {
 227:               BasicListUI.this.list.clearSelection();
 228:               BasicListUI.this.list.setSelectedIndex(Math.max(lead-1,0));
 229:             }
 230:           else
 231:             {
 232:               BasicListUI.this.list.getSelectionModel().
 233:                 setLeadSelectionIndex(Math.max(lead-1,0));
 234:             }
 235:         }
 236:       else if (evt.getKeyCode() == KeyEvent.VK_PAGE_UP)
 237:         {
 238:           // FIXME: implement, need JList.ensureIndexIsVisible to work
 239:         }
 240:       else if (evt.getKeyCode() == KeyEvent.VK_PAGE_DOWN)
 241:         {
 242:           // FIXME: implement, need JList.ensureIndexIsVisible to work
 243:         }
 244:       else if (evt.getKeyCode() == KeyEvent.VK_BACK_SLASH
 245:                && evt.isControlDown())
 246:         {
 247:             BasicListUI.this.list.clearSelection();
 248:         }
 249:       else if ((evt.getKeyCode() == KeyEvent.VK_HOME)
 250:                || evt.getKeyCode() == KeyEvent.VK_END)
 251:         {
 252:           // index is either 0 for HOME, or last cell for END
 253:           int index = (evt.getKeyCode() == KeyEvent.VK_HOME) ? 0 : max;
 254:           
 255:           if (!evt.isShiftDown() ||(BasicListUI.this.list.getSelectionMode() 
 256:                                     == ListSelectionModel.SINGLE_SELECTION))
 257:             BasicListUI.this.list.setSelectedIndex(index);
 258:           else if (BasicListUI.this.list.getSelectionMode() == 
 259:                    ListSelectionModel.SINGLE_INTERVAL_SELECTION)
 260:             BasicListUI.this.list.setSelectionInterval
 261:               (BasicListUI.this.list.getAnchorSelectionIndex(), index);
 262:           else
 263:             BasicListUI.this.list.getSelectionModel().
 264:               setLeadSelectionIndex(index);
 265:         }
 266:       else if ((evt.getKeyCode() == KeyEvent.VK_A || evt.getKeyCode()
 267:                 == KeyEvent.VK_SLASH) && evt.isControlDown())
 268:         {
 269:           BasicListUI.this.list.setSelectionInterval(0, max);
 270:         }
 271:       else if (evt.getKeyCode() == KeyEvent.VK_SPACE && evt.isControlDown())
 272:         {
 273:           BasicListUI.this.list.getSelectionModel().
 274:             setLeadSelectionIndex(Math.min(lead+1,max));
 275:         }
 276:       
 277:     }
 278:   }
 279:   
 280:   /**
 281:    * A helper class which listens for {@link MouseEvent}s 
 282:    * from the {@link JList}.
 283:    */
 284:   public class MouseInputHandler implements MouseInputListener
 285:   {
 286:     /**
 287:      * Called when a mouse button press/release cycle completes
 288:      * on the {@link JList}
 289:      *
 290:      * @param event The event representing the mouse click
 291:      */
 292:     public void mouseClicked(MouseEvent event)
 293:     {
 294:       Point click = event.getPoint();
 295:       int index = BasicListUI.this.locationToIndex(list, click);
 296:       if (index == -1)
 297:         return;
 298:       if (event.isControlDown())
 299:         {
 300:           if (BasicListUI.this.list.getSelectionMode() == 
 301:               ListSelectionModel.SINGLE_SELECTION)
 302:             BasicListUI.this.list.setSelectedIndex(index);
 303:           else if (BasicListUI.this.list.isSelectedIndex(index))
 304:             BasicListUI.this.list.removeSelectionInterval(index,index);
 305:           else
 306:             BasicListUI.this.list.addSelectionInterval(index,index);
 307:         }
 308:       else if (event.isShiftDown())
 309:         {
 310:           if (BasicListUI.this.list.getSelectionMode() == 
 311:               ListSelectionModel.SINGLE_SELECTION)
 312:             BasicListUI.this.list.setSelectedIndex(index);
 313:           else if (BasicListUI.this.list.getSelectionMode() == 
 314:                    ListSelectionModel.SINGLE_INTERVAL_SELECTION)
 315:             // COMPAT: the IBM VM is compatible with the following line of code.
 316:             // However, compliance with Sun's VM would correspond to replacing 
 317:             // getAnchorSelectionIndex() with getLeadSelectionIndex().This is 
 318:             // both unnatural and contradictory to the way they handle other 
 319:             // similar UI interactions.
 320:             BasicListUI.this.list.setSelectionInterval
 321:               (BasicListUI.this.list.getAnchorSelectionIndex(), index);
 322:           else
 323:             // COMPAT: both Sun and IBM are compatible instead with:
 324:             // BasicListUI.this.list.setSelectionInterval
 325:             //     (BasicListUI.this.list.getLeadSelectionIndex(),index);
 326:             // Note that for IBM this is contradictory to what they did in 
 327:             // the above situation for SINGLE_INTERVAL_SELECTION.  
 328:             // The most natural thing to do is the following:
 329:             BasicListUI.this.list.getSelectionModel().
 330:               setLeadSelectionIndex(index);
 331:         }
 332:       else
 333:         BasicListUI.this.list.setSelectedIndex(index);
 334:     }
 335: 
 336:     /**
 337:      * Called when a mouse button is pressed down on the
 338:      * {@link JList}.
 339:      *
 340:      * @param event The event representing the mouse press
 341:      */
 342:     public void mousePressed(MouseEvent event)
 343:     {
 344:     }
 345: 
 346:     /**
 347:      * Called when a mouse button is released on
 348:      * the {@link JList}
 349:      *
 350:      * @param event The event representing the mouse press
 351:      */
 352:     public void mouseReleased(MouseEvent event)
 353:     {
 354:     }
 355: 
 356:     /**
 357:      * Called when the mouse pointer enters the area bounded
 358:      * by the {@link JList}
 359:      *
 360:      * @param event The event representing the mouse entry
 361:      */
 362:     public void mouseEntered(MouseEvent event)
 363:     {
 364:     }
 365: 
 366:     /**
 367:      * Called when the mouse pointer leaves the area bounded
 368:      * by the {@link JList}
 369:      *
 370:      * @param event The event representing the mouse exit
 371:      */
 372:     public void mouseExited(MouseEvent event)
 373:     {
 374:     }
 375: 
 376:     /**
 377:      * Called when the mouse pointer moves over the area bounded
 378:      * by the {@link JList} while a button is held down.
 379:      *
 380:      * @param event The event representing the mouse drag
 381:      */
 382:     public void mouseDragged(MouseEvent event)
 383:     {
 384:     }
 385: 
 386:     /**
 387:      * Called when the mouse pointer moves over the area bounded
 388:      * by the {@link JList}.
 389:      *
 390:      * @param event The event representing the mouse move
 391:      */
 392:     public void mouseMoved(MouseEvent event)
 393:     {
 394:     }
 395:   }
 396: 
 397:   /**
 398:    * Helper class which listens to {@link PropertyChangeEvent}s
 399:    * from the {@link JList}.
 400:    */
 401:   public class PropertyChangeHandler implements PropertyChangeListener
 402:   {
 403:     /**
 404:      * Called when the {@link JList} changes one of its bound properties.
 405:      *
 406:      * @param e The event representing the property change
 407:      */
 408:     public void propertyChange(PropertyChangeEvent e)
 409:     {
 410:       if (e.getSource() == BasicListUI.this.list)
 411:         {
 412:           if (e.getOldValue() != null && e.getOldValue() instanceof ListModel)
 413:             ((ListModel) e.getOldValue()).removeListDataListener(BasicListUI.this.listDataListener);
 414: 
 415:           if (e.getNewValue() != null && e.getNewValue() instanceof ListModel)
 416:             ((ListModel) e.getNewValue()).addListDataListener(BasicListUI.this.listDataListener);
 417:         }
 418:       BasicListUI.this.damageLayout();
 419:     }
 420:   }
 421: 
 422:   /**
 423:    * Creates a new BasicListUI for the component.
 424:    *
 425:    * @param c The component to create a UI for
 426:    *
 427:    * @return A new UI
 428:    */
 429:   public static ComponentUI createUI(final JComponent c)
 430:   {
 431:     return new BasicListUI();
 432:   }
 433: 
 434:   /** The current focus listener. */
 435:   protected FocusListener focusListener;
 436: 
 437:   /** The data listener listening to the model. */
 438:   protected ListDataListener listDataListener;
 439: 
 440:   /** The selection listener listening to the selection model. */
 441:   protected ListSelectionListener listSelectionListener;
 442: 
 443:   /** The mouse listener listening to the list. */
 444:   protected MouseInputListener mouseInputListener;
 445: 
 446:   /** The key listener listening to the list */
 447:   private KeyHandler keyListener;
 448: 
 449:   /** The property change listener listening to the list. */
 450:   protected PropertyChangeListener propertyChangeListener;
 451: 
 452: 
 453:   /** The component listener that receives notification for resizing the
 454:    * JList component.*/
 455:   private ComponentListener componentListener;
 456: 
 457:   /** Saved reference to the list this UI was created for. */
 458:   protected JList list;
 459: 
 460:   /** The height of a single cell in the list. */
 461:   protected int cellHeight;
 462: 
 463:   /** The width of a single cell in the list. */
 464:   protected int cellWidth;
 465: 
 466:   /** 
 467:    * An array of varying heights of cells in the list, in cases where each
 468:    * cell might have a different height.
 469:    */
 470:   protected int[] cellHeights;
 471: 
 472:   /**
 473:    * A simple counter. When nonzero, indicates that the UI class is out of
 474:    * date with respect to the underlying list, and must recalculate the
 475:    * list layout before painting or performing size calculations.
 476:    */
 477:   protected int updateLayoutStateNeeded;
 478: 
 479:   /**
 480:    * The {@link CellRendererPane} that is used for painting.
 481:    */
 482:   protected CellRendererPane rendererPane;
 483: 
 484:   /**
 485:    * Calculate the height of a particular row. If there is a fixed {@link
 486:    * #cellHeight}, return it; otherwise return the specific row height
 487:    * requested from the {@link #cellHeights} array. If the requested row
 488:    * is invalid, return <code>-1</code>.
 489:    *
 490:    * @param row The row to get the height of
 491:    *
 492:    * @return The height, in pixels, of the specified row
 493:    */
 494:   protected int getRowHeight(int row)
 495:   {
 496:     if (row < 0 || row >= cellHeights.length)
 497:       return -1;
 498:     else if (cellHeight != -1)
 499:       return cellHeight;
 500:     else
 501:       return cellHeights[row];
 502:   }
 503: 
 504:   /**
 505:    * Calculate the bounds of a particular cell, considering the upper left
 506:    * corner of the list as the origin position <code>(0,0)</code>.
 507:    *
 508:    * @param l Ignored; calculates over <code>this.list</code>
 509:    * @param index1 The first row to include in the bounds
 510:    * @param index2 The last row to incude in the bounds
 511:    *
 512:    * @return A rectangle encompassing the range of rows between 
 513:    * <code>index1</code> and <code>index2</code> inclusive
 514:    */
 515:   public Rectangle getCellBounds(JList l, int index1, int index2)
 516:   {
 517:     maybeUpdateLayoutState();
 518: 
 519:     if (l != list || cellWidth == -1)
 520:       return null;
 521: 
 522:     int minIndex = Math.min(index1, index2);
 523:     int maxIndex = Math.max(index1, index2);
 524:     Point loc = indexToLocation(list, minIndex);
 525:     Rectangle bounds = new Rectangle(loc.x, loc.y, cellWidth,
 526:                                      getRowHeight(minIndex));
 527: 
 528:     for (int i = minIndex + 1; i <= maxIndex; i++)
 529:       {
 530:         Point hiLoc = indexToLocation(list, i);
 531:         Rectangle hibounds = new Rectangle(hiLoc.x, hiLoc.y, cellWidth,
 532:                                        getRowHeight(i));
 533:         bounds = bounds.union(hibounds);
 534:       }
 535: 
 536:     return bounds;
 537:   }
 538: 
 539:   /**
 540:    * Calculate the Y coordinate of the upper edge of a particular row,
 541:    * considering the Y coordinate <code>0</code> to occur at the top of the
 542:    * list.
 543:    *
 544:    * @param row The row to calculate the Y coordinate of
 545:    *
 546:    * @return The Y coordinate of the specified row, or <code>-1</code> if
 547:    * the specified row number is invalid
 548:    */
 549:   protected int convertRowToY(int row)
 550:   {
 551:     int y = 0;
 552:     for (int i = 0; i < row; ++i)
 553:       {
 554:         int h = getRowHeight(i);
 555:         if (h == -1)
 556:           return -1;
 557:         y += h;
 558:       }
 559:     return y;
 560:   }
 561: 
 562:   /**
 563:    * Calculate the row number containing a particular Y coordinate,
 564:    * considering the Y coodrinate <code>0</code> to occur at the top of the
 565:    * list.
 566:    *
 567:    * @param y0 The Y coordinate to calculate the row number for
 568:    *
 569:    * @return The row number containing the specified Y value, or <code>-1</code>
 570:    * if the specified Y coordinate is invalid
 571:    */
 572:   protected int convertYToRow(int y0)
 573:   {
 574:     for (int row = 0; row < cellHeights.length; ++row)
 575:       {
 576:         int h = getRowHeight(row);
 577: 
 578:         if (y0 < h)
 579:           return row;
 580:         y0 -= h;
 581:       }
 582:     return -1;
 583:   }
 584: 
 585:   /**
 586:    * Recomputes the {@link #cellHeights}, {@link #cellHeight}, and {@link
 587:    * #cellWidth} properties by examining the variouis properties of the
 588:    * {@link JList}.
 589:    */
 590:   protected void updateLayoutState()
 591:   {
 592:     int nrows = list.getModel().getSize();
 593:     cellHeight = -1;
 594:     cellWidth = -1;
 595:     if (cellHeights == null || cellHeights.length != nrows)
 596:       cellHeights = new int[nrows];
 597:     if (list.getFixedCellHeight() == -1 || list.getFixedCellWidth() == -1)
 598:       {
 599:         ListCellRenderer rend = list.getCellRenderer();
 600:         for (int i = 0; i < nrows; ++i)
 601:           {
 602:             Component flyweight = rend.getListCellRendererComponent(list,
 603:                                                                     list.getModel()
 604:                                                                         .getElementAt(i),
 605:                                                                     0, false,
 606:                                                                     false);
 607:             Dimension dim = flyweight.getPreferredSize();
 608:             cellHeights[i] = dim.height;
 609:             // compute average cell height (little hack here)
 610:             cellHeight = (cellHeight * i + cellHeights[i]) / (i + 1);
 611:             cellWidth = Math.max(cellWidth, dim.width);
 612:             if (list.getLayoutOrientation() == JList.VERTICAL)
 613:                 cellWidth = Math.max(cellWidth, list.getSize().width);
 614:           }
 615:       }
 616:     else
 617:       {
 618:         cellHeight = list.getFixedCellHeight();
 619:         cellWidth = list.getFixedCellWidth();
 620:       }
 621:   }
 622: 
 623:   /**
 624:    * Marks the current layout as damaged and requests revalidation from the
 625:    * JList.
 626:    * This is package-private to avoid an accessor method.
 627:    *
 628:    * @see #updateLayoutStateNeeded
 629:    */
 630:   void damageLayout()
 631:   {
 632:     updateLayoutStateNeeded = 1;
 633:   }
 634: 
 635:   /**
 636:    * Calls {@link #updateLayoutState} if {@link #updateLayoutStateNeeded}
 637:    * is nonzero, then resets {@link #updateLayoutStateNeeded} to zero.
 638:    */
 639:   protected void maybeUpdateLayoutState()
 640:   {
 641:     if (updateLayoutStateNeeded != 0)
 642:       {
 643:         updateLayoutState();
 644:         updateLayoutStateNeeded = 0;
 645:       }
 646:   }
 647: 
 648:   /**
 649:    * Creates a new BasicListUI object.
 650:    */
 651:   public BasicListUI()
 652:   {
 653:     focusListener = new FocusHandler();
 654:     listDataListener = new ListDataHandler();
 655:     listSelectionListener = new ListSelectionHandler();
 656:     mouseInputListener = new MouseInputHandler();
 657:     keyListener = new KeyHandler();
 658:     propertyChangeListener = new PropertyChangeHandler();
 659:     componentListener = new ComponentHandler();
 660:     updateLayoutStateNeeded = 1;
 661:     rendererPane = new CellRendererPane();
 662:   }
 663: 
 664:   /**
 665:    * Installs various default settings (mostly colors) from the {@link
 666:    * UIDefaults} into the {@link JList}
 667:    *
 668:    * @see #uninstallDefaults
 669:    */
 670:   protected void installDefaults()
 671:   {
 672:     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 673:     list.setForeground(defaults.getColor("List.foreground"));
 674:     list.setBackground(defaults.getColor("List.background"));
 675:     list.setSelectionForeground(defaults.getColor("List.selectionForeground"));
 676:     list.setSelectionBackground(defaults.getColor("List.selectionBackground"));
 677:     list.setOpaque(true);
 678:   }
 679: 
 680:   /**
 681:    * Resets to <code>null</code> those defaults which were installed in 
 682:    * {@link #installDefaults}
 683:    */
 684:   protected void uninstallDefaults()
 685:   {
 686:     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 687:     list.setForeground(null);
 688:     list.setBackground(null);
 689:     list.setSelectionForeground(null);
 690:     list.setSelectionBackground(null);
 691:   }
 692: 
 693:   /**
 694:    * Attaches all the listeners we have in the UI class to the {@link
 695:    * JList}, its model and its selection model.
 696:    *
 697:    * @see #uninstallListeners
 698:    */
 699:   protected void installListeners()
 700:   {
 701:     list.addFocusListener(focusListener);
 702:     list.getModel().addListDataListener(listDataListener);
 703:     list.addListSelectionListener(listSelectionListener);
 704:     list.addMouseListener(mouseInputListener);
 705:     list.addKeyListener(keyListener);
 706:     list.addMouseMotionListener(mouseInputListener);
 707:     list.addPropertyChangeListener(propertyChangeListener);
 708:     list.addComponentListener(componentListener);
 709:   }
 710: 
 711:   /**
 712:    * Detaches all the listeners we attached in {@link #installListeners}.
 713:    */
 714:   protected void uninstallListeners()
 715:   {
 716:     list.removeFocusListener(focusListener);
 717:     list.getModel().removeListDataListener(listDataListener);
 718:     list.removeListSelectionListener(listSelectionListener);
 719:     list.removeMouseListener(mouseInputListener);
 720:     list.removeKeyListener(keyListener);
 721:     list.removeMouseMotionListener(mouseInputListener);
 722:     list.removePropertyChangeListener(propertyChangeListener);
 723:   }
 724: 
 725:   /**
 726:    * Installs keyboard actions for this UI in the {@link JList}.
 727:    */
 728:   protected void installKeyboardActions()
 729:   {
 730:   }
 731: 
 732:   /**
 733:    * Uninstalls keyboard actions for this UI in the {@link JList}.
 734:    */
 735:   protected void uninstallKeyboardActions()
 736:   {
 737:   }
 738: 
 739:   /**
 740:    * Installs the various aspects of the UI in the {@link JList}. In
 741:    * particular, calls {@link #installDefaults}, {@link #installListeners}
 742:    * and {@link #installKeyboardActions}. Also saves a reference to the
 743:    * provided component, cast to a {@link JList}.
 744:    *
 745:    * @param c The {@link JList} to install the UI into
 746:    */
 747:   public void installUI(final JComponent c)
 748:   {
 749:     super.installUI(c);
 750:     list = (JList) c;
 751:     installDefaults();
 752:     installListeners();
 753:     installKeyboardActions();
 754:     maybeUpdateLayoutState();
 755:   }
 756: 
 757:   /**
 758:    * Uninstalls all the aspects of the UI which were installed in {@link
 759:    * #installUI}. When finished uninstalling, drops the saved reference to
 760:    * the {@link JList}.
 761:    *
 762:    * @param c Ignored; the UI is uninstalled from the {@link JList}
 763:    * reference saved during the call to {@link #installUI}
 764:    */
 765:   public void uninstallUI(final JComponent c)
 766:   {
 767:     uninstallKeyboardActions();
 768:     uninstallListeners();
 769:     uninstallDefaults();
 770:     list = null;
 771:   }
 772: 
 773:   /**
 774:    * Gets the size this list would prefer to assume. This is calculated by
 775:    * calling {@link #getCellBounds} over the entire list.
 776:    *
 777:    * @param c Ignored; uses the saved {@link JList} reference 
 778:    *
 779:    * @return DOCUMENT ME!
 780:    */
 781:   public Dimension getPreferredSize(JComponent c)
 782:   {
 783:     int size = list.getModel().getSize();
 784:     if (size == 0)
 785:       return new Dimension(0, 0);
 786:     int visibleRows = list.getVisibleRowCount();
 787:     int layoutOrientation = list.getLayoutOrientation();
 788:     Rectangle bounds = getCellBounds(list, 0, list.getModel().getSize() - 1);
 789:     Dimension retVal = bounds.getSize();
 790:     Component parent = list.getParent();
 791:     if ((visibleRows == -1) && (parent instanceof JViewport))
 792:       {
 793:         JViewport viewport = (JViewport) parent;
 794: 
 795:         if (layoutOrientation == JList.HORIZONTAL_WRAP)
 796:           {
 797:             int h = viewport.getSize().height;
 798:             int cellsPerCol = h / cellHeight;
 799:             int w = size / cellsPerCol * cellWidth;
 800:             retVal = new Dimension(w, h);
 801:           }
 802:         else if (layoutOrientation == JList.VERTICAL_WRAP)
 803:           {
 804:             int w = viewport.getSize().width;
 805:             int cellsPerRow = Math.max(w / cellWidth, 1);
 806:             int h = size / cellsPerRow * cellHeight;
 807:             retVal = new Dimension(w, h);
 808:           }
 809:       }
 810:     return retVal;
 811:   }
 812: 
 813:   /**
 814:    * Paints the packground of the list using the background color
 815:    * of the specified component.
 816:    *
 817:    * @param g The graphics context to paint in
 818:    * @param c The component to paint the background of
 819:    */
 820:   private void paintBackground(Graphics g, JComponent c)
 821:   {
 822:     Dimension size = getPreferredSize(c);
 823:     Color save = g.getColor();
 824:     g.setColor(c.getBackground());
 825:     g.fillRect(0, 0, size.width, size.height);
 826:     g.setColor(save);
 827:   }
 828: 
 829:   /**
 830:    * Paints a single cell in the list.
 831:    *
 832:    * @param g The graphics context to paint in
 833:    * @param row The row number to paint
 834:    * @param bounds The bounds of the cell to paint, assuming a coordinate
 835:    * system beginning at <code>(0,0)</code> in the upper left corner of the
 836:    * list
 837:    * @param rend A cell renderer to paint with
 838:    * @param data The data to provide to the cell renderer
 839:    * @param sel A selection model to provide to the cell renderer
 840:    * @param lead The lead selection index of the list
 841:    */
 842:   protected void paintCell(Graphics g, int row, Rectangle bounds,
 843:                  ListCellRenderer rend, ListModel data,
 844:                  ListSelectionModel sel, int lead)
 845:   {
 846:     boolean is_sel = list.isSelectedIndex(row);
 847:     boolean has_focus = false;
 848:     Component comp = rend.getListCellRendererComponent(list,
 849:                                                        data.getElementAt(row),
 850:                                                        0, is_sel, has_focus);
 851:     //comp.setBounds(new Rectangle(0, 0, bounds.width, bounds.height));
 852:     //comp.paint(g);
 853:     rendererPane.paintComponent(g, comp, list, bounds);
 854:   }
 855: 
 856:   /**
 857:    * Paints the list by calling {@link #paintBackground} and then repeatedly
 858:    * calling {@link #paintCell} for each visible cell in the list.
 859:    *
 860:    * @param g The graphics context to paint with
 861:    * @param c Ignored; uses the saved {@link JList} reference 
 862:    */
 863:   public void paint(Graphics g, JComponent c)
 864:   {
 865:     int nrows = list.getModel().getSize();
 866:     if (nrows == 0)
 867:       return;
 868: 
 869:     maybeUpdateLayoutState();
 870:     ListCellRenderer render = list.getCellRenderer();
 871:     ListModel model = list.getModel();
 872:     ListSelectionModel sel = list.getSelectionModel();
 873:     int lead = sel.getLeadSelectionIndex();
 874:     Rectangle clip = g.getClipBounds();
 875:     paintBackground(g, list);
 876: 
 877:     for (int row = 0; row < nrows; ++row)
 878:       {
 879:         Rectangle bounds = getCellBounds(list, row, row);
 880:         if (bounds.intersects(clip))
 881:           paintCell(g, row, bounds, render, model, sel, lead);
 882:       }
 883:   }
 884: 
 885:   /**
 886:    * Computes the index of a list cell given a point within the list.
 887:    *
 888:    * @param list the list which on which the computation is based on
 889:    * @param location the coordinates
 890:    *
 891:    * @return the index of the list item that is located at the given
 892:    *         coordinates or <code>null</code> if the location is invalid
 893:    */
 894:   public int locationToIndex(JList list, Point location)
 895:   {
 896:     int layoutOrientation = list.getLayoutOrientation();
 897:     int index = -1;
 898:     switch (layoutOrientation)
 899:       {
 900:       case JList.VERTICAL:
 901:         index = convertYToRow(location.y);
 902:         break;
 903:       case JList.HORIZONTAL_WRAP:
 904:         // determine visible rows and cells per row
 905:         int visibleRows = list.getVisibleRowCount();
 906:         int cellsPerRow = -1;
 907:         int numberOfItems = list.getModel().getSize();
 908:         Dimension listDim = list.getSize();
 909:         if (visibleRows <= 0)
 910:           {
 911:             try
 912:               {
 913:                 cellsPerRow = listDim.width / cellWidth;
 914:               }
 915:             catch (ArithmeticException ex)
 916:               {
 917:                 cellsPerRow = 1;
 918:               }
 919:           }
 920:         else
 921:           {
 922:             cellsPerRow = numberOfItems / visibleRows + 1;
 923:           }
 924: 
 925:         // determine index for the given location
 926:         int cellsPerColumn = numberOfItems / cellsPerRow + 1;
 927:         int gridX = Math.min(location.x / cellWidth, cellsPerRow - 1);
 928:         int gridY = Math.min(location.y / cellHeight, cellsPerColumn);
 929:         index = gridX + gridY * cellsPerRow;
 930:         break;
 931:       case JList.VERTICAL_WRAP:
 932:         // determine visible rows and cells per column
 933:         int visibleRows2 = list.getVisibleRowCount();
 934:         if (visibleRows2 <= 0)
 935:           {
 936:             Dimension listDim2 = list.getSize();
 937:             visibleRows2 = listDim2.height / cellHeight;
 938:           }
 939:         int numberOfItems2 = list.getModel().getSize();
 940:         int cellsPerRow2 = numberOfItems2 / visibleRows2 + 1;
 941: 
 942:         Dimension listDim2 = list.getSize();
 943:         int gridX2 = Math.min(location.x / cellWidth, cellsPerRow2 - 1);
 944:         int gridY2 = Math.min(location.y / cellHeight, visibleRows2);
 945:         index = gridY2 + gridX2 * visibleRows2;
 946:         break;
 947:       }
 948:     return index;
 949:   }
 950: 
 951:   public Point indexToLocation(JList list, int index)
 952:   {
 953:     int layoutOrientation = list.getLayoutOrientation();
 954:     Point loc = null;
 955:     switch (layoutOrientation)
 956:       {
 957:       case JList.VERTICAL:
 958:         loc = new Point(0, convertRowToY(index));
 959:         break;
 960:       case JList.HORIZONTAL_WRAP:
 961:         // determine visible rows and cells per row
 962:         int visibleRows = list.getVisibleRowCount();
 963:         int numberOfCellsPerRow = -1;
 964:         if (visibleRows <= 0)
 965:           {
 966:             Dimension listDim = list.getSize();
 967:             numberOfCellsPerRow = Math.max(listDim.width / cellWidth, 1);
 968:           }
 969:         else
 970:           {
 971:             int numberOfItems = list.getModel().getSize();
 972:             numberOfCellsPerRow = numberOfItems / visibleRows + 1;
 973:           }
 974:         // compute coordinates inside the grid
 975:         int gridX = index % numberOfCellsPerRow;
 976:         int gridY = index / numberOfCellsPerRow;
 977:         int locX = gridX * cellWidth;
 978:         int locY = gridY * cellHeight;
 979:         loc = new Point(locX, locY);
 980:         break;
 981:       case JList.VERTICAL_WRAP:
 982:         // determine visible rows and cells per column
 983:         int visibleRows2 = list.getVisibleRowCount();
 984:         if (visibleRows2 <= 0)
 985:           {
 986:             Dimension listDim2 = list.getSize();
 987:             visibleRows2 = listDim2.height / cellHeight;
 988:           }
 989:         // compute coordinates inside the grid
 990:         if (visibleRows2 > 0)
 991:           {
 992:             int gridY2 = index % visibleRows2;
 993:             int gridX2 = index / visibleRows2;
 994:             int locX2 = gridX2 * cellWidth;
 995:             int locY2 = gridY2 * cellHeight;
 996:             loc = new Point(locX2, locY2);
 997:           }
 998:         else
 999:           loc = new Point(0, convertRowToY(index));
1000:         break;
1001:       }
1002:     return loc;
1003:   }
1004: }