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