001/*
002 * $Id$
003 *
004 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 *
021 */
022package org.jdesktop.swingx.plaf.basic.core;
023
024import java.awt.Color;
025import java.awt.Component;
026import java.awt.Dimension;
027import java.awt.Font;
028import java.awt.Graphics;
029import java.awt.Insets;
030import java.awt.Point;
031import java.awt.Rectangle;
032import java.awt.Shape;
033import java.awt.datatransfer.Transferable;
034import java.awt.event.ActionEvent;
035import java.awt.event.FocusEvent;
036import java.awt.event.FocusListener;
037import java.awt.event.KeyEvent;
038import java.awt.event.KeyListener;
039import java.awt.event.MouseEvent;
040import java.awt.geom.Point2D;
041import java.beans.PropertyChangeEvent;
042import java.beans.PropertyChangeListener;
043
044import javax.swing.Action;
045import javax.swing.CellRendererPane;
046import javax.swing.DefaultListCellRenderer;
047import javax.swing.DefaultListSelectionModel;
048import javax.swing.InputMap;
049import javax.swing.JComponent;
050import javax.swing.JList;
051import javax.swing.KeyStroke;
052import javax.swing.ListCellRenderer;
053import javax.swing.ListModel;
054import javax.swing.ListSelectionModel;
055import javax.swing.LookAndFeel;
056import javax.swing.SwingUtilities;
057import javax.swing.TransferHandler;
058import javax.swing.UIDefaults;
059import javax.swing.UIManager;
060import javax.swing.event.ListDataEvent;
061import javax.swing.event.ListDataListener;
062import javax.swing.event.ListSelectionEvent;
063import javax.swing.event.ListSelectionListener;
064import javax.swing.event.MouseInputListener;
065import javax.swing.plaf.ComponentUI;
066import javax.swing.plaf.UIResource;
067import javax.swing.plaf.basic.BasicListUI;
068import javax.swing.text.Position;
069
070import org.jdesktop.swingx.JXList;
071import org.jdesktop.swingx.SwingXUtilities;
072import org.jdesktop.swingx.plaf.LookAndFeelUtils;
073import org.jdesktop.swingx.plaf.UIAction;
074import org.jdesktop.swingx.plaf.basic.core.DragRecognitionSupport.BeforeDrag;
075
076//import sun.swing.DefaultLookup;
077//import sun.swing.SwingUtilities2;
078//import sun.swing.UIAction;
079
080/**
081 * An extensible implementation of {@code ListUI} for JXList.
082 * {@code BasicXListUI} instances cannot be shared between multiple
083 * lists.<p>
084 * 
085 * The heart of added functionality is to support sorting/filtering, that is keep 
086 * model-selection and RowSorter state synchronized. The details are delegated to a ListSortUI, 
087 * but this class is responsible to manage the sortUI on changes of list properties, model and 
088 * view selection (same strategy as in JXTable).<p>
089 * 
090 * Note: this delegate is mostly a 1:1 copy of BasicListUI. The difference is that
091 * it accesses the list elements and list elementCount exclusively through the 
092 * JXList api. This allows a clean implementation of sorting/filtering.<p>
093 * 
094 * The differences (goal was to touch as little code as possible as this needs
095 * to be updated on every change to core until that is changed to not access
096 * the list's model directly, sigh) for core functionality:
097 * <ul>
098 * <li> extracted method for list.getModel().getSize (for the delegate class and 
099 *      all contained static classes) and use that method exclusively
100 * <li> similar for remaining list.getModel(): implemented wrapping listModel 
101 *    which messages the list
102 * <li> rename key for shared actionMap to keep core list actions separate 
103 *    (just in case somebody wants both) - they point to the wrong delegate
104 * <li> replaced references to SwingUtilities2 in sun packages by references to 
105 *     c&p'ed methods in SwingXUtilities
106 * <li> replaced storage of shared Input/ActionMap in defaultLookup by direct
107 *     storage in UIManager.         
108 * </ul>
109 * 
110 * Differences to achieve extended functionality:
111 * <ul>
112 * <li> added methods to un/-installSortUI and call in un/installUI(component)
113 * <li> changed PropertyChangeHandler to call a) hasHandledPropertyChange to 
114 *  allow this class to replace super handler functinality and 
115 *  b) updateSortUI after handling all not-sorter related properties.
116 * <li> changed createPropertyChangeListener to return a PropertyChangeHandler
117 * <li> changed ListDataHandler to check if event handled by SortUI and delegate
118 *    to handler only if not
119 * <li> changed createListDataListener to return a ListDataHandler
120 * <li> changed ListSelectionHandler to check if event handled by SortUI and 
121 *   delegate to handler only if not
122 * </ul> changed createListSelectionListener to return a ListSelectionHandler
123 * 
124 * Note: extension of core (instead of implement from scratch) is to keep 
125 * external (?) code working which expects a ui delegate of type BasicSomething.
126 * LAF implementors with a custom ListUI extending BasicListUI should be able to
127 * add support for JXList by adding a separate CustomXListUI extending this, same
128 * as the default with parent changed. <b>Beware</b>: custom code must not 
129 * call access the model directly or - if they insist - convert the row index to 
130 * account for sorting/filtering! That's the whole point of this class.
131 * 
132 * @version 1.127 12/02/08
133 * @author Hans Muller
134 * @author Philip Milne
135 * @author Shannon Hickey (drag and drop)
136 */
137public class BasicXListUI  extends BasicListUI 
138{
139    private static final StringBuilder BASELINE_COMPONENT_KEY =
140        new StringBuilder("List.baselineComponent");
141
142    protected JXList list = null;
143    protected CellRendererPane rendererPane;
144
145    // Listeners that this UI attaches to the JList
146    protected FocusListener focusListener;
147    protected MouseInputListener mouseInputListener;
148    protected ListSelectionListener listSelectionListener;
149    protected ListDataListener listDataListener;
150    protected PropertyChangeListener propertyChangeListener;
151    private Handler handler;
152
153    protected int[] cellHeights = null;
154    protected int cellHeight = -1;
155    protected int cellWidth = -1;
156    protected int updateLayoutStateNeeded = modelChanged;
157    /**
158     * Height of the list. When asked to paint, if the current size of
159     * the list differs, this will update the layout state.
160     */
161    private int listHeight;
162
163    /**
164     * Width of the list. When asked to paint, if the current size of
165     * the list differs, this will update the layout state.
166     */
167    private int listWidth;
168
169    /**
170     * The layout orientation of the list.
171     */
172    private int layoutOrientation;
173
174    // Following ivars are used if the list is laying out horizontally
175
176    /**
177     * Number of columns to create.
178     */
179    private int columnCount;
180    /**
181     * Preferred height to make the list, this is only used if the 
182     * the list is layed out horizontally.
183     */
184    private int preferredHeight;
185    /**
186     * Number of rows per column. This is only used if the row height is
187     * fixed.
188     */
189    private int rowsPerColumn;
190
191    /**
192     * The time factor to treate the series of typed alphanumeric key
193     * as prefix for first letter navigation.
194     */
195    private long timeFactor = 1000L;
196
197    /**
198     * Local cache of JList's client property "List.isFileList"
199     */
200    private boolean isFileList = false;
201
202    /**
203     * Local cache of JList's component orientation property
204     */
205    private boolean isLeftToRight = true;
206
207    /* The bits below define JList property changes that affect layout.
208     * When one of these properties changes we set a bit in
209     * updateLayoutStateNeeded.  The change is dealt with lazily, see
210     * maybeUpdateLayoutState.  Changes to the JLists model, e.g. the
211     * models length changed, are handled similarly, see DataListener.
212     */
213
214    protected final static int modelChanged = 1 << 0;
215    protected final static int selectionModelChanged = 1 << 1;
216    protected final static int fontChanged = 1 << 2;
217    protected final static int fixedCellWidthChanged = 1 << 3;
218    protected final static int fixedCellHeightChanged = 1 << 4;
219    protected final static int prototypeCellValueChanged = 1 << 5;
220    protected final static int cellRendererChanged = 1 << 6;
221    private final static int layoutOrientationChanged = 1 << 7;
222    private final static int heightChanged = 1 << 8;
223    private final static int widthChanged = 1 << 9;
224    private final static int componentOrientationChanged = 1 << 10;
225
226    private static final int DROP_LINE_THICKNESS = 2;
227
228    // FIXME - JW LazyActionMap copy is in different package ... move here?
229    public static void loadActionMap(LazyActionMap map) {
230        map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN));
231        map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_EXTEND));
232        map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD));
233        map.put(new Actions(Actions.SELECT_NEXT_COLUMN));
234        map.put(new Actions(Actions.SELECT_NEXT_COLUMN_EXTEND));
235        map.put(new Actions(Actions.SELECT_NEXT_COLUMN_CHANGE_LEAD));
236        map.put(new Actions(Actions.SELECT_PREVIOUS_ROW));
237        map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_EXTEND));
238        map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_CHANGE_LEAD));
239        map.put(new Actions(Actions.SELECT_NEXT_ROW));
240        map.put(new Actions(Actions.SELECT_NEXT_ROW_EXTEND));
241        map.put(new Actions(Actions.SELECT_NEXT_ROW_CHANGE_LEAD));
242        map.put(new Actions(Actions.SELECT_FIRST_ROW));
243        map.put(new Actions(Actions.SELECT_FIRST_ROW_EXTEND));
244        map.put(new Actions(Actions.SELECT_FIRST_ROW_CHANGE_LEAD));
245        map.put(new Actions(Actions.SELECT_LAST_ROW));
246        map.put(new Actions(Actions.SELECT_LAST_ROW_EXTEND));
247        map.put(new Actions(Actions.SELECT_LAST_ROW_CHANGE_LEAD));
248        map.put(new Actions(Actions.SCROLL_UP));
249        map.put(new Actions(Actions.SCROLL_UP_EXTEND));
250        map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
251        map.put(new Actions(Actions.SCROLL_DOWN));
252        map.put(new Actions(Actions.SCROLL_DOWN_EXTEND));
253        map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
254        map.put(new Actions(Actions.SELECT_ALL));
255        map.put(new Actions(Actions.CLEAR_SELECTION));
256        map.put(new Actions(Actions.ADD_TO_SELECTION));
257        map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
258        map.put(new Actions(Actions.EXTEND_TO));
259        map.put(new Actions(Actions.MOVE_SELECTION_TO));
260
261        map.put(TransferHandler.getCutAction().getValue(Action.NAME),
262                TransferHandler.getCutAction());
263        map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
264                TransferHandler.getCopyAction());
265        map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
266                TransferHandler.getPasteAction());
267    }
268
269//-------------------- X-Wrapper
270    
271    private ListModel modelX;
272
273    private ListSortUI sortUI;
274    /**
275     * Compatibility Wrapper: a synthetic model which delegates to list api and throws
276     * @return
277     */
278    protected ListModel getViewModel() {
279        if (modelX == null) {
280         modelX = new ListModel() {
281            
282            @Override
283            public int getSize() {
284                return ((JXList) list).getElementCount();
285            }
286            
287            @Override
288            public Object getElementAt(int index) {
289                return ((JXList) list).getElementAt(index);
290            }
291            
292            @Override
293            public void addListDataListener(ListDataListener l) {
294               throw new UnsupportedOperationException("this is a synthetic model wrapper");
295            }
296            @Override
297            public void removeListDataListener(ListDataListener l) {
298                throw new UnsupportedOperationException("this is a synthetic model wrapper");
299                
300            }
301            
302        };
303        }
304        return modelX;
305    }
306
307    /**
308     * @return
309     */
310    protected int getElementCount() {
311        return ((JXList) list).getElementCount();
312    }
313
314    protected Object getElementAt(int viewIndex) {
315        return ((JXList) list).getElementAt(viewIndex);
316    }
317
318//--------------- api to support/control sorting/filtering
319    
320    protected ListSortUI getSortUI() {
321        return sortUI;
322    }
323    
324    /**
325     * Installs SortUI if the list has a rowSorter. Does nothing if not.
326     */
327    protected void installSortUI() {
328        if (list.getRowSorter() == null) return;
329        sortUI = new ListSortUI(list, list.getRowSorter());
330    }
331    
332    /**
333     * Dispose and null's the sortUI if installed. Does nothing if not.
334     */
335    protected void uninstallSortUI() {
336        if (sortUI == null) return;
337        sortUI.dispose();
338        sortUI = null;
339    }
340    
341    /**
342     * Called from the PropertyChangeHandler.
343     * 
344     * @param property the name of the changed property.
345     */
346    protected void updateSortUI(String property) {
347        if ("rowSorter".equals(property)) {
348            updateSortUIToRowSorterProperty();
349        }
350    }
351    /**
352     * 
353     */
354    private void updateSortUIToRowSorterProperty() {
355        uninstallSortUI();
356        installSortUI();
357    }
358    
359    /**
360     * Returns a boolean indicating whether or not the event has been processed
361     * by the sortUI. 
362     * @param e
363     * @return
364     */
365    protected boolean processedBySortUI(ListDataEvent e) {
366        if (sortUI == null)
367            return false;
368        sortUI.modelChanged(e);
369        updateLayoutStateNeeded = modelChanged;
370        redrawList();
371        return true;
372    }
373    
374    /**
375     * Returns a boolean indicating whether or not the event has been processed
376     * by the sortUI. 
377     * @param e
378     * @return
379     */
380    protected boolean processedBySortUI(ListSelectionEvent e) {
381        if (sortUI == null) return false;
382        sortUI.viewSelectionChanged(e);
383        list.repaint();
384        return true;
385    }
386
387    
388//--------------------- enhanced support
389    /**
390     * Invalidates the cell size cache and revalidates/-paints the list.
391     * 
392     */
393    public void invalidateCellSizeCache() {
394        updateLayoutStateNeeded |= 1;
395        redrawList();
396    }
397
398//---------------------  core copy
399    
400
401    /**
402     * Paint one List cell: compute the relevant state, get the "rubber stamp"
403     * cell renderer component, and then use the CellRendererPane to paint it.
404     * Subclasses may want to override this method rather than paint().
405     *
406     * @see #paint
407     */
408    protected void paintCell(
409        Graphics g,
410        int row,
411        Rectangle rowBounds,
412        ListCellRenderer cellRenderer,
413        ListModel dataModel,
414        ListSelectionModel selModel,
415        int leadIndex)
416    {
417        Object value = dataModel.getElementAt(row);
418        boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
419        boolean isSelected = selModel.isSelectedIndex(row);
420
421        Component rendererComponent =
422            cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
423
424        int cx = rowBounds.x;
425        int cy = rowBounds.y;
426        int cw = rowBounds.width;
427        int ch = rowBounds.height;
428
429        if (isFileList) {
430            // Shrink renderer to preferred size. This is mostly used on Windows
431            // where selection is only shown around the file name, instead of
432            // across the whole list cell.
433            int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
434            if (!isLeftToRight) {
435                cx += (cw - w);
436            }
437            cw = w;
438        }
439
440        rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
441    }
442
443
444    /**
445     * Paint the rows that intersect the Graphics objects clipRect.  This
446     * method calls paintCell as necessary.  Subclasses
447     * may want to override these methods.
448     *
449     * @see #paintCell
450     */
451    public void paint(Graphics g, JComponent c) {
452        Shape clip = g.getClip();
453        paintImpl(g, c);
454        g.setClip(clip);
455
456        paintDropLine(g);
457    }
458
459    private void paintImpl(Graphics g, JComponent c)
460    {
461        switch (layoutOrientation) {
462        case JList.VERTICAL_WRAP:
463            if (list.getHeight() != listHeight) {
464                updateLayoutStateNeeded |= heightChanged;
465                redrawList();
466            }
467            break;
468        case JList.HORIZONTAL_WRAP:
469            if (list.getWidth() != listWidth) {
470                updateLayoutStateNeeded |= widthChanged;
471                redrawList();
472            }
473            break;
474        default:
475            break;
476        }
477        maybeUpdateLayoutState();
478
479        ListCellRenderer renderer = list.getCellRenderer();
480        ListModel dataModel = getViewModel();
481        ListSelectionModel selModel = list.getSelectionModel();
482        int size;
483
484        if ((renderer == null) || (size = dataModel.getSize()) == 0) {
485            return;
486        }
487
488        // Determine how many columns we need to paint
489        Rectangle paintBounds = g.getClipBounds();
490
491        int startColumn, endColumn;
492        if (c.getComponentOrientation().isLeftToRight()) {
493            startColumn = convertLocationToColumn(paintBounds.x,
494                                                  paintBounds.y);
495            endColumn = convertLocationToColumn(paintBounds.x +
496                                                paintBounds.width,
497                                                paintBounds.y);
498        } else {
499            startColumn = convertLocationToColumn(paintBounds.x +
500                                                paintBounds.width,
501                                                paintBounds.y);
502            endColumn = convertLocationToColumn(paintBounds.x,
503                                                  paintBounds.y);
504        }
505        int maxY = paintBounds.y + paintBounds.height;
506        int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
507        int rowIncrement = (layoutOrientation == JList.HORIZONTAL_WRAP) ?
508                           columnCount : 1;
509
510
511        for (int colCounter = startColumn; colCounter <= endColumn;
512             colCounter++) {
513            // And then how many rows in this columnn
514            int row = convertLocationToRowInColumn(paintBounds.y, colCounter);
515            int rowCount = getRowCount(colCounter);
516            int index = getModelIndex(colCounter, row);
517            Rectangle rowBounds = getCellBounds(list, index, index);
518
519            if (rowBounds == null) {
520                // Not valid, bail!
521                return;
522            }
523            while (row < rowCount && rowBounds.y < maxY &&
524                   index < size) {
525                rowBounds.height = getHeight(colCounter, row);
526                g.setClip(rowBounds.x, rowBounds.y, rowBounds.width,
527                          rowBounds.height);
528                g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width,
529                           paintBounds.height);
530                paintCell(g, index, rowBounds, renderer, dataModel, selModel,
531                          leadIndex);
532                rowBounds.y += rowBounds.height;
533                index += rowIncrement;
534                row++;
535            }
536        }
537        // Empty out the renderer pane, allowing renderers to be gc'ed.
538        rendererPane.removeAll();
539    }
540
541
542
543    private void paintDropLine(Graphics g) {
544        JList.DropLocation loc = list.getDropLocation();
545        if (loc == null || !loc.isInsert()) {
546            return;
547        }
548        // PENDING JW: revisit ... side-effects?
549//        Color c = DefaultLookup.getColor(list, this, "List.dropLineColor", null);
550        Color c = UIManager.getColor("List.dropLineColor");
551        if (c != null) {
552            g.setColor(c);
553            Rectangle rect = getDropLineRect(loc);
554            g.fillRect(rect.x, rect.y, rect.width, rect.height);
555        }
556    }
557
558    private Rectangle getDropLineRect(JList.DropLocation loc) {
559        int size = getElementCount();
560
561        if (size == 0) {
562            Insets insets = list.getInsets();
563            if (layoutOrientation == JList.HORIZONTAL_WRAP) {
564                if (isLeftToRight) {
565                    return new Rectangle(insets.left, insets.top, DROP_LINE_THICKNESS, 20);
566                } else {
567                    return new Rectangle(list.getWidth() - DROP_LINE_THICKNESS - insets.right,
568                                         insets.top, DROP_LINE_THICKNESS, 20);
569                }
570            } else {
571                return new Rectangle(insets.left, insets.top,
572                                     list.getWidth() - insets.left - insets.right,
573                                     DROP_LINE_THICKNESS);
574            }
575        }
576
577        Rectangle rect = null;
578        int index = loc.getIndex();
579        boolean decr = false;
580
581        if (layoutOrientation == JList.HORIZONTAL_WRAP) {
582            if (index == size) {
583                decr = true;
584            } else if (index != 0 && convertModelToRow(index)
585                                         != convertModelToRow(index - 1)) {
586
587                Rectangle prev = getCellBounds(list, index - 1);
588                Rectangle me = getCellBounds(list, index);
589                Point p = loc.getDropPoint();
590                
591                if (isLeftToRight) {
592                    decr = Point2D.distance(prev.x + prev.width,
593                                            prev.y + (int)(prev.height / 2.0),
594                                            p.x, p.y)
595                           < Point2D.distance(me.x,
596                                              me.y + (int)(me.height / 2.0),
597                                              p.x, p.y);
598                } else {
599                    decr = Point2D.distance(prev.x,
600                                            prev.y + (int)(prev.height / 2.0),
601                                            p.x, p.y)
602                           < Point2D.distance(me.x + me.width,
603                                              me.y + (int)(prev.height / 2.0),
604                                              p.x, p.y);
605                }
606            }
607
608            if (decr) {
609                index--;
610                rect = getCellBounds(list, index);
611                if (isLeftToRight) {
612                    rect.x += rect.width;
613                } else {
614                    rect.x -= DROP_LINE_THICKNESS;
615                }
616            } else {
617                rect = getCellBounds(list, index);
618                if (!isLeftToRight) {
619                    rect.x += rect.width - DROP_LINE_THICKNESS;
620                }
621            }
622
623            if (rect.x >= list.getWidth()) {
624                rect.x = list.getWidth() - DROP_LINE_THICKNESS;
625            } else if (rect.x < 0) {
626                rect.x = 0;
627            }
628
629            rect.width = DROP_LINE_THICKNESS;
630        } else if (layoutOrientation == JList.VERTICAL_WRAP) {
631            if (index == size) {
632                index--;
633                rect = getCellBounds(list, index);
634                rect.y += rect.height;
635            } else if (index != 0 && convertModelToColumn(index)
636                                         != convertModelToColumn(index - 1)) {
637
638                Rectangle prev = getCellBounds(list, index - 1);
639                Rectangle me = getCellBounds(list, index);
640                Point p = loc.getDropPoint();
641                if (Point2D.distance(prev.x + (int)(prev.width / 2.0),
642                                     prev.y + prev.height,
643                                     p.x, p.y)
644                        < Point2D.distance(me.x + (int)(me.width / 2.0),
645                                           me.y,
646                                           p.x, p.y)) {
647
648                    index--;
649                    rect = getCellBounds(list, index);
650                    rect.y += rect.height;
651                } else {
652                    rect = getCellBounds(list, index);
653                }
654            } else {
655                rect = getCellBounds(list, index);
656            }
657            
658            if (rect.y >= list.getHeight()) {
659                rect.y = list.getHeight() - DROP_LINE_THICKNESS;
660            }
661            
662            rect.height = DROP_LINE_THICKNESS;
663        } else {
664            if (index == size) {
665                index--;
666                rect = getCellBounds(list, index);
667                rect.y += rect.height;
668            } else {
669                rect = getCellBounds(list, index);
670            }
671
672            if (rect.y >= list.getHeight()) {
673                rect.y = list.getHeight() - DROP_LINE_THICKNESS;
674            }
675
676            rect.height = DROP_LINE_THICKNESS;
677        }
678
679        return rect;
680    }
681
682    /**
683     * Returns the baseline.
684     *
685     * @throws NullPointerException {@inheritDoc}
686     * @throws IllegalArgumentException {@inheritDoc}
687     * @see javax.swing.JComponent#getBaseline(int, int)
688     * @since 1.6
689     */
690    public int getBaseline(JComponent c, int width, int height) {
691        super.getBaseline(c, width, height);
692        int rowHeight = list.getFixedCellHeight();
693        UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
694        Component renderer = (Component)lafDefaults.get(
695                BASELINE_COMPONENT_KEY);
696        if (renderer == null) {
697            ListCellRenderer lcr = (ListCellRenderer)UIManager.get(
698                    "List.cellRenderer");
699
700            // fix for 6711072 some LAFs like Nimbus do not provide this
701            // UIManager key and we should not through a NPE here because of it
702            if (lcr == null) {
703                lcr = new DefaultListCellRenderer();
704            }
705            
706            renderer = lcr.getListCellRendererComponent(
707                    list, "a", -1, false, false);
708            lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
709        }
710        renderer.setFont(list.getFont());
711        // JList actually has much more complex behavior here.
712        // If rowHeight != -1 the rowHeight is either the max of all cell
713        // heights (layout orientation != VERTICAL), or is variable depending
714        // upon the cell.  We assume a default size.
715        // We could theoretically query the real renderer, but that would
716        // not work for an empty model and the results may vary with 
717        // the content.
718        if (rowHeight == -1) {
719            rowHeight = renderer.getPreferredSize().height;
720        }
721        return renderer.getBaseline(Integer.MAX_VALUE, rowHeight) +
722                list.getInsets().top;
723    }
724
725    /**
726     * Returns an enum indicating how the baseline of the component
727     * changes as the size changes.
728     *
729     * @throws NullPointerException {@inheritDoc}
730     * @see javax.swing.JComponent#getBaseline(int, int)
731     * @since 1.6
732     */
733    public Component.BaselineResizeBehavior getBaselineResizeBehavior(
734            JComponent c) {
735        super.getBaselineResizeBehavior(c);
736        return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
737    }
738
739    /**
740     * The preferredSize of the list depends upon the layout orientation.
741     * <table summary="Describes the preferred size for each layout orientation">
742     * <tr><th>Layout Orientation</th><th>Preferred Size</th></tr>
743     * <tr>
744     *   <td>JList.VERTICAL
745     *   <td>The preferredSize of the list is total height of the rows
746     *       and the maximum width of the cells.  If JList.fixedCellHeight
747     *       is specified then the total height of the rows is just
748     *       (cellVerticalMargins + fixedCellHeight) * model.getSize() where
749     *       rowVerticalMargins is the space we allocate for drawing
750     *       the yellow focus outline.  Similarly if fixedCellWidth is
751     *       specified then we just use that.
752     *   </td>
753     * <tr>
754     *   <td>JList.VERTICAL_WRAP
755     *   <td>If the visible row count is greater than zero, the preferredHeight
756     *       is the maximum cell height * visibleRowCount. If the visible row
757     *       count is <= 0, the preferred height is either the current height
758     *       of the list, or the maximum cell height, whichever is
759     *       bigger. The preferred width is than the maximum cell width *
760     *       number of columns needed. Where the number of columns needs is
761     *       list.height / max cell height. Max cell height is either the fixed
762     *       cell height, or is determined by iterating through all the cells
763     *       to find the maximum height from the ListCellRenderer.
764     * <tr>
765     *   <td>JList.HORIZONTAL_WRAP
766     *   <td>If the visible row count is greater than zero, the preferredHeight
767     *       is the maximum cell height * adjustedRowCount.  Where
768     *       visibleRowCount is used to determine the number of columns.
769     *       Because this lays out horizontally the number of rows is
770     *       then determined from the column count.  For example, lets say
771     *       you have a model with 10 items and the visible row count is 8.
772     *       The number of columns needed to display this is 2, but you no
773     *       longer need 8 rows to display this, you only need 5, thus
774     *       the adjustedRowCount is 5.
775     *       <p>If the visible row
776     *       count is <= 0, the preferred height is dictated by the 
777     *       number of columns, which will be as many as can fit in the width
778     *       of the <code>JList</code> (width / max cell width), with at
779     *       least one column.  The preferred height then becomes the
780     *       model size / number of columns * maximum cell height.
781     *       Max cell height is either the fixed
782     *       cell height, or is determined by iterating through all the cells
783     *       to find the maximum height from the ListCellRenderer.
784     * </table>
785     * The above specifies the raw preferred width and height. The resulting
786     * preferred width is the above width + insets.left + insets.right and
787     * the resulting preferred height is the above height + insets.top +
788     * insets.bottom. Where the <code>Insets</code> are determined from
789     * <code>list.getInsets()</code>.
790     *
791     * @param c The JList component.
792     * @return The total size of the list.
793     */
794    public Dimension getPreferredSize(JComponent c) {
795        maybeUpdateLayoutState();
796
797        int lastRow = getElementCount() - 1;
798        if (lastRow < 0) {
799            return new Dimension(0, 0);
800        }
801
802        Insets insets = list.getInsets();
803        int width = cellWidth * columnCount + insets.left + insets.right;
804        int height;
805
806        if (layoutOrientation != JList.VERTICAL) {
807            height = preferredHeight;
808        }
809        else {
810            Rectangle bounds = getCellBounds(list, lastRow);
811
812            if (bounds != null) {
813                height = bounds.y + bounds.height + insets.bottom;
814            }
815            else {
816                height = 0;
817            }
818        }
819        return new Dimension(width, height);
820    }
821
822
823    /**
824     * Selected the previous row and force it to be visible.
825     *
826     * @see JList#ensureIndexIsVisible
827     */
828    protected void selectPreviousIndex() {
829        int s = list.getSelectedIndex();
830        if(s > 0) {
831            s -= 1;
832            list.setSelectedIndex(s);
833            list.ensureIndexIsVisible(s);
834        }
835    }
836
837
838    /**
839     * Selected the previous row and force it to be visible.
840     *
841     * @see JList#ensureIndexIsVisible
842     */
843    protected void selectNextIndex()
844    {
845        int s = list.getSelectedIndex();
846        if((s + 1) < getElementCount()) {
847            s += 1;
848            list.setSelectedIndex(s);
849            list.ensureIndexIsVisible(s);
850        }
851    }
852
853
854    /**
855     * Registers the keyboard bindings on the <code>JList</code> that the
856     * <code>BasicXListUI</code> is associated with. This method is called at
857     * installUI() time.
858     *
859     * @see #installUI
860     */
861    protected void installKeyboardActions() {
862        InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
863
864        SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
865                                           inputMap);
866
867        LazyActionMap.installLazyActionMap(list, BasicXListUI.class,
868                                           "XList.actionMap");
869    }
870
871    InputMap getInputMap(int condition) {
872        if (condition == JComponent.WHEN_FOCUSED) {
873            // PENDING JW: side-effect when reverting to ui manager? revisit!
874            InputMap keyMap = (InputMap) UIManager.get("List.focusInputMap");
875//            InputMap keyMap = (InputMap)DefaultLookup.get(
876//                             list, this, "List.focusInputMap");
877            InputMap rtlKeyMap;
878
879            if (isLeftToRight ||
880                  ((rtlKeyMap = (InputMap) UIManager.get("List.focusInputMap.RightToLeft")) 
881                          == null)) {
882//                ((rtlKeyMap = (InputMap)DefaultLookup.get(list, this,
883//                              "List.focusInputMap.RightToLeft")) == null)) {
884                    return keyMap;
885            } else {
886                rtlKeyMap.setParent(keyMap);
887                return rtlKeyMap;
888            }
889        }
890        return null;
891    }
892
893    /**
894     * Unregisters keyboard actions installed from
895     * <code>installKeyboardActions</code>.
896     * This method is called at uninstallUI() time - subclassess should
897     * ensure that all of the keyboard actions registered at installUI
898     * time are removed here.
899     *
900     * @see #installUI
901     */
902    protected void uninstallKeyboardActions() {
903        SwingUtilities.replaceUIActionMap(list, null);
904        SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null);
905    }
906
907
908    /**
909     * Create and install the listeners for the JList, its model, and its
910     * selectionModel.  This method is called at installUI() time.
911     *
912     * @see #installUI
913     * @see #uninstallListeners
914     */
915    protected void installListeners()
916    {
917        TransferHandler th = list.getTransferHandler();
918        if (th == null || th instanceof UIResource) {
919            list.setTransferHandler(defaultTransferHandler);
920            // default TransferHandler doesn't support drop
921            // so we don't want drop handling
922            if (list.getDropTarget() instanceof UIResource) {
923                list.setDropTarget(null);
924            }
925        }
926
927        focusListener = createFocusListener();
928        mouseInputListener = createMouseInputListener();
929        propertyChangeListener = createPropertyChangeListener();
930        listSelectionListener = createListSelectionListener();
931        listDataListener = createListDataListener();
932
933        list.addFocusListener(focusListener);
934        list.addMouseListener(mouseInputListener);
935        list.addMouseMotionListener(mouseInputListener);
936        list.addPropertyChangeListener(propertyChangeListener);
937        list.addKeyListener(getHandler());
938        // JW: here we really want the model
939        ListModel model = list.getModel();
940        if (model != null) {
941            model.addListDataListener(listDataListener);
942        }
943
944        ListSelectionModel selectionModel = list.getSelectionModel();
945        if (selectionModel != null) {
946            selectionModel.addListSelectionListener(listSelectionListener);
947        }
948    }
949
950
951    /**
952     * Remove the listeners for the JList, its model, and its
953     * selectionModel.  All of the listener fields, are reset to
954     * null here.  This method is called at uninstallUI() time,
955     * it should be kept in sync with installListeners.
956     *
957     * @see #uninstallUI
958     * @see #installListeners
959     */
960    protected void uninstallListeners()
961    {
962        list.removeFocusListener(focusListener);
963        list.removeMouseListener(mouseInputListener);
964        list.removeMouseMotionListener(mouseInputListener);
965        list.removePropertyChangeListener(propertyChangeListener);
966        list.removeKeyListener(getHandler());
967
968        ListModel model = list.getModel();
969        if (model != null) {
970            model.removeListDataListener(listDataListener);
971        }
972
973        ListSelectionModel selectionModel = list.getSelectionModel();
974        if (selectionModel != null) {
975            selectionModel.removeListSelectionListener(listSelectionListener);
976        }
977
978        focusListener = null;
979        mouseInputListener  = null;
980        listSelectionListener = null;
981        listDataListener = null;
982        propertyChangeListener = null;
983        handler = null;
984    }
985
986
987    /**
988     * Initialize JList properties, e.g. font, foreground, and background,
989     * and add the CellRendererPane.  The font, foreground, and background
990     * properties are only set if their current value is either null
991     * or a UIResource, other properties are set if the current
992     * value is null.
993     *
994     * @see #uninstallDefaults
995     * @see #installUI
996     * @see CellRendererPane
997     */
998    protected void installDefaults()
999    {
1000        list.setLayout(null);
1001
1002        LookAndFeel.installBorder(list, "List.border");
1003
1004        LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
1005
1006        LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
1007
1008        if (list.getCellRenderer() == null) {
1009            list.setCellRenderer((ListCellRenderer)(UIManager.get("List.cellRenderer")));
1010        }
1011
1012        Color sbg = list.getSelectionBackground();
1013        if (sbg == null || sbg instanceof UIResource) {
1014            list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
1015        }
1016
1017        Color sfg = list.getSelectionForeground();
1018        if (sfg == null || sfg instanceof UIResource) {
1019            list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
1020        }
1021
1022        Long l = (Long)UIManager.get("List.timeFactor");
1023        timeFactor = (l!=null) ? l.longValue() : 1000L;
1024
1025        updateIsFileList();
1026    }
1027
1028    private void updateIsFileList() {
1029        boolean b = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
1030        if (b != isFileList) {
1031            isFileList = b;
1032            Font oldFont = list.getFont();
1033            if (oldFont == null || oldFont instanceof UIResource) {
1034                Font newFont = UIManager.getFont(b ? "FileChooser.listFont" : "List.font");
1035                if (newFont != null && newFont != oldFont) {
1036                    list.setFont(newFont);
1037                }
1038            }
1039        }
1040    }
1041
1042
1043    /**
1044     * Set the JList properties that haven't been explicitly overridden to
1045     * null.  A property is considered overridden if its current value
1046     * is not a UIResource.
1047     *
1048     * @see #installDefaults
1049     * @see #uninstallUI
1050     * @see CellRendererPane
1051     */
1052    protected void uninstallDefaults()
1053    {
1054        LookAndFeel.uninstallBorder(list);
1055        if (list.getFont() instanceof UIResource) {
1056            list.setFont(null);
1057        }
1058        if (list.getForeground() instanceof UIResource) {
1059            list.setForeground(null);
1060        }
1061        if (list.getBackground() instanceof UIResource) {
1062            list.setBackground(null);
1063        }
1064        if (list.getSelectionBackground() instanceof UIResource) {
1065            list.setSelectionBackground(null);
1066        }
1067        if (list.getSelectionForeground() instanceof UIResource) {
1068            list.setSelectionForeground(null);
1069        }
1070        if (list.getCellRenderer() instanceof UIResource) {
1071            list.setCellRenderer(null);
1072        }
1073        if (list.getTransferHandler() instanceof UIResource) {
1074            list.setTransferHandler(null);
1075        }
1076    }
1077
1078
1079    /**
1080     * Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
1081     * <code>installListeners()</code>, and <code>installKeyboardActions()</code>
1082     * in order.
1083     *
1084     * @see #installDefaults
1085     * @see #installListeners
1086     * @see #installKeyboardActions
1087     */
1088    public void installUI(JComponent c)
1089    {
1090        list = (JXList)c;
1091
1092        layoutOrientation = list.getLayoutOrientation();
1093
1094        rendererPane = new CellRendererPane();
1095        list.add(rendererPane);
1096
1097        columnCount = 1;
1098
1099        updateLayoutStateNeeded = modelChanged;
1100        isLeftToRight = list.getComponentOrientation().isLeftToRight();
1101
1102        installDefaults();
1103        installListeners();
1104        installKeyboardActions();
1105        installSortUI();
1106    }
1107
1108
1109    /**
1110     * Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
1111     * <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
1112     * in order.  Sets this.list to null.
1113     *
1114     * @see #uninstallListeners
1115     * @see #uninstallKeyboardActions
1116     * @see #uninstallDefaults
1117     */
1118    public void uninstallUI(JComponent c)
1119    {
1120        uninstallSortUI();
1121        uninstallListeners();
1122        uninstallDefaults();
1123        uninstallKeyboardActions();
1124
1125        cellWidth = cellHeight = -1;
1126        cellHeights = null;
1127
1128        listWidth = listHeight = -1;
1129
1130        list.remove(rendererPane);
1131        rendererPane = null;
1132        list = null;
1133    }
1134
1135
1136    /**
1137     * Returns a new instance of BasicXListUI.  BasicXListUI delegates are
1138     * allocated one per JList.
1139     *
1140     * @return A new ListUI implementation for the Windows look and feel.
1141     */
1142    public static ComponentUI createUI(JComponent list) {
1143        return new BasicXListUI();
1144    }
1145
1146
1147    /**
1148     * {@inheritDoc}
1149     * @throws NullPointerException {@inheritDoc}
1150     */
1151    public int locationToIndex(JList list, Point location) {
1152        maybeUpdateLayoutState();
1153        return convertLocationToModel(location.x, location.y);
1154    }
1155
1156
1157    /**
1158     * {@inheritDoc}
1159     */
1160    public Point indexToLocation(JList list, int index) {
1161        maybeUpdateLayoutState();
1162        Rectangle rect = getCellBounds(list, index, index);
1163
1164        if (rect != null) {
1165            return new Point(rect.x, rect.y);
1166        }
1167        return null;
1168    }
1169
1170
1171    /**
1172     * {@inheritDoc}
1173     */
1174    public Rectangle getCellBounds(JList list, int index1, int index2) {
1175        maybeUpdateLayoutState();
1176
1177        int minIndex = Math.min(index1, index2);
1178        int maxIndex = Math.max(index1, index2);
1179
1180        if (minIndex >= getElementCount()) {
1181            return null;
1182        }
1183
1184        Rectangle minBounds = getCellBounds(list, minIndex);
1185
1186        if (minBounds == null) {
1187            return null;
1188        }
1189        if (minIndex == maxIndex) {
1190            return minBounds;
1191        }
1192        Rectangle maxBounds = getCellBounds(list, maxIndex);
1193
1194        if (maxBounds != null) {
1195            if (layoutOrientation == JList.HORIZONTAL_WRAP) {
1196                int minRow = convertModelToRow(minIndex);
1197                int maxRow = convertModelToRow(maxIndex);
1198
1199                if (minRow != maxRow) {
1200                    minBounds.x = 0;
1201                    minBounds.width = list.getWidth();
1202                }
1203            }
1204            else if (minBounds.x != maxBounds.x) {
1205                // Different columns
1206                minBounds.y = 0;
1207                minBounds.height = list.getHeight();
1208            }
1209            minBounds.add(maxBounds);
1210        }
1211        return minBounds;
1212    }
1213
1214    /**
1215     * Gets the bounds of the specified model index, returning the resulting
1216     * bounds, or null if <code>index</code> is not valid.
1217     */
1218    private Rectangle getCellBounds(JList list, int index) {
1219        maybeUpdateLayoutState();
1220
1221        int row = convertModelToRow(index);
1222        int column = convertModelToColumn(index);
1223
1224        if (row == -1 || column == -1) {
1225            return null;
1226        }
1227
1228        Insets insets = list.getInsets();
1229        int x;
1230        int w = cellWidth;
1231        int y = insets.top;
1232        int h;
1233        switch (layoutOrientation) {
1234        case JList.VERTICAL_WRAP:
1235        case JList.HORIZONTAL_WRAP:
1236            if (isLeftToRight) {
1237                x = insets.left + column * cellWidth;
1238            } else {
1239                x = list.getWidth() - insets.right - (column+1) * cellWidth;
1240            }
1241            y += cellHeight * row;
1242            h = cellHeight;
1243            break;
1244        default:
1245            x = insets.left;
1246            if (cellHeights == null) {
1247                y += (cellHeight * row);
1248            }
1249            else if (row >= cellHeights.length) {
1250                y = 0;
1251            }
1252            else {
1253                for(int i = 0; i < row; i++) {
1254                    y += cellHeights[i];
1255                }
1256            }
1257            w = list.getWidth() - (insets.left + insets.right);
1258            h = getRowHeight(index);
1259            break;
1260        }
1261        return new Rectangle(x, y, w, h);
1262    }
1263
1264    /**
1265     * Returns the height of the specified row based on the current layout.
1266     *
1267     * @return The specified row height or -1 if row isn't valid.
1268     * @see #convertYToRow
1269     * @see #convertRowToY
1270     * @see #updateLayoutState
1271     */
1272    protected int getRowHeight(int row)
1273    {
1274        return getHeight(0, row);
1275    }
1276
1277
1278    /**
1279     * Convert the JList relative coordinate to the row that contains it,
1280     * based on the current layout.  If y0 doesn't fall within any row,
1281     * return -1.
1282     *
1283     * @return The row that contains y0, or -1.
1284     * @see #getRowHeight
1285     * @see #updateLayoutState
1286     */
1287    protected int convertYToRow(int y0)
1288    {
1289        return convertLocationToRow(0, y0, false);
1290    }
1291
1292
1293    /**
1294     * Return the JList relative Y coordinate of the origin of the specified
1295     * row or -1 if row isn't valid.
1296     *
1297     * @return The Y coordinate of the origin of row, or -1.
1298     * @see #getRowHeight
1299     * @see #updateLayoutState
1300     */
1301    protected int convertRowToY(int row)
1302    {
1303        if (row >= getRowCount(0) || row < 0) {
1304            return -1;
1305        }
1306        Rectangle bounds = getCellBounds(list, row, row);
1307        return bounds.y;
1308    }
1309
1310    /**
1311     * Returns the height of the cell at the passed in location.
1312     */
1313    private int getHeight(int column, int row) {
1314        if (column < 0 || column > columnCount || row < 0) {
1315            return -1;
1316        }
1317        if (layoutOrientation != JList.VERTICAL) {
1318            return cellHeight;
1319        }
1320        if (row >= getElementCount()) {
1321            return -1;
1322        }
1323        return (cellHeights == null) ? cellHeight :
1324                           ((row < cellHeights.length) ? cellHeights[row] : -1);
1325    }
1326
1327    /**
1328     * Returns the row at location x/y.
1329     *
1330     * @param closest If true and the location doesn't exactly match a
1331     *                particular location, this will return the closest row.
1332     */
1333    private int convertLocationToRow(int x, int y0, boolean closest) {
1334        int size = getElementCount();
1335
1336        if (size <= 0) {
1337            return -1;
1338        }
1339        Insets insets = list.getInsets();
1340        if (cellHeights == null) {
1341            int row = (cellHeight == 0) ? 0 :
1342                           ((y0 - insets.top) / cellHeight);
1343            if (closest) {
1344                if (row < 0) {
1345                    row = 0;
1346                }
1347                else if (row >= size) {
1348                    row = size - 1;
1349                }
1350            }
1351            return row;
1352        }
1353        else if (size > cellHeights.length) {
1354            return -1;
1355        }
1356        else {
1357            int y = insets.top;
1358            int row = 0;
1359
1360            if (closest && y0 < y) {
1361                return 0;
1362            }
1363            int i;
1364            for (i = 0; i < size; i++) {
1365                if ((y0 >= y) && (y0 < y + cellHeights[i])) {
1366                    return row;
1367                }
1368                y += cellHeights[i];
1369                row += 1;
1370            }
1371            return i - 1;
1372        }
1373    }
1374
1375    /**
1376     * Returns the closest row that starts at the specified y-location
1377     * in the passed in column.
1378     */
1379    private int convertLocationToRowInColumn(int y, int column) {
1380        int x = 0;
1381
1382        if (layoutOrientation != JList.VERTICAL) {
1383            if (isLeftToRight) {
1384                x = column * cellWidth;
1385            } else {
1386                x = list.getWidth() - (column+1)*cellWidth - list.getInsets().right;
1387            } 
1388        }
1389        return convertLocationToRow(x, y, true);
1390    }
1391
1392    /**
1393     * Returns the closest location to the model index of the passed in
1394     * location.
1395     */
1396    private int convertLocationToModel(int x, int y) {
1397        int row = convertLocationToRow(x, y, true);
1398        int column = convertLocationToColumn(x, y);
1399
1400        if (row >= 0 && column >= 0) {
1401            return getModelIndex(column, row);
1402        }
1403        return -1;
1404    }
1405
1406    /**
1407     * Returns the number of rows in the given column.
1408     */
1409    private int getRowCount(int column) {
1410        if (column < 0 || column >= columnCount) {
1411            return -1;
1412        }
1413        if (layoutOrientation == JList.VERTICAL ||
1414                  (column == 0 && columnCount == 1)) {
1415            return getElementCount();
1416        }
1417        if (column >= columnCount) {
1418            return -1;
1419        }
1420        if (layoutOrientation == JList.VERTICAL_WRAP) {
1421            if (column < (columnCount - 1)) {
1422                return rowsPerColumn;
1423            }
1424            return getElementCount() - (columnCount - 1) *
1425                        rowsPerColumn;
1426        }
1427        // JList.HORIZONTAL_WRAP
1428        int diff = columnCount - (columnCount * rowsPerColumn -
1429                                  getElementCount());
1430
1431        if (column >= diff) {
1432            return Math.max(0, rowsPerColumn - 1);
1433        }
1434        return rowsPerColumn;
1435    }
1436
1437    /**
1438     * Returns the model index for the specified display location.
1439     * If <code>column</code>x<code>row</code> is beyond the length of the
1440     * model, this will return the model size - 1.
1441     */
1442    private int getModelIndex(int column, int row) {
1443        switch (layoutOrientation) {
1444        case JList.VERTICAL_WRAP:
1445            return Math.min(getElementCount() - 1, rowsPerColumn *
1446                            column + Math.min(row, rowsPerColumn-1));
1447        case JList.HORIZONTAL_WRAP:
1448            return Math.min(getElementCount() - 1, row * columnCount +
1449                            column);
1450        default:
1451            return row;
1452        }
1453    }
1454
1455    /**
1456     * Returns the closest column to the passed in location.
1457     */
1458    private int convertLocationToColumn(int x, int y) {
1459        if (cellWidth > 0) {
1460            if (layoutOrientation == JList.VERTICAL) {
1461                return 0;
1462            }
1463            Insets insets = list.getInsets();
1464            int col;
1465            if (isLeftToRight) {
1466                col = (x - insets.left) / cellWidth;
1467            } else { 
1468                col = (list.getWidth() - x - insets.right - 1) / cellWidth;
1469            }
1470            if (col < 0) {
1471                return 0;
1472            }
1473            else if (col >= columnCount) {
1474                return columnCount - 1;
1475            }
1476            return col;
1477        }
1478        return 0;
1479    }
1480
1481    /**
1482     * Returns the row that the model index <code>index</code> will be
1483     * displayed in..
1484     */
1485    private int convertModelToRow(int index) {
1486        int size = getElementCount();
1487
1488        if ((index < 0) || (index >= size)) {
1489            return -1;
1490        }
1491
1492        if (layoutOrientation != JList.VERTICAL && columnCount > 1 &&
1493                                                   rowsPerColumn > 0) {
1494            if (layoutOrientation == JList.VERTICAL_WRAP) {
1495                return index % rowsPerColumn;
1496            }
1497            return index / columnCount;
1498        }
1499        return index;
1500    }
1501
1502    /**
1503     * Returns the column that the model index <code>index</code> will be
1504     * displayed in.
1505     */
1506    private int convertModelToColumn(int index) {
1507        int size = getElementCount();
1508
1509        if ((index < 0) || (index >= size)) {
1510            return -1;
1511        }
1512
1513        if (layoutOrientation != JList.VERTICAL && rowsPerColumn > 0 &&
1514                                                   columnCount > 1) {
1515            if (layoutOrientation == JList.VERTICAL_WRAP) {
1516                return index / rowsPerColumn;
1517            }
1518            return index % columnCount;
1519        }
1520        return 0;
1521    }
1522
1523    /**
1524     * If updateLayoutStateNeeded is non zero, call updateLayoutState() and reset
1525     * updateLayoutStateNeeded.  This method should be called by methods
1526     * before doing any computation based on the geometry of the list.
1527     * For example it's the first call in paint() and getPreferredSize().
1528     *
1529     * @see #updateLayoutState
1530     */
1531    protected void maybeUpdateLayoutState()
1532    {
1533        if (updateLayoutStateNeeded != 0) {
1534            updateLayoutState();
1535            updateLayoutStateNeeded = 0;
1536        }
1537    }
1538
1539
1540    /**
1541     * Recompute the value of cellHeight or cellHeights based
1542     * and cellWidth, based on the current font and the current
1543     * values of fixedCellWidth, fixedCellHeight, and prototypeCellValue.
1544     *
1545     * @see #maybeUpdateLayoutState
1546     */
1547    protected void updateLayoutState()
1548    {
1549        /* If both JList fixedCellWidth and fixedCellHeight have been
1550         * set, then initialize cellWidth and cellHeight, and set
1551         * cellHeights to null.
1552         */
1553
1554        int fixedCellHeight = list.getFixedCellHeight();
1555        int fixedCellWidth = list.getFixedCellWidth();
1556
1557        cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
1558
1559        if (fixedCellHeight != -1) {
1560            cellHeight = fixedCellHeight;
1561            cellHeights = null;
1562        }
1563        else {
1564            cellHeight = -1;
1565            cellHeights = new int[getElementCount()];
1566        }
1567
1568        /* If either of  JList fixedCellWidth and fixedCellHeight haven't
1569         * been set, then initialize cellWidth and cellHeights by
1570         * scanning through the entire model.  Note: if the renderer is
1571         * null, we just set cellWidth and cellHeights[*] to zero,
1572         * if they're not set already.
1573         */
1574
1575        if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
1576
1577            ListModel dataModel = getViewModel();
1578            int dataModelSize = dataModel.getSize();
1579            ListCellRenderer renderer = list.getCellRenderer();
1580
1581            if (renderer != null) {
1582                for(int index = 0; index < dataModelSize; index++) {
1583                    Object value = dataModel.getElementAt(index);
1584                    Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
1585                    rendererPane.add(c);
1586                    Dimension cellSize = c.getPreferredSize();
1587                    if (fixedCellWidth == -1) {
1588                        cellWidth = Math.max(cellSize.width, cellWidth);
1589                    }
1590                    if (fixedCellHeight == -1) {
1591                        cellHeights[index] = cellSize.height;
1592                    }
1593                }
1594            }
1595            else {
1596                if (cellWidth == -1) {
1597                    cellWidth = 0;
1598                }
1599                if (cellHeights == null) {
1600                    cellHeights = new int[dataModelSize];
1601                }
1602                for(int index = 0; index < dataModelSize; index++) {
1603                    cellHeights[index] = 0;
1604                }
1605            }
1606        }
1607
1608        columnCount = 1;
1609        if (layoutOrientation != JList.VERTICAL) {
1610            updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
1611        }
1612    }
1613
1614    /**
1615     * Invoked when the list is layed out horizontally to determine how
1616     * many columns to create.
1617     * <p>
1618     * This updates the <code>rowsPerColumn, </code><code>columnCount</code>,
1619     * <code>preferredHeight</code> and potentially <code>cellHeight</code>
1620     * instance variables.
1621     */
1622    private void updateHorizontalLayoutState(int fixedCellWidth,
1623                                             int fixedCellHeight) {
1624        int visRows = list.getVisibleRowCount();
1625        int dataModelSize = getElementCount();
1626        Insets insets = list.getInsets();
1627
1628        listHeight = list.getHeight();
1629        listWidth = list.getWidth();
1630
1631        if (dataModelSize == 0) {
1632            rowsPerColumn = columnCount = 0;
1633            preferredHeight = insets.top + insets.bottom;
1634            return;
1635        }
1636
1637        int height;
1638
1639        if (fixedCellHeight != -1) {
1640            height = fixedCellHeight;
1641        }
1642        else {
1643            // Determine the max of the renderer heights.
1644            int maxHeight = 0;
1645            if (cellHeights.length > 0) {
1646                maxHeight = cellHeights[cellHeights.length - 1];
1647                for (int counter = cellHeights.length - 2;
1648                     counter >= 0; counter--) {
1649                    maxHeight = Math.max(maxHeight, cellHeights[counter]);
1650                }
1651            }
1652            height = cellHeight = maxHeight;
1653            cellHeights = null;
1654        }
1655        // The number of rows is either determined by the visible row
1656        // count, or by the height of the list.
1657        rowsPerColumn = dataModelSize;
1658        if (visRows > 0) {
1659            rowsPerColumn = visRows;
1660            columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1661            if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
1662                dataModelSize % rowsPerColumn != 0) {
1663                columnCount++;
1664            }
1665            if (layoutOrientation == JList.HORIZONTAL_WRAP) {
1666                // Because HORIZONTAL_WRAP flows differently, the 
1667                // rowsPerColumn needs to be adjusted.
1668                rowsPerColumn = (dataModelSize / columnCount);
1669                if (dataModelSize % columnCount > 0) {
1670                    rowsPerColumn++;
1671                }
1672            }
1673        }
1674        else if (layoutOrientation == JList.VERTICAL_WRAP && height != 0) {
1675            rowsPerColumn = Math.max(1, (listHeight - insets.top -
1676                                         insets.bottom) / height);
1677            columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1678            if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
1679                dataModelSize % rowsPerColumn != 0) {
1680                columnCount++;
1681            }
1682        }
1683        else if (layoutOrientation == JList.HORIZONTAL_WRAP && cellWidth > 0 &&
1684                 listWidth > 0) {
1685            columnCount = Math.max(1, (listWidth - insets.left -
1686                                       insets.right) / cellWidth);
1687            rowsPerColumn = dataModelSize / columnCount;
1688            if (dataModelSize % columnCount > 0) {
1689                rowsPerColumn++;
1690            }
1691        }
1692        preferredHeight = rowsPerColumn * cellHeight + insets.top +
1693                              insets.bottom;
1694    }
1695
1696    private Handler getHandler() {
1697        if (handler == null) {
1698            handler = new Handler();
1699        }
1700        return handler;
1701    }
1702
1703    /**
1704     * Mouse input, and focus handling for JList.  An instance of this
1705     * class is added to the appropriate java.awt.Component lists
1706     * at installUI() time.  Note keyboard input is handled with JComponent
1707     * KeyboardActions, see installKeyboardActions().
1708     * <p>
1709     * <strong>Warning:</strong>
1710     * Serialized objects of this class will not be compatible with
1711     * future Swing releases. The current serialization support is
1712     * appropriate for short term storage or RMI between applications running
1713     * the same version of Swing.  As of 1.4, support for long term storage
1714     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1715     * has been added to the <code>java.beans</code> package.
1716     * Please see {@link java.beans.XMLEncoder}.
1717     *
1718     * @see #createMouseInputListener
1719     * @see #installKeyboardActions
1720     * @see #installUI
1721     */
1722    public class MouseInputHandler implements MouseInputListener
1723    {
1724        public void mouseClicked(MouseEvent e) {
1725            getHandler().mouseClicked(e);
1726        }
1727
1728        public void mouseEntered(MouseEvent e) {
1729            getHandler().mouseEntered(e);
1730        }
1731
1732        public void mouseExited(MouseEvent e) {
1733            getHandler().mouseExited(e);
1734        }
1735
1736        public void mousePressed(MouseEvent e) {
1737            getHandler().mousePressed(e);
1738        }
1739
1740        public void mouseDragged(MouseEvent e) {
1741            getHandler().mouseDragged(e);
1742        }
1743
1744        public void mouseMoved(MouseEvent e) {
1745            getHandler().mouseMoved(e);
1746        }
1747
1748        public void mouseReleased(MouseEvent e) {
1749            getHandler().mouseReleased(e);
1750        }
1751    }
1752
1753
1754    /**
1755     * Creates a delegate that implements MouseInputListener.
1756     * The delegate is added to the corresponding java.awt.Component listener 
1757     * lists at installUI() time. Subclasses can override this method to return 
1758     * a custom MouseInputListener, e.g.
1759     * <pre>
1760     * class MyListUI extends BasicXListUI {
1761     *    protected MouseInputListener <b>createMouseInputListener</b>() {
1762     *        return new MyMouseInputHandler();
1763     *    }
1764     *    public class MyMouseInputHandler extends MouseInputHandler {
1765     *        public void mouseMoved(MouseEvent e) {
1766     *            // do some extra work when the mouse moves
1767     *            super.mouseMoved(e);
1768     *        }
1769     *    }
1770     * }
1771     * </pre>
1772     *
1773     * @see MouseInputHandler
1774     * @see #installUI
1775     */
1776    protected MouseInputListener createMouseInputListener() {
1777        return getHandler();
1778    }
1779
1780    /**
1781     * This inner class is marked &quot;public&quot; due to a compiler bug.
1782     * This class should be treated as a &quot;protected&quot; inner class.
1783     * Instantiate it only within subclasses of BasicTableUI.
1784     */
1785    public class FocusHandler implements FocusListener
1786    {
1787        protected void repaintCellFocus()
1788        {
1789            getHandler().repaintCellFocus();
1790        }
1791
1792        /* The focusGained() focusLost() methods run when the JList
1793         * focus changes.
1794         */
1795
1796        public void focusGained(FocusEvent e) {
1797            getHandler().focusGained(e);
1798        }
1799
1800        public void focusLost(FocusEvent e) {
1801            getHandler().focusLost(e);
1802        }
1803    }
1804
1805    protected FocusListener createFocusListener() {
1806        return getHandler();
1807    }
1808
1809    /**
1810     * The ListSelectionListener that's added to the JLists selection
1811     * model at installUI time, and whenever the JList.selectionModel property
1812     * changes.  When the selection changes we repaint the affected rows.
1813     * <p>
1814     * <strong>Warning:</strong>
1815     * Serialized objects of this class will not be compatible with
1816     * future Swing releases. The current serialization support is
1817     * appropriate for short term storage or RMI between applications running
1818     * the same version of Swing.  As of 1.4, support for long term storage
1819     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1820     * has been added to the <code>java.beans</code> package.
1821     * Please see {@link java.beans.XMLEncoder}.
1822     *
1823     * @see #createListSelectionListener
1824     * @see #getCellBounds
1825     * @see #installUI
1826     */
1827    public class ListSelectionHandler implements ListSelectionListener
1828    {
1829        public void valueChanged(ListSelectionEvent e)
1830        {
1831            if (processedBySortUI(e)) return;
1832            getHandler().valueChanged(e);
1833        }
1834    }
1835
1836
1837    /**
1838     * Creates an instance of ListSelectionHandler that's added to
1839     * the JLists by selectionModel as needed.  Subclasses can override
1840     * this method to return a custom ListSelectionListener, e.g.
1841     * <pre>
1842     * class MyListUI extends BasicXListUI {
1843     *    protected ListSelectionListener <b>createListSelectionListener</b>() {
1844     *        return new MySelectionListener();
1845     *    }
1846     *    public class MySelectionListener extends ListSelectionHandler {
1847     *        public void valueChanged(ListSelectionEvent e) {
1848     *            // do some extra work when the selection changes
1849     *            super.valueChange(e);
1850     *        }
1851     *    }
1852     * }
1853     * </pre>
1854     *
1855     * @see ListSelectionHandler
1856     * @see #installUI
1857     */
1858    protected ListSelectionListener createListSelectionListener() {
1859        return new ListSelectionHandler();
1860    }
1861
1862
1863    private void redrawList() {
1864        list.revalidate();
1865        list.repaint();
1866    }
1867
1868
1869    /**
1870     * The ListDataListener that's added to the JLists model at
1871     * installUI time, and whenever the JList.model property changes.
1872     * <p>
1873     * <strong>Warning:</strong>
1874     * Serialized objects of this class will not be compatible with
1875     * future Swing releases. The current serialization support is
1876     * appropriate for short term storage or RMI between applications running
1877     * the same version of Swing.  As of 1.4, support for long term storage
1878     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1879     * has been added to the <code>java.beans</code> package.
1880     * Please see {@link java.beans.XMLEncoder}.
1881     *
1882     * @see JList#getModel
1883     * @see #maybeUpdateLayoutState
1884     * @see #createListDataListener
1885     * @see #installUI
1886     */
1887    public class ListDataHandler implements ListDataListener
1888    {
1889        public void intervalAdded(ListDataEvent e) {
1890            if (processedBySortUI(e))
1891                return;
1892            getHandler().intervalAdded(e);
1893        }
1894
1895
1896        public void intervalRemoved(ListDataEvent e) {
1897            if (processedBySortUI(e))
1898                return;
1899            getHandler().intervalRemoved(e);
1900        }
1901
1902
1903        public void contentsChanged(ListDataEvent e) {
1904            if (processedBySortUI(e))
1905                return;
1906            getHandler().contentsChanged(e);
1907        }
1908    }
1909
1910
1911    /**
1912     * Creates an instance of ListDataListener that's added to
1913     * the JLists by model as needed.  Subclasses can override
1914     * this method to return a custom ListDataListener, e.g.
1915     * <pre>
1916     * class MyListUI extends BasicXListUI {
1917     *    protected ListDataListener <b>createListDataListener</b>() {
1918     *        return new MyListDataListener();
1919     *    }
1920     *    public class MyListDataListener extends ListDataHandler {
1921     *        public void contentsChanged(ListDataEvent e) {
1922     *            // do some extra work when the models contents change
1923     *            super.contentsChange(e);
1924     *        }
1925     *    }
1926     * }
1927     * </pre>
1928     *
1929     * @see ListDataListener
1930     * @see JList#getModel
1931     * @see #installUI
1932     */
1933    protected ListDataListener createListDataListener() {
1934        return new ListDataHandler();
1935    }
1936
1937
1938    /**
1939     * The PropertyChangeListener that's added to the JList at
1940     * installUI time.  When the value of a JList property that
1941     * affects layout changes, we set a bit in updateLayoutStateNeeded.
1942     * If the JLists model changes we additionally remove our listeners
1943     * from the old model.  Likewise for the JList selectionModel.
1944     * <p>
1945     * <strong>Warning:</strong>
1946     * Serialized objects of this class will not be compatible with
1947     * future Swing releases. The current serialization support is
1948     * appropriate for short term storage or RMI between applications running
1949     * the same version of Swing.  As of 1.4, support for long term storage
1950     * of all JavaBeans<sup><font size="-2">TM</font></sup>
1951     * has been added to the <code>java.beans</code> package.
1952     * Please see {@link java.beans.XMLEncoder}.
1953     *
1954     * @see #maybeUpdateLayoutState
1955     * @see #createPropertyChangeListener
1956     * @see #installUI
1957     */
1958    public class PropertyChangeHandler implements PropertyChangeListener
1959    {
1960        public void propertyChange(PropertyChangeEvent e) {
1961            getHandler().propertyChange(e);
1962            updateSortUI(e.getPropertyName());
1963        }
1964    }
1965
1966    /**
1967     * Creates an instance of PropertyChangeHandler that's added to
1968     * the JList by installUI().  Subclasses can override this method
1969     * to return a custom PropertyChangeListener, e.g.
1970     * <pre>
1971     * class MyListUI extends BasicXListUI {
1972     *    protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
1973     *        return new MyPropertyChangeListener();
1974     *    }
1975     *    public class MyPropertyChangeListener extends PropertyChangeHandler {
1976     *        public void propertyChange(PropertyChangeEvent e) {
1977     *            if (e.getPropertyName().equals("model")) {
1978     *                // do some extra work when the model changes
1979     *            }
1980     *            super.propertyChange(e);
1981     *        }
1982     *    }
1983     * }
1984     * </pre>
1985     *
1986     * @see PropertyChangeListener
1987     * @see #installUI
1988     */
1989    protected PropertyChangeListener createPropertyChangeListener() {
1990        return new PropertyChangeHandler();
1991    }
1992
1993    /** Used by IncrementLeadSelectionAction. Indicates the action should
1994     * change the lead, and not select it. */
1995    private static final int CHANGE_LEAD = 0;
1996    /** Used by IncrementLeadSelectionAction. Indicates the action should
1997     * change the selection and lead. */
1998    private static final int CHANGE_SELECTION = 1;
1999    /** Used by IncrementLeadSelectionAction. Indicates the action should
2000     * extend the selection from the anchor to the next index. */
2001    private static final int EXTEND_SELECTION = 2;
2002
2003    // PENDING JW: this is not a complete replacement of sun.UIAction ...
2004    private static class Actions extends UIAction {
2005        private static final String SELECT_PREVIOUS_COLUMN =
2006                                    "selectPreviousColumn";
2007        private static final String SELECT_PREVIOUS_COLUMN_EXTEND =
2008                                    "selectPreviousColumnExtendSelection";
2009        private static final String SELECT_PREVIOUS_COLUMN_CHANGE_LEAD =
2010                                    "selectPreviousColumnChangeLead";
2011        private static final String SELECT_NEXT_COLUMN = "selectNextColumn";
2012        private static final String SELECT_NEXT_COLUMN_EXTEND =
2013                                    "selectNextColumnExtendSelection";
2014        private static final String SELECT_NEXT_COLUMN_CHANGE_LEAD =
2015                                    "selectNextColumnChangeLead";
2016        private static final String SELECT_PREVIOUS_ROW = "selectPreviousRow";
2017        private static final String SELECT_PREVIOUS_ROW_EXTEND =
2018                                     "selectPreviousRowExtendSelection";
2019        private static final String SELECT_PREVIOUS_ROW_CHANGE_LEAD =
2020                                     "selectPreviousRowChangeLead";
2021        private static final String SELECT_NEXT_ROW = "selectNextRow";
2022        private static final String SELECT_NEXT_ROW_EXTEND =
2023                                     "selectNextRowExtendSelection";
2024        private static final String SELECT_NEXT_ROW_CHANGE_LEAD =
2025                                     "selectNextRowChangeLead";
2026        private static final String SELECT_FIRST_ROW = "selectFirstRow";
2027        private static final String SELECT_FIRST_ROW_EXTEND =
2028                                     "selectFirstRowExtendSelection";
2029        private static final String SELECT_FIRST_ROW_CHANGE_LEAD =
2030                                     "selectFirstRowChangeLead";
2031        private static final String SELECT_LAST_ROW = "selectLastRow";
2032        private static final String SELECT_LAST_ROW_EXTEND =
2033                                     "selectLastRowExtendSelection";
2034        private static final String SELECT_LAST_ROW_CHANGE_LEAD =
2035                                     "selectLastRowChangeLead";
2036        private static final String SCROLL_UP = "scrollUp";
2037        private static final String SCROLL_UP_EXTEND =
2038                                     "scrollUpExtendSelection";
2039        private static final String SCROLL_UP_CHANGE_LEAD =
2040                                     "scrollUpChangeLead";
2041        private static final String SCROLL_DOWN = "scrollDown";
2042        private static final String SCROLL_DOWN_EXTEND =
2043                                     "scrollDownExtendSelection";
2044        private static final String SCROLL_DOWN_CHANGE_LEAD =
2045                                     "scrollDownChangeLead";
2046        private static final String SELECT_ALL = "selectAll";
2047        private static final String CLEAR_SELECTION = "clearSelection";
2048
2049        // add the lead item to the selection without changing lead or anchor
2050        private static final String ADD_TO_SELECTION = "addToSelection";
2051
2052        // toggle the selected state of the lead item and move the anchor to it
2053        private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
2054
2055        // extend the selection to the lead item
2056        private static final String EXTEND_TO = "extendTo";
2057
2058        // move the anchor to the lead and ensure only that item is selected
2059        private static final String MOVE_SELECTION_TO = "moveSelectionTo";
2060
2061        Actions(String name) {
2062            super(name);
2063        }
2064        public void actionPerformed(ActionEvent e) {
2065            String name = getName();
2066            JList list = (JList)e.getSource();
2067            BasicXListUI ui = (BasicXListUI)LookAndFeelUtils.getUIOfType(
2068                     list.getUI(), BasicXListUI.class);
2069
2070            if (name == SELECT_PREVIOUS_COLUMN) {
2071                changeSelection(list, CHANGE_SELECTION,
2072                                getNextColumnIndex(list, ui, -1), -1);
2073            }
2074            else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
2075                changeSelection(list, EXTEND_SELECTION,
2076                                getNextColumnIndex(list, ui, -1), -1);
2077            }
2078            else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
2079                changeSelection(list, CHANGE_LEAD,
2080                                getNextColumnIndex(list, ui, -1), -1);
2081            }
2082            else if (name == SELECT_NEXT_COLUMN) {
2083                changeSelection(list, CHANGE_SELECTION,
2084                                getNextColumnIndex(list, ui, 1), 1);
2085            }
2086            else if (name == SELECT_NEXT_COLUMN_EXTEND) {
2087                changeSelection(list, EXTEND_SELECTION,
2088                                getNextColumnIndex(list, ui, 1), 1);
2089            }
2090            else if (name == SELECT_NEXT_COLUMN_CHANGE_LEAD) {
2091                changeSelection(list, CHANGE_LEAD,
2092                                getNextColumnIndex(list, ui, 1), 1);
2093            }
2094            else if (name == SELECT_PREVIOUS_ROW) {
2095                changeSelection(list, CHANGE_SELECTION,
2096                                getNextIndex(list, ui, -1), -1);
2097            }
2098            else if (name == SELECT_PREVIOUS_ROW_EXTEND) {
2099                changeSelection(list, EXTEND_SELECTION,
2100                                getNextIndex(list, ui, -1), -1);
2101            }
2102            else if (name == SELECT_PREVIOUS_ROW_CHANGE_LEAD) {
2103                changeSelection(list, CHANGE_LEAD,
2104                                getNextIndex(list, ui, -1), -1);
2105            }
2106            else if (name == SELECT_NEXT_ROW) {
2107                changeSelection(list, CHANGE_SELECTION,
2108                                getNextIndex(list, ui, 1), 1);
2109            }
2110            else if (name == SELECT_NEXT_ROW_EXTEND) {
2111                changeSelection(list, EXTEND_SELECTION,
2112                                getNextIndex(list, ui, 1), 1);
2113            }
2114            else if (name == SELECT_NEXT_ROW_CHANGE_LEAD) {
2115                changeSelection(list, CHANGE_LEAD,
2116                                getNextIndex(list, ui, 1), 1);
2117            }
2118            else if (name == SELECT_FIRST_ROW) {
2119                changeSelection(list, CHANGE_SELECTION, 0, -1);
2120            }
2121            else if (name == SELECT_FIRST_ROW_EXTEND) {
2122                changeSelection(list, EXTEND_SELECTION, 0, -1);
2123            }
2124            else if (name == SELECT_FIRST_ROW_CHANGE_LEAD) {
2125                changeSelection(list, CHANGE_LEAD, 0, -1);
2126            }
2127            else if (name == SELECT_LAST_ROW) {
2128                changeSelection(list, CHANGE_SELECTION,
2129                                getElementCount(list) - 1, 1);
2130            }
2131            else if (name == SELECT_LAST_ROW_EXTEND) {
2132                changeSelection(list, EXTEND_SELECTION,
2133                                getElementCount(list) - 1, 1);
2134            }
2135            else if (name == SELECT_LAST_ROW_CHANGE_LEAD) {
2136                changeSelection(list, CHANGE_LEAD,
2137                                getElementCount(list) - 1, 1);
2138            }
2139            else if (name == SCROLL_UP) {
2140                changeSelection(list, CHANGE_SELECTION,
2141                                getNextPageIndex(list, -1), -1);
2142            }
2143            else if (name == SCROLL_UP_EXTEND) {
2144                changeSelection(list, EXTEND_SELECTION,
2145                                getNextPageIndex(list, -1), -1);
2146            }
2147            else if (name == SCROLL_UP_CHANGE_LEAD) {
2148                changeSelection(list, CHANGE_LEAD,
2149                                getNextPageIndex(list, -1), -1);
2150            }
2151            else if (name == SCROLL_DOWN) {
2152                changeSelection(list, CHANGE_SELECTION,
2153                                getNextPageIndex(list, 1), 1);
2154            }
2155            else if (name == SCROLL_DOWN_EXTEND) {
2156                changeSelection(list, EXTEND_SELECTION,
2157                                getNextPageIndex(list, 1), 1);
2158            }
2159            else if (name == SCROLL_DOWN_CHANGE_LEAD) {
2160                changeSelection(list, CHANGE_LEAD,
2161                                getNextPageIndex(list, 1), 1);
2162            }
2163            else if (name == SELECT_ALL) {
2164                selectAll(list);
2165            }
2166            else if (name == CLEAR_SELECTION) {
2167                clearSelection(list);
2168            }
2169            else if (name == ADD_TO_SELECTION) {
2170                int index = adjustIndex(
2171                    list.getSelectionModel().getLeadSelectionIndex(), list);
2172
2173                if (!list.isSelectedIndex(index)) {
2174                    int oldAnchor = list.getSelectionModel().getAnchorSelectionIndex();
2175                    list.setValueIsAdjusting(true);
2176                    list.addSelectionInterval(index, index);
2177                    list.getSelectionModel().setAnchorSelectionIndex(oldAnchor);
2178                    list.setValueIsAdjusting(false);
2179                }
2180            }
2181            else if (name == TOGGLE_AND_ANCHOR) {
2182                int index = adjustIndex(
2183                    list.getSelectionModel().getLeadSelectionIndex(), list);
2184
2185                if (list.isSelectedIndex(index)) {
2186                    list.removeSelectionInterval(index, index);
2187                } else {
2188                    list.addSelectionInterval(index, index);
2189                }
2190            }
2191            else if (name == EXTEND_TO) {
2192                changeSelection(
2193                    list, EXTEND_SELECTION,
2194                    adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list),
2195                    0);
2196            }
2197            else if (name == MOVE_SELECTION_TO) {
2198                changeSelection(
2199                    list, CHANGE_SELECTION,
2200                    adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list),
2201                    0);
2202            }
2203        }
2204        /**
2205         * @param list
2206         * @return
2207         */
2208        private int getElementCount(JList list) {
2209            return ((JXList) list).getElementCount();
2210        }
2211
2212        public boolean isEnabled(Object c) {
2213            Object name = getName();
2214            if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD ||
2215                    name == SELECT_NEXT_COLUMN_CHANGE_LEAD ||
2216                    name == SELECT_PREVIOUS_ROW_CHANGE_LEAD ||
2217                    name == SELECT_NEXT_ROW_CHANGE_LEAD ||
2218                    name == SELECT_FIRST_ROW_CHANGE_LEAD ||
2219                    name == SELECT_LAST_ROW_CHANGE_LEAD ||
2220                    name == SCROLL_UP_CHANGE_LEAD ||
2221                    name == SCROLL_DOWN_CHANGE_LEAD) {
2222
2223                // discontinuous selection actions are only enabled for
2224                // DefaultListSelectionModel
2225                return c != null && ((JList)c).getSelectionModel()
2226                                        instanceof DefaultListSelectionModel;
2227            }
2228
2229            return true;
2230        }
2231
2232        private void clearSelection(JList list) {
2233            list.clearSelection();
2234        }
2235
2236        private void selectAll(JList list) {
2237            int size = getElementCount(list);
2238            if (size > 0) {
2239                ListSelectionModel lsm = list.getSelectionModel();
2240                int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2241
2242                if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
2243                    if (lead == -1) {
2244                        int min = adjustIndex(list.getMinSelectionIndex(), list);
2245                        lead = (min == -1 ? 0 : min);
2246                    }
2247
2248                    list.setSelectionInterval(lead, lead);
2249                    list.ensureIndexIsVisible(lead);
2250                } else {
2251                    list.setValueIsAdjusting(true);
2252
2253                    int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2254
2255                    list.setSelectionInterval(0, size - 1);
2256
2257                    // this is done to restore the anchor and lead
2258                    SwingXUtilities.setLeadAnchorWithoutSelection(lsm, anchor, lead);
2259
2260                    list.setValueIsAdjusting(false);
2261                }
2262            }
2263        }
2264
2265        private int getNextPageIndex(JList list, int direction) {
2266            if (getElementCount(list) == 0) {
2267                return -1;
2268            }
2269
2270            int index = -1;
2271            Rectangle visRect = list.getVisibleRect();
2272            ListSelectionModel lsm = list.getSelectionModel();
2273            int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2274            Rectangle leadRect =
2275                (lead==-1) ? new Rectangle() : list.getCellBounds(lead, lead);
2276  
2277            if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2278                list.getVisibleRowCount() <= 0) {
2279                if (!list.getComponentOrientation().isLeftToRight()) {
2280                    direction = -direction;
2281                }
2282                // apply for horizontal scrolling: the step for next
2283                // page index is number of visible columns
2284                if (direction < 0) {
2285                    // left
2286                    visRect.x = leadRect.x + leadRect.width - visRect.width;
2287                    Point p = new Point(visRect.x - 1, leadRect.y);
2288                    index = list.locationToIndex(p);
2289                    Rectangle cellBounds = list.getCellBounds(index, index);
2290                    if (visRect.intersects(cellBounds)) {
2291                        p.x = cellBounds.x - 1;
2292                        index = list.locationToIndex(p);
2293                        cellBounds = list.getCellBounds(index, index);
2294                    }
2295                    // this is necessary for right-to-left orientation only
2296                    if (cellBounds.y != leadRect.y) {
2297                        p.x = cellBounds.x + cellBounds.width;
2298                        index = list.locationToIndex(p);
2299                    }
2300                }
2301                else {
2302                    // right
2303                    visRect.x = leadRect.x;
2304                    Point p = new Point(visRect.x + visRect.width, leadRect.y);
2305                    index = list.locationToIndex(p);
2306                    Rectangle cellBounds = list.getCellBounds(index, index);
2307                    if (visRect.intersects(cellBounds)) {
2308                        p.x = cellBounds.x + cellBounds.width;
2309                        index = list.locationToIndex(p);
2310                        cellBounds = list.getCellBounds(index, index);
2311                    }
2312                    if (cellBounds.y != leadRect.y) {
2313                        p.x = cellBounds.x - 1;
2314                        index = list.locationToIndex(p);
2315                    }
2316                }
2317            }
2318            else {
2319                if (direction < 0) {
2320                    // up
2321                    // go to the first visible cell
2322                    Point p = new Point(leadRect.x, visRect.y);
2323                    index = list.locationToIndex(p);
2324                    if (lead <= index) {
2325                        // if lead is the first visible cell (or above it)
2326                        // adjust the visible rect up
2327                        visRect.y = leadRect.y + leadRect.height - visRect.height;
2328                        p.y = visRect.y;
2329                        index = list.locationToIndex(p);
2330                        Rectangle cellBounds = list.getCellBounds(index, index);
2331                        // go one cell down if first visible cell doesn't fit
2332                        // into adjasted visible rectangle
2333                        if (cellBounds.y < visRect.y) {
2334                            p.y = cellBounds.y + cellBounds.height;
2335                            index = list.locationToIndex(p);
2336                            cellBounds = list.getCellBounds(index, index);
2337                        }
2338                        // if index isn't less then lead
2339                        // try to go to cell previous to lead
2340                        if (cellBounds.y >= leadRect.y) {
2341                            p.y = leadRect.y - 1;
2342                            index = list.locationToIndex(p);
2343                        }
2344                    }
2345                }
2346                else {
2347                    // down
2348                    // go to the last completely visible cell
2349                    Point p = new Point(leadRect.x,
2350                                        visRect.y + visRect.height - 1);
2351                    index = list.locationToIndex(p);
2352                    Rectangle cellBounds = list.getCellBounds(index, index);
2353                    // go up one cell if last visible cell doesn't fit
2354                    // into visible rectangle
2355                    if (cellBounds.y + cellBounds.height >
2356                        visRect.y + visRect.height) {
2357                        p.y = cellBounds.y - 1;
2358                        index = list.locationToIndex(p);
2359                        cellBounds = list.getCellBounds(index, index);
2360                        index = Math.max(index, lead);
2361                    }
2362                    
2363                    if (lead >= index) {
2364                        // if lead is the last completely visible index
2365                        // (or below it) adjust the visible rect down
2366                        visRect.y = leadRect.y;
2367                        p.y = visRect.y + visRect.height - 1;
2368                        index = list.locationToIndex(p);
2369                        cellBounds = list.getCellBounds(index, index);
2370                        // go one cell up if last visible cell doesn't fit
2371                        // into adjasted visible rectangle
2372                        if (cellBounds.y + cellBounds.height >
2373                            visRect.y + visRect.height) {
2374                            p.y = cellBounds.y - 1;
2375                            index = list.locationToIndex(p);
2376                            cellBounds = list.getCellBounds(index, index);
2377                        }
2378                        // if index isn't greater then lead
2379                        // try to go to cell next after lead
2380                        if (cellBounds.y <= leadRect.y) {
2381                            p.y = leadRect.y + leadRect.height;
2382                            index = list.locationToIndex(p);
2383                        }
2384                    }
2385                }
2386            }
2387            return index;
2388        }
2389
2390        private void changeSelection(JList list, int type,
2391                                     int index, int direction) {
2392            if (index >= 0 && index < getElementCount(list)) {
2393                ListSelectionModel lsm = list.getSelectionModel();
2394
2395                // CHANGE_LEAD is only valid with multiple interval selection
2396                if (type == CHANGE_LEAD &&
2397                        list.getSelectionMode()
2398                            != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
2399
2400                    type = CHANGE_SELECTION;
2401                }
2402
2403                // IMPORTANT - This needs to happen before the index is changed.
2404                // This is because JFileChooser, which uses JList, also scrolls
2405                // the selected item into view. If that happens first, then
2406                // this method becomes a no-op.
2407                adjustScrollPositionIfNecessary(list, index, direction);
2408
2409                if (type == EXTEND_SELECTION) {
2410                    int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2411                    if (anchor == -1) {
2412                        anchor = 0;
2413                    }
2414                    
2415                    list.setSelectionInterval(anchor, index);
2416                }
2417                else if (type == CHANGE_SELECTION) {
2418                    list.setSelectedIndex(index);
2419                }
2420                else {
2421                    // casting should be safe since the action is only enabled
2422                    // for DefaultListSelectionModel
2423                    if (lsm instanceof DefaultListSelectionModel)
2424                    ((DefaultListSelectionModel)lsm).moveLeadSelectionIndex(index);
2425                }
2426            }
2427        }
2428
2429        /**
2430         * When scroll down makes selected index the last completely visible
2431         * index. When scroll up makes selected index the first visible index.
2432         * Adjust visible rectangle respect to list's component orientation.
2433         */
2434        private void adjustScrollPositionIfNecessary(JList list, int index,
2435                                                     int direction) {
2436            if (direction == 0) {
2437                return;
2438            }
2439            Rectangle cellBounds = list.getCellBounds(index, index);
2440            Rectangle visRect = list.getVisibleRect();
2441            if (cellBounds != null && !visRect.contains(cellBounds)) {
2442                if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2443                    list.getVisibleRowCount() <= 0) {
2444                    // horizontal
2445                    if (list.getComponentOrientation().isLeftToRight()) {
2446                        if (direction > 0) {
2447                            // right for left-to-right
2448                            int x =Math.max(0,
2449                                cellBounds.x + cellBounds.width - visRect.width);
2450                            int startIndex =
2451                                list.locationToIndex(new Point(x, cellBounds.y));
2452                            Rectangle startRect = list.getCellBounds(startIndex,
2453                                                                     startIndex);
2454                            if (startRect.x < x && startRect.x < cellBounds.x) {
2455                                startRect.x += startRect.width;
2456                                startIndex =
2457                                    list.locationToIndex(startRect.getLocation());
2458                                startRect = list.getCellBounds(startIndex,
2459                                                               startIndex);
2460                            }
2461                            cellBounds = startRect;
2462                        }
2463                        cellBounds.width = visRect.width;
2464                    }
2465                    else {
2466                        if (direction > 0) {
2467                            // left for right-to-left
2468                            int x = cellBounds.x + visRect.width;
2469                            int rightIndex =
2470                                list.locationToIndex(new Point(x, cellBounds.y));
2471                            Rectangle rightRect = list.getCellBounds(rightIndex,
2472                                                                     rightIndex);
2473                            if (rightRect.x + rightRect.width > x &&
2474                                rightRect.x > cellBounds.x) {
2475                                rightRect.width = 0;
2476                            }
2477                            cellBounds.x = Math.max(0,
2478                                rightRect.x + rightRect.width - visRect.width);
2479                            cellBounds.width = visRect.width;
2480                        }
2481                        else {
2482                            cellBounds.x += Math.max(0,
2483                                cellBounds.width - visRect.width);
2484                            // adjust width to fit into visible rectangle
2485                            cellBounds.width = Math.min(cellBounds.width,
2486                                                        visRect.width);
2487                        }
2488                    }
2489                }
2490                else {
2491                    // vertical
2492                    if (direction > 0 && 
2493                             (cellBounds.y < visRect.y || 
2494                                cellBounds.y + cellBounds.height  
2495                                             > visRect.y + visRect.height)) { 
2496                        //down
2497                        int y = Math.max(0,
2498                            cellBounds.y + cellBounds.height - visRect.height);
2499                        int startIndex =
2500                            list.locationToIndex(new Point(cellBounds.x, y));
2501                        Rectangle startRect = list.getCellBounds(startIndex,
2502                                                                 startIndex);
2503                        if (startRect.y < y && startRect.y < cellBounds.y) {
2504                            startRect.y += startRect.height;
2505                            startIndex = 
2506                                list.locationToIndex(startRect.getLocation());
2507                            startRect =
2508                                list.getCellBounds(startIndex, startIndex);
2509                        }
2510                        cellBounds = startRect;
2511                        cellBounds.height = visRect.height;
2512                    }
2513                    else {
2514                        // adjust height to fit into visible rectangle
2515                        cellBounds.height = Math.min(cellBounds.height, visRect.height);
2516                    }
2517                }
2518                list.scrollRectToVisible(cellBounds);
2519            }
2520        }
2521
2522        private int getNextColumnIndex(JList list, BasicXListUI ui,
2523                                       int amount) {
2524            if (list.getLayoutOrientation() != JList.VERTICAL) {
2525                int index = adjustIndex(list.getLeadSelectionIndex(), list);
2526                int size = getElementCount(list);
2527
2528                if (index == -1) {
2529                    return 0;
2530                } else if (size == 1) {
2531                    // there's only one item so we should select it
2532                    return 0;
2533                } else if (ui == null || ui.columnCount <= 1) {
2534                    return -1;
2535                }
2536
2537                int column = ui.convertModelToColumn(index);
2538                int row = ui.convertModelToRow(index);
2539
2540                column += amount;
2541                if (column >= ui.columnCount || column < 0) {
2542                    // No wrapping.
2543                    return -1;
2544                }
2545                int maxRowCount = ui.getRowCount(column);
2546                if (row >= maxRowCount) {
2547                    return -1;
2548                }
2549                return ui.getModelIndex(column, row);
2550            }
2551            // Won't change the selection.
2552            return -1;
2553        }
2554
2555        private int getNextIndex(JList list, BasicXListUI ui, int amount) {
2556            int index = adjustIndex(list.getLeadSelectionIndex(), list);
2557            int size = getElementCount(list);
2558
2559            if (index == -1) {
2560                if (size > 0) {
2561                    if (amount > 0) {
2562                        index = 0;
2563                    }
2564                    else {
2565                        index = size - 1;
2566                    }
2567                }
2568            } else if (size == 1) { 
2569                // there's only one item so we should select it
2570                index = 0; 
2571            } else if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) { 
2572                if (ui != null) {
2573                    index += ui.columnCount * amount;
2574                }
2575            } else {
2576                index += amount;
2577            }
2578
2579            return index;
2580        }
2581    }
2582
2583
2584    private class Handler implements FocusListener, KeyListener,
2585                          ListDataListener, ListSelectionListener,
2586                          MouseInputListener, PropertyChangeListener,
2587                          BeforeDrag {
2588        //
2589        // KeyListener
2590        //
2591        private String prefix = "";
2592        private String typedString = "";
2593        private long lastTime = 0L;
2594
2595        
2596        /**
2597         * Invoked when a key has been typed.
2598         * 
2599         * Moves the keyboard focus to the first element whose prefix matches the
2600         * sequence of alphanumeric keys pressed by the user with delay less
2601         * than value of <code>timeFactor</code> property (or 1000 milliseconds
2602         * if it is not defined). Subsequent same key presses move the keyboard
2603         * focus to the next object that starts with the same letter until another
2604         * key is pressed, then it is treated as the prefix with appropriate number
2605         * of the same letters followed by first typed anothe letter.
2606         */
2607        public void keyTyped(KeyEvent e) {
2608            JList src = (JList)e.getSource();
2609
2610            if (getElementCount() == 0 || e.isAltDown() || e.isControlDown() || e.isMetaDown() ||
2611                isNavigationKey(e)) {
2612                // Nothing to select
2613                return;
2614            }
2615            boolean startingFromSelection = true;
2616
2617            char c = e.getKeyChar();
2618
2619            long time = e.getWhen();
2620            int startIndex = adjustIndex(src.getLeadSelectionIndex(), list);
2621            if (time - lastTime < timeFactor) {
2622                typedString += c;
2623                if((prefix.length() == 1) && (c == prefix.charAt(0))) {
2624                    // Subsequent same key presses move the keyboard focus to the next 
2625                    // object that starts with the same letter.
2626                    startIndex++;
2627                } else {
2628                    prefix = typedString;
2629                }
2630            } else {
2631                startIndex++;
2632                typedString = "" + c;
2633                prefix = typedString;
2634            }
2635            lastTime = time;
2636
2637            if (startIndex < 0 || startIndex >= getElementCount()) {
2638                startingFromSelection = false;
2639                startIndex = 0;
2640            }
2641            int index = src.getNextMatch(prefix, startIndex,
2642                                         Position.Bias.Forward);
2643            if (index >= 0) {
2644                src.setSelectedIndex(index);
2645                src.ensureIndexIsVisible(index);
2646            } else if (startingFromSelection) { // wrap
2647                index = src.getNextMatch(prefix, 0,
2648                                         Position.Bias.Forward);
2649                if (index >= 0) {
2650                    src.setSelectedIndex(index);
2651                    src.ensureIndexIsVisible(index);
2652                }
2653            }
2654        }
2655        
2656        /**
2657         * Invoked when a key has been pressed.
2658         *
2659         * Checks to see if the key event is a navigation key to prevent
2660         * dispatching these keys for the first letter navigation.
2661         */
2662        public void keyPressed(KeyEvent e) {
2663            if ( isNavigationKey(e) ) {
2664                prefix = "";
2665                typedString = "";
2666                lastTime = 0L;
2667            }
2668        }
2669        
2670        /**
2671         * Invoked when a key has been released.
2672         * See the class description for {@link KeyEvent} for a definition of 
2673         * a key released event.
2674         */
2675        public void keyReleased(KeyEvent e) {
2676        }
2677
2678        /**
2679         * Returns whether or not the supplied key event maps to a key that is used for
2680         * navigation.  This is used for optimizing key input by only passing non-
2681         * navigation keys to the first letter navigation mechanism.
2682         */
2683        private boolean isNavigationKey(KeyEvent event) {
2684            InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
2685            KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
2686
2687            if (inputMap != null && inputMap.get(key) != null) {
2688                return true;
2689            }
2690            return false;
2691        }  
2692
2693        // 
2694        // PropertyChangeListener
2695        //
2696        public void propertyChange(PropertyChangeEvent e) {
2697            String propertyName = e.getPropertyName();
2698
2699            /* If the JList.model property changes, remove our listener,
2700             * listDataListener from the old model and add it to the new one.
2701             */
2702            if (propertyName == "model") {
2703                ListModel oldModel = (ListModel)e.getOldValue();
2704                ListModel newModel = (ListModel)e.getNewValue();
2705                if (oldModel != null) {
2706                    oldModel.removeListDataListener(listDataListener);
2707                }
2708                if (newModel != null) {
2709                    newModel.addListDataListener(listDataListener);
2710                }
2711                updateLayoutStateNeeded |= modelChanged;
2712                redrawList();
2713            }
2714
2715            /* If the JList.selectionModel property changes, remove our listener,
2716             * listSelectionListener from the old selectionModel and add it to the new one.
2717             */
2718            else if (propertyName == "selectionModel") {
2719                ListSelectionModel oldModel = (ListSelectionModel)e.getOldValue();
2720                ListSelectionModel newModel = (ListSelectionModel)e.getNewValue();
2721                if (oldModel != null) {
2722                    oldModel.removeListSelectionListener(listSelectionListener);
2723                }
2724                if (newModel != null) {
2725                    newModel.addListSelectionListener(listSelectionListener);
2726                }
2727                updateLayoutStateNeeded |= modelChanged;
2728                redrawList();
2729            }
2730            else if (propertyName == "cellRenderer") {
2731                updateLayoutStateNeeded |= cellRendererChanged;
2732                redrawList();
2733            }
2734            else if (propertyName == "font") {
2735                updateLayoutStateNeeded |= fontChanged;
2736                redrawList();
2737            }
2738            else if (propertyName == "prototypeCellValue") {
2739                updateLayoutStateNeeded |= prototypeCellValueChanged;
2740                redrawList();
2741            }
2742            else if (propertyName == "fixedCellHeight") {
2743                updateLayoutStateNeeded |= fixedCellHeightChanged;
2744                redrawList();
2745            }
2746            else if (propertyName == "fixedCellWidth") {
2747                updateLayoutStateNeeded |= fixedCellWidthChanged;
2748                redrawList();
2749            }
2750            else if (propertyName == "cellRenderer") {
2751                updateLayoutStateNeeded |= cellRendererChanged;
2752                redrawList();
2753            }
2754            else if (propertyName == "selectionForeground") {
2755                list.repaint();
2756            }
2757            else if (propertyName == "selectionBackground") {
2758                list.repaint();
2759            }
2760            else if ("layoutOrientation" == propertyName) {
2761                updateLayoutStateNeeded |= layoutOrientationChanged;
2762                layoutOrientation = list.getLayoutOrientation();
2763                redrawList();
2764            }
2765            else if ("visibleRowCount" == propertyName) {
2766                if (layoutOrientation != JList.VERTICAL) {
2767                    updateLayoutStateNeeded |= layoutOrientationChanged;
2768                    redrawList();
2769                }
2770            }
2771            else if ("componentOrientation" == propertyName) {
2772                isLeftToRight = list.getComponentOrientation().isLeftToRight();
2773                updateLayoutStateNeeded |= componentOrientationChanged;
2774                redrawList();
2775
2776                InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
2777                SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
2778                                                 inputMap);
2779            } else if ("List.isFileList" == propertyName) {
2780                updateIsFileList();
2781                redrawList();
2782            } else if ("dropLocation" == propertyName) {
2783                JList.DropLocation oldValue = (JList.DropLocation)e.getOldValue();
2784                repaintDropLocation(oldValue);
2785                repaintDropLocation(list.getDropLocation());
2786            }
2787        }
2788
2789        private void repaintDropLocation(JList.DropLocation loc) {
2790            if (loc == null) {
2791                return;
2792            }
2793
2794            Rectangle r;
2795
2796            if (loc.isInsert()) {
2797                r = getDropLineRect(loc);
2798            } else {
2799                r = getCellBounds(list, loc.getIndex());
2800            }
2801
2802            if (r != null) {
2803                list.repaint(r);
2804            }
2805        }
2806
2807        //
2808        // ListDataListener
2809        //
2810        public void intervalAdded(ListDataEvent e) {
2811            updateLayoutStateNeeded = modelChanged;
2812
2813            int minIndex = Math.min(e.getIndex0(), e.getIndex1());
2814            int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
2815
2816            /* Sync the SelectionModel with the DataModel.
2817             */
2818
2819            ListSelectionModel sm = list.getSelectionModel();
2820            if (sm != null) {
2821                sm.insertIndexInterval(minIndex, maxIndex - minIndex+1, true);
2822            }
2823
2824            /* Repaint the entire list, from the origin of
2825             * the first added cell, to the bottom of the
2826             * component.
2827             */
2828            redrawList();
2829        }
2830
2831
2832        public void intervalRemoved(ListDataEvent e)
2833        {
2834            updateLayoutStateNeeded = modelChanged;
2835
2836            /* Sync the SelectionModel with the DataModel.
2837             */
2838
2839            ListSelectionModel sm = list.getSelectionModel();
2840            if (sm != null) {
2841                sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
2842            }
2843
2844            /* Repaint the entire list, from the origin of
2845             * the first removed cell, to the bottom of the
2846             * component.
2847             */
2848
2849            redrawList();
2850        }
2851
2852
2853        public void contentsChanged(ListDataEvent e) {
2854            updateLayoutStateNeeded = modelChanged;
2855            redrawList();
2856        }
2857
2858
2859        //
2860        // ListSelectionListener
2861        //
2862        public void valueChanged(ListSelectionEvent e) {
2863            maybeUpdateLayoutState();
2864            int size = getElementCount();
2865            int firstIndex = Math.min(size - 1, Math.max(e.getFirstIndex(), 0));
2866            int lastIndex = Math.min(size - 1, Math.max(e.getLastIndex(), 0));
2867
2868            Rectangle bounds = getCellBounds(list, firstIndex, lastIndex);
2869
2870            if (bounds != null) {
2871                list.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
2872            }
2873        }
2874
2875        //
2876        // MouseListener
2877        //
2878        public void mouseClicked(MouseEvent e) {
2879        }
2880
2881        public void mouseEntered(MouseEvent e) {
2882        }
2883
2884        public void mouseExited(MouseEvent e) {
2885        }
2886
2887        // Whether or not the mouse press (which is being considered as part
2888        // of a drag sequence) also caused the selection change to be fully
2889        // processed.
2890        private boolean dragPressDidSelection;
2891
2892        public void mousePressed(MouseEvent e) {
2893            if (SwingXUtilities.shouldIgnore(e, list)) {
2894                return;
2895            }
2896
2897            boolean dragEnabled = list.getDragEnabled();
2898            boolean grabFocus = true;
2899
2900            // different behavior if drag is enabled
2901            if (dragEnabled) {
2902                // PENDING JW: this isn't aware of sorting/filtering - fix!
2903                int row = SwingXUtilities.loc2IndexFileList(list, e.getPoint());
2904                // if we have a valid row and this is a drag initiating event
2905                if (row != -1 && DragRecognitionSupport.mousePressed(e)) {
2906                    dragPressDidSelection = false;
2907
2908                    if (e.isControlDown()) {
2909                        // do nothing for control - will be handled on release
2910                        // or when drag starts
2911                        return;
2912                    } else if (!e.isShiftDown() && list.isSelectedIndex(row)) {
2913                        // clicking on something that's already selected
2914                        // and need to make it the lead now
2915                        list.addSelectionInterval(row, row);
2916                        return;
2917                    }
2918
2919                    // could be a drag initiating event - don't grab focus
2920                    grabFocus = false;
2921
2922                    dragPressDidSelection = true;
2923                }
2924            } else {
2925                // When drag is enabled mouse drags won't change the selection
2926                // in the list, so we only set the isAdjusting flag when it's
2927                // not enabled
2928                list.setValueIsAdjusting(true);
2929            }
2930
2931            if (grabFocus) {
2932                SwingXUtilities.adjustFocus(list);
2933            }
2934
2935            adjustSelection(e);
2936        }
2937
2938        private void adjustSelection(MouseEvent e) {
2939            // PENDING JW: this isn't aware of sorting/filtering - fix!
2940            int row = SwingXUtilities.loc2IndexFileList(list, e.getPoint());
2941            if (row < 0) {
2942                // If shift is down in multi-select, we should do nothing.
2943                // For single select or non-shift-click, clear the selection
2944                if (isFileList &&
2945                    e.getID() == MouseEvent.MOUSE_PRESSED &&
2946                    (!e.isShiftDown() ||
2947                     list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
2948                    list.clearSelection();
2949                }
2950            }
2951            else {
2952                int anchorIndex = adjustIndex(list.getAnchorSelectionIndex(), list);
2953                boolean anchorSelected;
2954                if (anchorIndex == -1) {
2955                    anchorIndex = 0;
2956                    anchorSelected = false;
2957                } else {
2958                    anchorSelected = list.isSelectedIndex(anchorIndex);
2959                }
2960
2961                if (e.isControlDown()) {
2962                    if (e.isShiftDown()) {
2963                        if (anchorSelected) {
2964                            list.addSelectionInterval(anchorIndex, row);
2965                        } else {
2966                            list.removeSelectionInterval(anchorIndex, row);
2967                            if (isFileList) {
2968                                list.addSelectionInterval(row, row);
2969                                list.getSelectionModel().setAnchorSelectionIndex(anchorIndex);
2970                            }
2971                        }
2972                    } else if (list.isSelectedIndex(row)) {
2973                        list.removeSelectionInterval(row, row);
2974                    } else {
2975                        list.addSelectionInterval(row, row);
2976                    }
2977                } else if (e.isShiftDown()) {
2978                    list.setSelectionInterval(anchorIndex, row);
2979                } else {
2980                    list.setSelectionInterval(row, row);
2981                }
2982            }
2983        }
2984
2985        public void dragStarting(MouseEvent me) {
2986            if (me.isControlDown()) {
2987                // PENDING JW: this isn't aware of sorting/filtering - fix!
2988                int row = SwingXUtilities.loc2IndexFileList(list, me.getPoint());
2989                list.addSelectionInterval(row, row);
2990            }
2991        }
2992
2993        public void mouseDragged(MouseEvent e) {
2994            if (SwingXUtilities.shouldIgnore(e, list)) {
2995                return;
2996            }
2997
2998            if (list.getDragEnabled()) {
2999                DragRecognitionSupport.mouseDragged(e, this);
3000                return;
3001            }
3002
3003            if (e.isShiftDown() || e.isControlDown()) {
3004                return;
3005            }
3006
3007            int row = locationToIndex(list, e.getPoint());
3008            if (row != -1) {
3009                // 4835633.  Dragging onto a File should not select it.
3010                if (isFileList) {
3011                    return;
3012                }
3013                Rectangle cellBounds = getCellBounds(list, row, row);
3014                if (cellBounds != null) {
3015                    list.scrollRectToVisible(cellBounds);
3016                    list.setSelectionInterval(row, row);
3017                }
3018            }
3019        }
3020
3021        public void mouseMoved(MouseEvent e) {
3022        }
3023
3024        public void mouseReleased(MouseEvent e) {
3025            if (SwingXUtilities.shouldIgnore(e, list)) {
3026                return;
3027            }
3028
3029            if (list.getDragEnabled()) {
3030                MouseEvent me = DragRecognitionSupport.mouseReleased(e);
3031                if (me != null) {
3032                    SwingXUtilities.adjustFocus(list);
3033                    if (!dragPressDidSelection) {
3034                        adjustSelection(me);
3035                    }
3036                }
3037            } else {
3038                list.setValueIsAdjusting(false);
3039            }
3040        }
3041
3042        //
3043        // FocusListener
3044        //
3045        protected void repaintCellFocus()
3046        {
3047            int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
3048            if (leadIndex != -1) {
3049                Rectangle r = getCellBounds(list, leadIndex, leadIndex);
3050                if (r != null) {
3051                    list.repaint(r.x, r.y, r.width, r.height);
3052                }
3053            }
3054        }
3055
3056        /* The focusGained() focusLost() methods run when the JList
3057         * focus changes.
3058         */
3059
3060        public void focusGained(FocusEvent e) {
3061            repaintCellFocus();
3062        }
3063
3064        public void focusLost(FocusEvent e) {
3065            repaintCellFocus();
3066        }
3067    }
3068
3069    private static int adjustIndex(int index, JList list) {
3070        return index < ((JXList) list).getElementCount() ? index : -1;
3071    }
3072
3073    private static final TransferHandler defaultTransferHandler = new ListTransferHandler();
3074
3075    static class ListTransferHandler extends TransferHandler implements UIResource {
3076
3077        /**
3078         * Create a Transferable to use as the source for a data transfer.
3079         *
3080         * @param c  The component holding the data to be transfered.  This
3081         *  argument is provided to enable sharing of TransferHandlers by
3082         *  multiple components.
3083         * @return  The representation of the data to be transfered. 
3084         *  
3085         */
3086        protected Transferable createTransferable(JComponent c) {
3087            if (c instanceof JList) {
3088                JList list = (JList) c;
3089                Object[] values = list.getSelectedValues();
3090
3091                if (values == null || values.length == 0) {
3092                    return null;
3093                }
3094                
3095                StringBuffer plainBuf = new StringBuffer();
3096                StringBuffer htmlBuf = new StringBuffer();
3097                
3098                htmlBuf.append("<html>\n<body>\n<ul>\n");
3099
3100                for (int i = 0; i < values.length; i++) {
3101                    Object obj = values[i];
3102                    String val = ((obj == null) ? "" : obj.toString());
3103                    plainBuf.append(val + "\n");
3104                    htmlBuf.append("  <li>" + val + "\n");
3105                }
3106                
3107                // remove the last newline
3108                plainBuf.deleteCharAt(plainBuf.length() - 1);
3109                htmlBuf.append("</ul>\n</body>\n</html>");
3110                
3111                return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
3112            }
3113
3114            return null;
3115        }
3116
3117        public int getSourceActions(JComponent c) {
3118            return COPY;
3119        }
3120
3121    }
3122}