0001 /*
0002 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package javax.swing.plaf.basic;
0027
0028 import sun.swing.DefaultLookup;
0029 import sun.swing.UIAction;
0030
0031 import javax.swing.*;
0032 import javax.swing.event.*;
0033 import javax.swing.plaf.*;
0034 import javax.swing.text.Position;
0035
0036 import java.awt.*;
0037 import java.awt.event.*;
0038 import java.awt.datatransfer.Transferable;
0039 import java.awt.geom.Point2D;
0040
0041 import java.beans.PropertyChangeListener;
0042 import java.beans.PropertyChangeEvent;
0043
0044 import sun.swing.SwingUtilities2;
0045 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
0046
0047 /**
0048 * An extensible implementation of {@code ListUI}.
0049 * <p>
0050 * {@code BasicListUI} instances cannot be shared between multiple
0051 * lists.
0052 *
0053 * @version 1.130 05/09/07
0054 * @author Hans Muller
0055 * @author Philip Milne
0056 * @author Shannon Hickey (drag and drop)
0057 */
0058 public class BasicListUI extends ListUI {
0059 private static final StringBuilder BASELINE_COMPONENT_KEY = new StringBuilder(
0060 "List.baselineComponent");
0061
0062 protected JList list = null;
0063 protected CellRendererPane rendererPane;
0064
0065 // Listeners that this UI attaches to the JList
0066 protected FocusListener focusListener;
0067 protected MouseInputListener mouseInputListener;
0068 protected ListSelectionListener listSelectionListener;
0069 protected ListDataListener listDataListener;
0070 protected PropertyChangeListener propertyChangeListener;
0071 private Handler handler;
0072
0073 protected int[] cellHeights = null;
0074 protected int cellHeight = -1;
0075 protected int cellWidth = -1;
0076 protected int updateLayoutStateNeeded = modelChanged;
0077 /**
0078 * Height of the list. When asked to paint, if the current size of
0079 * the list differs, this will update the layout state.
0080 */
0081 private int listHeight;
0082
0083 /**
0084 * Width of the list. When asked to paint, if the current size of
0085 * the list differs, this will update the layout state.
0086 */
0087 private int listWidth;
0088
0089 /**
0090 * The layout orientation of the list.
0091 */
0092 private int layoutOrientation;
0093
0094 // Following ivars are used if the list is laying out horizontally
0095
0096 /**
0097 * Number of columns to create.
0098 */
0099 private int columnCount;
0100 /**
0101 * Preferred height to make the list, this is only used if the
0102 * the list is layed out horizontally.
0103 */
0104 private int preferredHeight;
0105 /**
0106 * Number of rows per column. This is only used if the row height is
0107 * fixed.
0108 */
0109 private int rowsPerColumn;
0110
0111 /**
0112 * The time factor to treate the series of typed alphanumeric key
0113 * as prefix for first letter navigation.
0114 */
0115 private long timeFactor = 1000L;
0116
0117 /**
0118 * Local cache of JList's client property "List.isFileList"
0119 */
0120 private boolean isFileList = false;
0121
0122 /**
0123 * Local cache of JList's component orientation property
0124 */
0125 private boolean isLeftToRight = true;
0126
0127 /* The bits below define JList property changes that affect layout.
0128 * When one of these properties changes we set a bit in
0129 * updateLayoutStateNeeded. The change is dealt with lazily, see
0130 * maybeUpdateLayoutState. Changes to the JLists model, e.g. the
0131 * models length changed, are handled similarly, see DataListener.
0132 */
0133
0134 protected final static int modelChanged = 1 << 0;
0135 protected final static int selectionModelChanged = 1 << 1;
0136 protected final static int fontChanged = 1 << 2;
0137 protected final static int fixedCellWidthChanged = 1 << 3;
0138 protected final static int fixedCellHeightChanged = 1 << 4;
0139 protected final static int prototypeCellValueChanged = 1 << 5;
0140 protected final static int cellRendererChanged = 1 << 6;
0141 private final static int layoutOrientationChanged = 1 << 7;
0142 private final static int heightChanged = 1 << 8;
0143 private final static int widthChanged = 1 << 9;
0144 private final static int componentOrientationChanged = 1 << 10;
0145
0146 private static final int DROP_LINE_THICKNESS = 2;
0147
0148 static void loadActionMap(LazyActionMap map) {
0149 map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN));
0150 map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_EXTEND));
0151 map
0152 .put(new Actions(
0153 Actions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD));
0154 map.put(new Actions(Actions.SELECT_NEXT_COLUMN));
0155 map.put(new Actions(Actions.SELECT_NEXT_COLUMN_EXTEND));
0156 map.put(new Actions(Actions.SELECT_NEXT_COLUMN_CHANGE_LEAD));
0157 map.put(new Actions(Actions.SELECT_PREVIOUS_ROW));
0158 map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_EXTEND));
0159 map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_CHANGE_LEAD));
0160 map.put(new Actions(Actions.SELECT_NEXT_ROW));
0161 map.put(new Actions(Actions.SELECT_NEXT_ROW_EXTEND));
0162 map.put(new Actions(Actions.SELECT_NEXT_ROW_CHANGE_LEAD));
0163 map.put(new Actions(Actions.SELECT_FIRST_ROW));
0164 map.put(new Actions(Actions.SELECT_FIRST_ROW_EXTEND));
0165 map.put(new Actions(Actions.SELECT_FIRST_ROW_CHANGE_LEAD));
0166 map.put(new Actions(Actions.SELECT_LAST_ROW));
0167 map.put(new Actions(Actions.SELECT_LAST_ROW_EXTEND));
0168 map.put(new Actions(Actions.SELECT_LAST_ROW_CHANGE_LEAD));
0169 map.put(new Actions(Actions.SCROLL_UP));
0170 map.put(new Actions(Actions.SCROLL_UP_EXTEND));
0171 map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
0172 map.put(new Actions(Actions.SCROLL_DOWN));
0173 map.put(new Actions(Actions.SCROLL_DOWN_EXTEND));
0174 map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
0175 map.put(new Actions(Actions.SELECT_ALL));
0176 map.put(new Actions(Actions.CLEAR_SELECTION));
0177 map.put(new Actions(Actions.ADD_TO_SELECTION));
0178 map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
0179 map.put(new Actions(Actions.EXTEND_TO));
0180 map.put(new Actions(Actions.MOVE_SELECTION_TO));
0181
0182 map.put(TransferHandler.getCutAction().getValue(Action.NAME),
0183 TransferHandler.getCutAction());
0184 map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
0185 TransferHandler.getCopyAction());
0186 map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
0187 TransferHandler.getPasteAction());
0188 }
0189
0190 /**
0191 * Paint one List cell: compute the relevant state, get the "rubber stamp"
0192 * cell renderer component, and then use the CellRendererPane to paint it.
0193 * Subclasses may want to override this method rather than paint().
0194 *
0195 * @see #paint
0196 */
0197 protected void paintCell(Graphics g, int row, Rectangle rowBounds,
0198 ListCellRenderer cellRenderer, ListModel dataModel,
0199 ListSelectionModel selModel, int leadIndex) {
0200 Object value = dataModel.getElementAt(row);
0201 boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
0202 boolean isSelected = selModel.isSelectedIndex(row);
0203
0204 Component rendererComponent = cellRenderer
0205 .getListCellRendererComponent(list, value, row,
0206 isSelected, cellHasFocus);
0207
0208 int cx = rowBounds.x;
0209 int cy = rowBounds.y;
0210 int cw = rowBounds.width;
0211 int ch = rowBounds.height;
0212
0213 if (isFileList) {
0214 // Shrink renderer to preferred size. This is mostly used on Windows
0215 // where selection is only shown around the file name, instead of
0216 // across the whole list cell.
0217 int w = Math.min(cw,
0218 rendererComponent.getPreferredSize().width + 4);
0219 if (!isLeftToRight) {
0220 cx += (cw - w);
0221 }
0222 cw = w;
0223 }
0224
0225 rendererPane.paintComponent(g, rendererComponent, list, cx, cy,
0226 cw, ch, true);
0227 }
0228
0229 /**
0230 * Paint the rows that intersect the Graphics objects clipRect. This
0231 * method calls paintCell as necessary. Subclasses
0232 * may want to override these methods.
0233 *
0234 * @see #paintCell
0235 */
0236 public void paint(Graphics g, JComponent c) {
0237 Shape clip = g.getClip();
0238 paintImpl(g, c);
0239 g.setClip(clip);
0240
0241 paintDropLine(g);
0242 }
0243
0244 private void paintImpl(Graphics g, JComponent c) {
0245 switch (layoutOrientation) {
0246 case JList.VERTICAL_WRAP:
0247 if (list.getHeight() != listHeight) {
0248 updateLayoutStateNeeded |= heightChanged;
0249 redrawList();
0250 }
0251 break;
0252 case JList.HORIZONTAL_WRAP:
0253 if (list.getWidth() != listWidth) {
0254 updateLayoutStateNeeded |= widthChanged;
0255 redrawList();
0256 }
0257 break;
0258 default:
0259 break;
0260 }
0261 maybeUpdateLayoutState();
0262
0263 ListCellRenderer renderer = list.getCellRenderer();
0264 ListModel dataModel = list.getModel();
0265 ListSelectionModel selModel = list.getSelectionModel();
0266 int size;
0267
0268 if ((renderer == null) || (size = dataModel.getSize()) == 0) {
0269 return;
0270 }
0271
0272 // Determine how many columns we need to paint
0273 Rectangle paintBounds = g.getClipBounds();
0274
0275 int startColumn, endColumn;
0276 if (c.getComponentOrientation().isLeftToRight()) {
0277 startColumn = convertLocationToColumn(paintBounds.x,
0278 paintBounds.y);
0279 endColumn = convertLocationToColumn(paintBounds.x
0280 + paintBounds.width, paintBounds.y);
0281 } else {
0282 startColumn = convertLocationToColumn(paintBounds.x
0283 + paintBounds.width, paintBounds.y);
0284 endColumn = convertLocationToColumn(paintBounds.x,
0285 paintBounds.y);
0286 }
0287 int maxY = paintBounds.y + paintBounds.height;
0288 int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
0289 int rowIncrement = (layoutOrientation == JList.HORIZONTAL_WRAP) ? columnCount
0290 : 1;
0291
0292 for (int colCounter = startColumn; colCounter <= endColumn; colCounter++) {
0293 // And then how many rows in this columnn
0294 int row = convertLocationToRowInColumn(paintBounds.y,
0295 colCounter);
0296 int rowCount = getRowCount(colCounter);
0297 int index = getModelIndex(colCounter, row);
0298 Rectangle rowBounds = getCellBounds(list, index, index);
0299
0300 if (rowBounds == null) {
0301 // Not valid, bail!
0302 return;
0303 }
0304 while (row < rowCount && rowBounds.y < maxY && index < size) {
0305 rowBounds.height = getHeight(colCounter, row);
0306 g.setClip(rowBounds.x, rowBounds.y, rowBounds.width,
0307 rowBounds.height);
0308 g.clipRect(paintBounds.x, paintBounds.y,
0309 paintBounds.width, paintBounds.height);
0310 paintCell(g, index, rowBounds, renderer, dataModel,
0311 selModel, leadIndex);
0312 rowBounds.y += rowBounds.height;
0313 index += rowIncrement;
0314 row++;
0315 }
0316 }
0317 // Empty out the renderer pane, allowing renderers to be gc'ed.
0318 rendererPane.removeAll();
0319 }
0320
0321 private void paintDropLine(Graphics g) {
0322 JList.DropLocation loc = list.getDropLocation();
0323 if (loc == null || !loc.isInsert()) {
0324 return;
0325 }
0326
0327 Color c = DefaultLookup.getColor(list, this ,
0328 "List.dropLineColor", null);
0329 if (c != null) {
0330 g.setColor(c);
0331 Rectangle rect = getDropLineRect(loc);
0332 g.fillRect(rect.x, rect.y, rect.width, rect.height);
0333 }
0334 }
0335
0336 private Rectangle getDropLineRect(JList.DropLocation loc) {
0337 int size = list.getModel().getSize();
0338
0339 if (size == 0) {
0340 Insets insets = list.getInsets();
0341 if (layoutOrientation == JList.HORIZONTAL_WRAP) {
0342 if (isLeftToRight) {
0343 return new Rectangle(insets.left, insets.top,
0344 DROP_LINE_THICKNESS, 20);
0345 } else {
0346 return new Rectangle(list.getWidth()
0347 - DROP_LINE_THICKNESS - insets.right,
0348 insets.top, DROP_LINE_THICKNESS, 20);
0349 }
0350 } else {
0351 return new Rectangle(insets.left, insets.top, list
0352 .getWidth()
0353 - insets.left - insets.right,
0354 DROP_LINE_THICKNESS);
0355 }
0356 }
0357
0358 Rectangle rect = null;
0359 int index = loc.getIndex();
0360 boolean decr = false;
0361
0362 if (layoutOrientation == JList.HORIZONTAL_WRAP) {
0363 if (index == size) {
0364 decr = true;
0365 } else if (index != 0
0366 && convertModelToRow(index) != convertModelToRow(index - 1)) {
0367
0368 Rectangle prev = getCellBounds(list, index - 1);
0369 Rectangle me = getCellBounds(list, index);
0370 Point p = loc.getDropPoint();
0371
0372 if (isLeftToRight) {
0373 decr = Point2D.distance(prev.x + prev.width, prev.y
0374 + (int) (prev.height / 2.0), p.x, p.y) < Point2D
0375 .distance(me.x, me.y
0376 + (int) (me.height / 2.0), p.x, p.y);
0377 } else {
0378 decr = Point2D.distance(prev.x, prev.y
0379 + (int) (prev.height / 2.0), p.x, p.y) < Point2D
0380 .distance(me.x + me.width, me.y
0381 + (int) (prev.height / 2.0), p.x,
0382 p.y);
0383 }
0384 }
0385
0386 if (decr) {
0387 index--;
0388 rect = getCellBounds(list, index);
0389 if (isLeftToRight) {
0390 rect.x += rect.width;
0391 } else {
0392 rect.x -= DROP_LINE_THICKNESS;
0393 }
0394 } else {
0395 rect = getCellBounds(list, index);
0396 if (!isLeftToRight) {
0397 rect.x += rect.width - DROP_LINE_THICKNESS;
0398 }
0399 }
0400
0401 if (rect.x >= list.getWidth()) {
0402 rect.x = list.getWidth() - DROP_LINE_THICKNESS;
0403 } else if (rect.x < 0) {
0404 rect.x = 0;
0405 }
0406
0407 rect.width = DROP_LINE_THICKNESS;
0408 } else if (layoutOrientation == JList.VERTICAL_WRAP) {
0409 if (index == size) {
0410 index--;
0411 rect = getCellBounds(list, index);
0412 rect.y += rect.height;
0413 } else if (index != 0
0414 && convertModelToColumn(index) != convertModelToColumn(index - 1)) {
0415
0416 Rectangle prev = getCellBounds(list, index - 1);
0417 Rectangle me = getCellBounds(list, index);
0418 Point p = loc.getDropPoint();
0419 if (Point2D.distance(prev.x + (int) (prev.width / 2.0),
0420 prev.y + prev.height, p.x, p.y) < Point2D
0421 .distance(me.x + (int) (me.width / 2.0), me.y,
0422 p.x, p.y)) {
0423
0424 index--;
0425 rect = getCellBounds(list, index);
0426 rect.y += rect.height;
0427 } else {
0428 rect = getCellBounds(list, index);
0429 }
0430 } else {
0431 rect = getCellBounds(list, index);
0432 }
0433
0434 if (rect.y >= list.getHeight()) {
0435 rect.y = list.getHeight() - DROP_LINE_THICKNESS;
0436 }
0437
0438 rect.height = DROP_LINE_THICKNESS;
0439 } else {
0440 if (index == size) {
0441 index--;
0442 rect = getCellBounds(list, index);
0443 rect.y += rect.height;
0444 } else {
0445 rect = getCellBounds(list, index);
0446 }
0447
0448 if (rect.y >= list.getHeight()) {
0449 rect.y = list.getHeight() - DROP_LINE_THICKNESS;
0450 }
0451
0452 rect.height = DROP_LINE_THICKNESS;
0453 }
0454
0455 return rect;
0456 }
0457
0458 /**
0459 * Returns the baseline.
0460 *
0461 * @throws NullPointerException {@inheritDoc}
0462 * @throws IllegalArgumentException {@inheritDoc}
0463 * @see javax.swing.JComponent#getBaseline(int, int)
0464 * @since 1.6
0465 */
0466 public int getBaseline(JComponent c, int width, int height) {
0467 super .getBaseline(c, width, height);
0468 int rowHeight = list.getFixedCellHeight();
0469 UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
0470 Component renderer = (Component) lafDefaults
0471 .get(BASELINE_COMPONENT_KEY);
0472 if (renderer == null) {
0473 ListCellRenderer lcr = (ListCellRenderer) UIManager
0474 .get("List.cellRenderer");
0475 renderer = lcr.getListCellRendererComponent(list, "a", -1,
0476 false, false);
0477 lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
0478 }
0479 renderer.setFont(list.getFont());
0480 // JList actually has much more complex behavior here.
0481 // If rowHeight != -1 the rowHeight is either the max of all cell
0482 // heights (layout orientation != VERTICAL), or is variable depending
0483 // upon the cell. We assume a default size.
0484 // We could theoretically query the real renderer, but that would
0485 // not work for an empty model and the results may vary with
0486 // the content.
0487 if (rowHeight == -1) {
0488 rowHeight = renderer.getPreferredSize().height;
0489 }
0490 return renderer.getBaseline(Integer.MAX_VALUE, rowHeight)
0491 + list.getInsets().top;
0492 }
0493
0494 /**
0495 * Returns an enum indicating how the baseline of the component
0496 * changes as the size changes.
0497 *
0498 * @throws NullPointerException {@inheritDoc}
0499 * @see javax.swing.JComponent#getBaseline(int, int)
0500 * @since 1.6
0501 */
0502 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
0503 JComponent c) {
0504 super .getBaselineResizeBehavior(c);
0505 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
0506 }
0507
0508 /**
0509 * The preferredSize of the list depends upon the layout orientation.
0510 * <table summary="Describes the preferred size for each layout orientation">
0511 * <tr><th>Layout Orientation</th><th>Preferred Size</th></tr>
0512 * <tr>
0513 * <td>JList.VERTICAL
0514 * <td>The preferredSize of the list is total height of the rows
0515 * and the maximum width of the cells. If JList.fixedCellHeight
0516 * is specified then the total height of the rows is just
0517 * (cellVerticalMargins + fixedCellHeight) * model.getSize() where
0518 * rowVerticalMargins is the space we allocate for drawing
0519 * the yellow focus outline. Similarly if fixedCellWidth is
0520 * specified then we just use that.
0521 * </td>
0522 * <tr>
0523 * <td>JList.VERTICAL_WRAP
0524 * <td>If the visible row count is greater than zero, the preferredHeight
0525 * is the maximum cell height * visibleRowCount. If the visible row
0526 * count is <= 0, the preferred height is either the current height
0527 * of the list, or the maximum cell height, whichever is
0528 * bigger. The preferred width is than the maximum cell width *
0529 * number of columns needed. Where the number of columns needs is
0530 * list.height / max cell height. Max cell height is either the fixed
0531 * cell height, or is determined by iterating through all the cells
0532 * to find the maximum height from the ListCellRenderer.
0533 * <tr>
0534 * <td>JList.HORIZONTAL_WRAP
0535 * <td>If the visible row count is greater than zero, the preferredHeight
0536 * is the maximum cell height * adjustedRowCount. Where
0537 * visibleRowCount is used to determine the number of columns.
0538 * Because this lays out horizontally the number of rows is
0539 * then determined from the column count. For example, lets say
0540 * you have a model with 10 items and the visible row count is 8.
0541 * The number of columns needed to display this is 2, but you no
0542 * longer need 8 rows to display this, you only need 5, thus
0543 * the adjustedRowCount is 5.
0544 * <p>If the visible row
0545 * count is <= 0, the preferred height is dictated by the
0546 * number of columns, which will be as many as can fit in the width
0547 * of the <code>JList</code> (width / max cell width), with at
0548 * least one column. The preferred height then becomes the
0549 * model size / number of columns * maximum cell height.
0550 * Max cell height is either the fixed
0551 * cell height, or is determined by iterating through all the cells
0552 * to find the maximum height from the ListCellRenderer.
0553 * </table>
0554 * The above specifies the raw preferred width and height. The resulting
0555 * preferred width is the above width + insets.left + insets.right and
0556 * the resulting preferred height is the above height + insets.top +
0557 * insets.bottom. Where the <code>Insets</code> are determined from
0558 * <code>list.getInsets()</code>.
0559 *
0560 * @param c The JList component.
0561 * @return The total size of the list.
0562 */
0563 public Dimension getPreferredSize(JComponent c) {
0564 maybeUpdateLayoutState();
0565
0566 int lastRow = list.getModel().getSize() - 1;
0567 if (lastRow < 0) {
0568 return new Dimension(0, 0);
0569 }
0570
0571 Insets insets = list.getInsets();
0572 int width = cellWidth * columnCount + insets.left
0573 + insets.right;
0574 int height;
0575
0576 if (layoutOrientation != JList.VERTICAL) {
0577 height = preferredHeight;
0578 } else {
0579 Rectangle bounds = getCellBounds(list, lastRow);
0580
0581 if (bounds != null) {
0582 height = bounds.y + bounds.height + insets.bottom;
0583 } else {
0584 height = 0;
0585 }
0586 }
0587 return new Dimension(width, height);
0588 }
0589
0590 /**
0591 * Selected the previous row and force it to be visible.
0592 *
0593 * @see JList#ensureIndexIsVisible
0594 */
0595 protected void selectPreviousIndex() {
0596 int s = list.getSelectedIndex();
0597 if (s > 0) {
0598 s -= 1;
0599 list.setSelectedIndex(s);
0600 list.ensureIndexIsVisible(s);
0601 }
0602 }
0603
0604 /**
0605 * Selected the previous row and force it to be visible.
0606 *
0607 * @see JList#ensureIndexIsVisible
0608 */
0609 protected void selectNextIndex() {
0610 int s = list.getSelectedIndex();
0611 if ((s + 1) < list.getModel().getSize()) {
0612 s += 1;
0613 list.setSelectedIndex(s);
0614 list.ensureIndexIsVisible(s);
0615 }
0616 }
0617
0618 /**
0619 * Registers the keyboard bindings on the <code>JList</code> that the
0620 * <code>BasicListUI</code> is associated with. This method is called at
0621 * installUI() time.
0622 *
0623 * @see #installUI
0624 */
0625 protected void installKeyboardActions() {
0626 InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
0627
0628 SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
0629 inputMap);
0630
0631 LazyActionMap.installLazyActionMap(list, BasicListUI.class,
0632 "List.actionMap");
0633 }
0634
0635 InputMap getInputMap(int condition) {
0636 if (condition == JComponent.WHEN_FOCUSED) {
0637 InputMap keyMap = (InputMap) DefaultLookup.get(list, this ,
0638 "List.focusInputMap");
0639 InputMap rtlKeyMap;
0640
0641 if (isLeftToRight
0642 || ((rtlKeyMap = (InputMap) DefaultLookup.get(list,
0643 this , "List.focusInputMap.RightToLeft")) == null)) {
0644 return keyMap;
0645 } else {
0646 rtlKeyMap.setParent(keyMap);
0647 return rtlKeyMap;
0648 }
0649 }
0650 return null;
0651 }
0652
0653 /**
0654 * Unregisters keyboard actions installed from
0655 * <code>installKeyboardActions</code>.
0656 * This method is called at uninstallUI() time - subclassess should
0657 * ensure that all of the keyboard actions registered at installUI
0658 * time are removed here.
0659 *
0660 * @see #installUI
0661 */
0662 protected void uninstallKeyboardActions() {
0663 SwingUtilities.replaceUIActionMap(list, null);
0664 SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
0665 null);
0666 }
0667
0668 /**
0669 * Create and install the listeners for the JList, its model, and its
0670 * selectionModel. This method is called at installUI() time.
0671 *
0672 * @see #installUI
0673 * @see #uninstallListeners
0674 */
0675 protected void installListeners() {
0676 TransferHandler th = list.getTransferHandler();
0677 if (th == null || th instanceof UIResource) {
0678 list.setTransferHandler(defaultTransferHandler);
0679 // default TransferHandler doesn't support drop
0680 // so we don't want drop handling
0681 if (list.getDropTarget() instanceof UIResource) {
0682 list.setDropTarget(null);
0683 }
0684 }
0685
0686 focusListener = createFocusListener();
0687 mouseInputListener = createMouseInputListener();
0688 propertyChangeListener = createPropertyChangeListener();
0689 listSelectionListener = createListSelectionListener();
0690 listDataListener = createListDataListener();
0691
0692 list.addFocusListener(focusListener);
0693 list.addMouseListener(mouseInputListener);
0694 list.addMouseMotionListener(mouseInputListener);
0695 list.addPropertyChangeListener(propertyChangeListener);
0696 list.addKeyListener(getHandler());
0697
0698 ListModel model = list.getModel();
0699 if (model != null) {
0700 model.addListDataListener(listDataListener);
0701 }
0702
0703 ListSelectionModel selectionModel = list.getSelectionModel();
0704 if (selectionModel != null) {
0705 selectionModel
0706 .addListSelectionListener(listSelectionListener);
0707 }
0708 }
0709
0710 /**
0711 * Remove the listeners for the JList, its model, and its
0712 * selectionModel. All of the listener fields, are reset to
0713 * null here. This method is called at uninstallUI() time,
0714 * it should be kept in sync with installListeners.
0715 *
0716 * @see #uninstallUI
0717 * @see #installListeners
0718 */
0719 protected void uninstallListeners() {
0720 list.removeFocusListener(focusListener);
0721 list.removeMouseListener(mouseInputListener);
0722 list.removeMouseMotionListener(mouseInputListener);
0723 list.removePropertyChangeListener(propertyChangeListener);
0724 list.removeKeyListener(getHandler());
0725
0726 ListModel model = list.getModel();
0727 if (model != null) {
0728 model.removeListDataListener(listDataListener);
0729 }
0730
0731 ListSelectionModel selectionModel = list.getSelectionModel();
0732 if (selectionModel != null) {
0733 selectionModel
0734 .removeListSelectionListener(listSelectionListener);
0735 }
0736
0737 focusListener = null;
0738 mouseInputListener = null;
0739 listSelectionListener = null;
0740 listDataListener = null;
0741 propertyChangeListener = null;
0742 handler = null;
0743 }
0744
0745 /**
0746 * Initialize JList properties, e.g. font, foreground, and background,
0747 * and add the CellRendererPane. The font, foreground, and background
0748 * properties are only set if their current value is either null
0749 * or a UIResource, other properties are set if the current
0750 * value is null.
0751 *
0752 * @see #uninstallDefaults
0753 * @see #installUI
0754 * @see CellRendererPane
0755 */
0756 protected void installDefaults() {
0757 list.setLayout(null);
0758
0759 LookAndFeel.installBorder(list, "List.border");
0760
0761 LookAndFeel.installColorsAndFont(list, "List.background",
0762 "List.foreground", "List.font");
0763
0764 LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
0765
0766 if (list.getCellRenderer() == null) {
0767 list.setCellRenderer((ListCellRenderer) (UIManager
0768 .get("List.cellRenderer")));
0769 }
0770
0771 Color sbg = list.getSelectionBackground();
0772 if (sbg == null || sbg instanceof UIResource) {
0773 list.setSelectionBackground(UIManager
0774 .getColor("List.selectionBackground"));
0775 }
0776
0777 Color sfg = list.getSelectionForeground();
0778 if (sfg == null || sfg instanceof UIResource) {
0779 list.setSelectionForeground(UIManager
0780 .getColor("List.selectionForeground"));
0781 }
0782
0783 Long l = (Long) UIManager.get("List.timeFactor");
0784 timeFactor = (l != null) ? l.longValue() : 1000L;
0785
0786 updateIsFileList();
0787 }
0788
0789 private void updateIsFileList() {
0790 boolean b = Boolean.TRUE.equals(list
0791 .getClientProperty("List.isFileList"));
0792 if (b != isFileList) {
0793 isFileList = b;
0794 Font oldFont = list.getFont();
0795 if (oldFont == null || oldFont instanceof UIResource) {
0796 Font newFont = UIManager
0797 .getFont(b ? "FileChooser.listFont"
0798 : "List.font");
0799 if (newFont != null && newFont != oldFont) {
0800 list.setFont(newFont);
0801 }
0802 }
0803 }
0804 }
0805
0806 /**
0807 * Set the JList properties that haven't been explicitly overridden to
0808 * null. A property is considered overridden if its current value
0809 * is not a UIResource.
0810 *
0811 * @see #installDefaults
0812 * @see #uninstallUI
0813 * @see CellRendererPane
0814 */
0815 protected void uninstallDefaults() {
0816 LookAndFeel.uninstallBorder(list);
0817 if (list.getFont() instanceof UIResource) {
0818 list.setFont(null);
0819 }
0820 if (list.getForeground() instanceof UIResource) {
0821 list.setForeground(null);
0822 }
0823 if (list.getBackground() instanceof UIResource) {
0824 list.setBackground(null);
0825 }
0826 if (list.getSelectionBackground() instanceof UIResource) {
0827 list.setSelectionBackground(null);
0828 }
0829 if (list.getSelectionForeground() instanceof UIResource) {
0830 list.setSelectionForeground(null);
0831 }
0832 if (list.getCellRenderer() instanceof UIResource) {
0833 list.setCellRenderer(null);
0834 }
0835 if (list.getTransferHandler() instanceof UIResource) {
0836 list.setTransferHandler(null);
0837 }
0838 }
0839
0840 /**
0841 * Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
0842 * <code>installListeners()</code>, and <code>installKeyboardActions()</code>
0843 * in order.
0844 *
0845 * @see #installDefaults
0846 * @see #installListeners
0847 * @see #installKeyboardActions
0848 */
0849 public void installUI(JComponent c) {
0850 list = (JList) c;
0851
0852 layoutOrientation = list.getLayoutOrientation();
0853
0854 rendererPane = new CellRendererPane();
0855 list.add(rendererPane);
0856
0857 columnCount = 1;
0858
0859 updateLayoutStateNeeded = modelChanged;
0860 isLeftToRight = list.getComponentOrientation().isLeftToRight();
0861
0862 installDefaults();
0863 installListeners();
0864 installKeyboardActions();
0865 }
0866
0867 /**
0868 * Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
0869 * <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
0870 * in order. Sets this.list to null.
0871 *
0872 * @see #uninstallListeners
0873 * @see #uninstallKeyboardActions
0874 * @see #uninstallDefaults
0875 */
0876 public void uninstallUI(JComponent c) {
0877 uninstallListeners();
0878 uninstallDefaults();
0879 uninstallKeyboardActions();
0880
0881 cellWidth = cellHeight = -1;
0882 cellHeights = null;
0883
0884 listWidth = listHeight = -1;
0885
0886 list.remove(rendererPane);
0887 rendererPane = null;
0888 list = null;
0889 }
0890
0891 /**
0892 * Returns a new instance of BasicListUI. BasicListUI delegates are
0893 * allocated one per JList.
0894 *
0895 * @return A new ListUI implementation for the Windows look and feel.
0896 */
0897 public static ComponentUI createUI(JComponent list) {
0898 return new BasicListUI();
0899 }
0900
0901 /**
0902 * {@inheritDoc}
0903 * @throws NullPointerException {@inheritDoc}
0904 */
0905 public int locationToIndex(JList list, Point location) {
0906 maybeUpdateLayoutState();
0907 return convertLocationToModel(location.x, location.y);
0908 }
0909
0910 /**
0911 * {@inheritDoc}
0912 */
0913 public Point indexToLocation(JList list, int index) {
0914 maybeUpdateLayoutState();
0915 Rectangle rect = getCellBounds(list, index, index);
0916
0917 if (rect != null) {
0918 return new Point(rect.x, rect.y);
0919 }
0920 return null;
0921 }
0922
0923 /**
0924 * {@inheritDoc}
0925 */
0926 public Rectangle getCellBounds(JList list, int index1, int index2) {
0927 maybeUpdateLayoutState();
0928
0929 int minIndex = Math.min(index1, index2);
0930 int maxIndex = Math.max(index1, index2);
0931
0932 if (minIndex >= list.getModel().getSize()) {
0933 return null;
0934 }
0935
0936 Rectangle minBounds = getCellBounds(list, minIndex);
0937
0938 if (minBounds == null) {
0939 return null;
0940 }
0941 if (minIndex == maxIndex) {
0942 return minBounds;
0943 }
0944 Rectangle maxBounds = getCellBounds(list, maxIndex);
0945
0946 if (maxBounds != null) {
0947 if (layoutOrientation == JList.HORIZONTAL_WRAP) {
0948 int minRow = convertModelToRow(minIndex);
0949 int maxRow = convertModelToRow(maxIndex);
0950
0951 if (minRow != maxRow) {
0952 minBounds.x = 0;
0953 minBounds.width = list.getWidth();
0954 }
0955 } else if (minBounds.x != maxBounds.x) {
0956 // Different columns
0957 minBounds.y = 0;
0958 minBounds.height = list.getHeight();
0959 }
0960 minBounds.add(maxBounds);
0961 }
0962 return minBounds;
0963 }
0964
0965 /**
0966 * Gets the bounds of the specified model index, returning the resulting
0967 * bounds, or null if <code>index</code> is not valid.
0968 */
0969 private Rectangle getCellBounds(JList list, int index) {
0970 maybeUpdateLayoutState();
0971
0972 int row = convertModelToRow(index);
0973 int column = convertModelToColumn(index);
0974
0975 if (row == -1 || column == -1) {
0976 return null;
0977 }
0978
0979 Insets insets = list.getInsets();
0980 int x;
0981 int w = cellWidth;
0982 int y = insets.top;
0983 int h;
0984 switch (layoutOrientation) {
0985 case JList.VERTICAL_WRAP:
0986 case JList.HORIZONTAL_WRAP:
0987 if (isLeftToRight) {
0988 x = insets.left + column * cellWidth;
0989 } else {
0990 x = list.getWidth() - insets.right - (column + 1)
0991 * cellWidth;
0992 }
0993 y += cellHeight * row;
0994 h = cellHeight;
0995 break;
0996 default:
0997 x = insets.left;
0998 if (cellHeights == null) {
0999 y += (cellHeight * row);
1000 } else if (row >= cellHeights.length) {
1001 y = 0;
1002 } else {
1003 for (int i = 0; i < row; i++) {
1004 y += cellHeights[i];
1005 }
1006 }
1007 w = list.getWidth() - (insets.left + insets.right);
1008 h = getRowHeight(index);
1009 break;
1010 }
1011 return new Rectangle(x, y, w, h);
1012 }
1013
1014 /**
1015 * Returns the height of the specified row based on the current layout.
1016 *
1017 * @return The specified row height or -1 if row isn't valid.
1018 * @see #convertYToRow
1019 * @see #convertRowToY
1020 * @see #updateLayoutState
1021 */
1022 protected int getRowHeight(int row) {
1023 return getHeight(0, row);
1024 }
1025
1026 /**
1027 * Convert the JList relative coordinate to the row that contains it,
1028 * based on the current layout. If y0 doesn't fall within any row,
1029 * return -1.
1030 *
1031 * @return The row that contains y0, or -1.
1032 * @see #getRowHeight
1033 * @see #updateLayoutState
1034 */
1035 protected int convertYToRow(int y0) {
1036 return convertLocationToRow(0, y0, false);
1037 }
1038
1039 /**
1040 * Return the JList relative Y coordinate of the origin of the specified
1041 * row or -1 if row isn't valid.
1042 *
1043 * @return The Y coordinate of the origin of row, or -1.
1044 * @see #getRowHeight
1045 * @see #updateLayoutState
1046 */
1047 protected int convertRowToY(int row) {
1048 if (row >= getRowCount(0) || row < 0) {
1049 return -1;
1050 }
1051 Rectangle bounds = getCellBounds(list, row, row);
1052 return bounds.y;
1053 }
1054
1055 /**
1056 * Returns the height of the cell at the passed in location.
1057 */
1058 private int getHeight(int column, int row) {
1059 if (column < 0 || column > columnCount || row < 0) {
1060 return -1;
1061 }
1062 if (layoutOrientation != JList.VERTICAL) {
1063 return cellHeight;
1064 }
1065 if (row >= list.getModel().getSize()) {
1066 return -1;
1067 }
1068 return (cellHeights == null) ? cellHeight
1069 : ((row < cellHeights.length) ? cellHeights[row] : -1);
1070 }
1071
1072 /**
1073 * Returns the row at location x/y.
1074 *
1075 * @param closest If true and the location doesn't exactly match a
1076 * particular location, this will return the closest row.
1077 */
1078 private int convertLocationToRow(int x, int y0, boolean closest) {
1079 int size = list.getModel().getSize();
1080
1081 if (size <= 0) {
1082 return -1;
1083 }
1084 Insets insets = list.getInsets();
1085 if (cellHeights == null) {
1086 int row = (cellHeight == 0) ? 0
1087 : ((y0 - insets.top) / cellHeight);
1088 if (closest) {
1089 if (row < 0) {
1090 row = 0;
1091 } else if (row >= size) {
1092 row = size - 1;
1093 }
1094 }
1095 return row;
1096 } else if (size > cellHeights.length) {
1097 return -1;
1098 } else {
1099 int y = insets.top;
1100 int row = 0;
1101
1102 if (closest && y0 < y) {
1103 return 0;
1104 }
1105 int i;
1106 for (i = 0; i < size; i++) {
1107 if ((y0 >= y) && (y0 < y + cellHeights[i])) {
1108 return row;
1109 }
1110 y += cellHeights[i];
1111 row += 1;
1112 }
1113 return i - 1;
1114 }
1115 }
1116
1117 /**
1118 * Returns the closest row that starts at the specified y-location
1119 * in the passed in column.
1120 */
1121 private int convertLocationToRowInColumn(int y, int column) {
1122 int x = 0;
1123
1124 if (layoutOrientation != JList.VERTICAL) {
1125 if (isLeftToRight) {
1126 x = column * cellWidth;
1127 } else {
1128 x = list.getWidth() - (column + 1) * cellWidth
1129 - list.getInsets().right;
1130 }
1131 }
1132 return convertLocationToRow(x, y, true);
1133 }
1134
1135 /**
1136 * Returns the closest location to the model index of the passed in
1137 * location.
1138 */
1139 private int convertLocationToModel(int x, int y) {
1140 int row = convertLocationToRow(x, y, true);
1141 int column = convertLocationToColumn(x, y);
1142
1143 if (row >= 0 && column >= 0) {
1144 return getModelIndex(column, row);
1145 }
1146 return -1;
1147 }
1148
1149 /**
1150 * Returns the number of rows in the given column.
1151 */
1152 private int getRowCount(int column) {
1153 if (column < 0 || column >= columnCount) {
1154 return -1;
1155 }
1156 if (layoutOrientation == JList.VERTICAL
1157 || (column == 0 && columnCount == 1)) {
1158 return list.getModel().getSize();
1159 }
1160 if (column >= columnCount) {
1161 return -1;
1162 }
1163 if (layoutOrientation == JList.VERTICAL_WRAP) {
1164 if (column < (columnCount - 1)) {
1165 return rowsPerColumn;
1166 }
1167 return list.getModel().getSize() - (columnCount - 1)
1168 * rowsPerColumn;
1169 }
1170 // JList.HORIZONTAL_WRAP
1171 int diff = columnCount
1172 - (columnCount * rowsPerColumn - list.getModel()
1173 .getSize());
1174
1175 if (column >= diff) {
1176 return Math.max(0, rowsPerColumn - 1);
1177 }
1178 return rowsPerColumn;
1179 }
1180
1181 /**
1182 * Returns the model index for the specified display location.
1183 * If <code>column</code>x<code>row</code> is beyond the length of the
1184 * model, this will return the model size - 1.
1185 */
1186 private int getModelIndex(int column, int row) {
1187 switch (layoutOrientation) {
1188 case JList.VERTICAL_WRAP:
1189 return Math.min(list.getModel().getSize() - 1,
1190 rowsPerColumn * column
1191 + Math.min(row, rowsPerColumn - 1));
1192 case JList.HORIZONTAL_WRAP:
1193 return Math.min(list.getModel().getSize() - 1, row
1194 * columnCount + column);
1195 default:
1196 return row;
1197 }
1198 }
1199
1200 /**
1201 * Returns the closest column to the passed in location.
1202 */
1203 private int convertLocationToColumn(int x, int y) {
1204 if (cellWidth > 0) {
1205 if (layoutOrientation == JList.VERTICAL) {
1206 return 0;
1207 }
1208 Insets insets = list.getInsets();
1209 int col;
1210 if (isLeftToRight) {
1211 col = (x - insets.left) / cellWidth;
1212 } else {
1213 col = (list.getWidth() - x - insets.right - 1)
1214 / cellWidth;
1215 }
1216 if (col < 0) {
1217 return 0;
1218 } else if (col >= columnCount) {
1219 return columnCount - 1;
1220 }
1221 return col;
1222 }
1223 return 0;
1224 }
1225
1226 /**
1227 * Returns the row that the model index <code>index</code> will be
1228 * displayed in..
1229 */
1230 private int convertModelToRow(int index) {
1231 int size = list.getModel().getSize();
1232
1233 if ((index < 0) || (index >= size)) {
1234 return -1;
1235 }
1236
1237 if (layoutOrientation != JList.VERTICAL && columnCount > 1
1238 && rowsPerColumn > 0) {
1239 if (layoutOrientation == JList.VERTICAL_WRAP) {
1240 return index % rowsPerColumn;
1241 }
1242 return index / columnCount;
1243 }
1244 return index;
1245 }
1246
1247 /**
1248 * Returns the column that the model index <code>index</code> will be
1249 * displayed in.
1250 */
1251 private int convertModelToColumn(int index) {
1252 int size = list.getModel().getSize();
1253
1254 if ((index < 0) || (index >= size)) {
1255 return -1;
1256 }
1257
1258 if (layoutOrientation != JList.VERTICAL && rowsPerColumn > 0
1259 && columnCount > 1) {
1260 if (layoutOrientation == JList.VERTICAL_WRAP) {
1261 return index / rowsPerColumn;
1262 }
1263 return index % columnCount;
1264 }
1265 return 0;
1266 }
1267
1268 /**
1269 * If updateLayoutStateNeeded is non zero, call updateLayoutState() and reset
1270 * updateLayoutStateNeeded. This method should be called by methods
1271 * before doing any computation based on the geometry of the list.
1272 * For example it's the first call in paint() and getPreferredSize().
1273 *
1274 * @see #updateLayoutState
1275 */
1276 protected void maybeUpdateLayoutState() {
1277 if (updateLayoutStateNeeded != 0) {
1278 updateLayoutState();
1279 updateLayoutStateNeeded = 0;
1280 }
1281 }
1282
1283 /**
1284 * Recompute the value of cellHeight or cellHeights based
1285 * and cellWidth, based on the current font and the current
1286 * values of fixedCellWidth, fixedCellHeight, and prototypeCellValue.
1287 *
1288 * @see #maybeUpdateLayoutState
1289 */
1290 protected void updateLayoutState() {
1291 /* If both JList fixedCellWidth and fixedCellHeight have been
1292 * set, then initialize cellWidth and cellHeight, and set
1293 * cellHeights to null.
1294 */
1295
1296 int fixedCellHeight = list.getFixedCellHeight();
1297 int fixedCellWidth = list.getFixedCellWidth();
1298
1299 cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
1300
1301 if (fixedCellHeight != -1) {
1302 cellHeight = fixedCellHeight;
1303 cellHeights = null;
1304 } else {
1305 cellHeight = -1;
1306 cellHeights = new int[list.getModel().getSize()];
1307 }
1308
1309 /* If either of JList fixedCellWidth and fixedCellHeight haven't
1310 * been set, then initialize cellWidth and cellHeights by
1311 * scanning through the entire model. Note: if the renderer is
1312 * null, we just set cellWidth and cellHeights[*] to zero,
1313 * if they're not set already.
1314 */
1315
1316 if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
1317
1318 ListModel dataModel = list.getModel();
1319 int dataModelSize = dataModel.getSize();
1320 ListCellRenderer renderer = list.getCellRenderer();
1321
1322 if (renderer != null) {
1323 for (int index = 0; index < dataModelSize; index++) {
1324 Object value = dataModel.getElementAt(index);
1325 Component c = renderer
1326 .getListCellRendererComponent(list, value,
1327 index, false, false);
1328 rendererPane.add(c);
1329 Dimension cellSize = c.getPreferredSize();
1330 if (fixedCellWidth == -1) {
1331 cellWidth = Math.max(cellSize.width, cellWidth);
1332 }
1333 if (fixedCellHeight == -1) {
1334 cellHeights[index] = cellSize.height;
1335 }
1336 }
1337 } else {
1338 if (cellWidth == -1) {
1339 cellWidth = 0;
1340 }
1341 if (cellHeights == null) {
1342 cellHeights = new int[dataModelSize];
1343 }
1344 for (int index = 0; index < dataModelSize; index++) {
1345 cellHeights[index] = 0;
1346 }
1347 }
1348 }
1349
1350 columnCount = 1;
1351 if (layoutOrientation != JList.VERTICAL) {
1352 updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
1353 }
1354 }
1355
1356 /**
1357 * Invoked when the list is layed out horizontally to determine how
1358 * many columns to create.
1359 * <p>
1360 * This updates the <code>rowsPerColumn, </code><code>columnCount</code>,
1361 * <code>preferredHeight</code> and potentially <code>cellHeight</code>
1362 * instance variables.
1363 */
1364 private void updateHorizontalLayoutState(int fixedCellWidth,
1365 int fixedCellHeight) {
1366 int visRows = list.getVisibleRowCount();
1367 int dataModelSize = list.getModel().getSize();
1368 Insets insets = list.getInsets();
1369
1370 listHeight = list.getHeight();
1371 listWidth = list.getWidth();
1372
1373 if (dataModelSize == 0) {
1374 rowsPerColumn = columnCount = 0;
1375 preferredHeight = insets.top + insets.bottom;
1376 return;
1377 }
1378
1379 int height;
1380
1381 if (fixedCellHeight != -1) {
1382 height = fixedCellHeight;
1383 } else {
1384 // Determine the max of the renderer heights.
1385 int maxHeight = 0;
1386 if (cellHeights.length > 0) {
1387 maxHeight = cellHeights[cellHeights.length - 1];
1388 for (int counter = cellHeights.length - 2; counter >= 0; counter--) {
1389 maxHeight = Math.max(maxHeight,
1390 cellHeights[counter]);
1391 }
1392 }
1393 height = cellHeight = maxHeight;
1394 cellHeights = null;
1395 }
1396 // The number of rows is either determined by the visible row
1397 // count, or by the height of the list.
1398 rowsPerColumn = dataModelSize;
1399 if (visRows > 0) {
1400 rowsPerColumn = visRows;
1401 columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1402 if (dataModelSize > 0 && dataModelSize > rowsPerColumn
1403 && dataModelSize % rowsPerColumn != 0) {
1404 columnCount++;
1405 }
1406 if (layoutOrientation == JList.HORIZONTAL_WRAP) {
1407 // Because HORIZONTAL_WRAP flows differently, the
1408 // rowsPerColumn needs to be adjusted.
1409 rowsPerColumn = (dataModelSize / columnCount);
1410 if (dataModelSize % columnCount > 0) {
1411 rowsPerColumn++;
1412 }
1413 }
1414 } else if (layoutOrientation == JList.VERTICAL_WRAP
1415 && height != 0) {
1416 rowsPerColumn = Math.max(1,
1417 (listHeight - insets.top - insets.bottom) / height);
1418 columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1419 if (dataModelSize > 0 && dataModelSize > rowsPerColumn
1420 && dataModelSize % rowsPerColumn != 0) {
1421 columnCount++;
1422 }
1423 } else if (layoutOrientation == JList.HORIZONTAL_WRAP
1424 && cellWidth > 0 && listWidth > 0) {
1425 columnCount = Math.max(1,
1426 (listWidth - insets.left - insets.right)
1427 / cellWidth);
1428 rowsPerColumn = dataModelSize / columnCount;
1429 if (dataModelSize % columnCount > 0) {
1430 rowsPerColumn++;
1431 }
1432 }
1433 preferredHeight = rowsPerColumn * cellHeight + insets.top
1434 + insets.bottom;
1435 }
1436
1437 private Handler getHandler() {
1438 if (handler == null) {
1439 handler = new Handler();
1440 }
1441 return handler;
1442 }
1443
1444 /**
1445 * Mouse input, and focus handling for JList. An instance of this
1446 * class is added to the appropriate java.awt.Component lists
1447 * at installUI() time. Note keyboard input is handled with JComponent
1448 * KeyboardActions, see installKeyboardActions().
1449 * <p>
1450 * <strong>Warning:</strong>
1451 * Serialized objects of this class will not be compatible with
1452 * future Swing releases. The current serialization support is
1453 * appropriate for short term storage or RMI between applications running
1454 * the same version of Swing. As of 1.4, support for long term storage
1455 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1456 * has been added to the <code>java.beans</code> package.
1457 * Please see {@link java.beans.XMLEncoder}.
1458 *
1459 * @see #createMouseInputListener
1460 * @see #installKeyboardActions
1461 * @see #installUI
1462 */
1463 public class MouseInputHandler implements MouseInputListener {
1464 public void mouseClicked(MouseEvent e) {
1465 getHandler().mouseClicked(e);
1466 }
1467
1468 public void mouseEntered(MouseEvent e) {
1469 getHandler().mouseEntered(e);
1470 }
1471
1472 public void mouseExited(MouseEvent e) {
1473 getHandler().mouseExited(e);
1474 }
1475
1476 public void mousePressed(MouseEvent e) {
1477 getHandler().mousePressed(e);
1478 }
1479
1480 public void mouseDragged(MouseEvent e) {
1481 getHandler().mouseDragged(e);
1482 }
1483
1484 public void mouseMoved(MouseEvent e) {
1485 getHandler().mouseMoved(e);
1486 }
1487
1488 public void mouseReleased(MouseEvent e) {
1489 getHandler().mouseReleased(e);
1490 }
1491 }
1492
1493 /**
1494 * Creates a delegate that implements MouseInputListener.
1495 * The delegate is added to the corresponding java.awt.Component listener
1496 * lists at installUI() time. Subclasses can override this method to return
1497 * a custom MouseInputListener, e.g.
1498 * <pre>
1499 * class MyListUI extends BasicListUI {
1500 * protected MouseInputListener <b>createMouseInputListener</b>() {
1501 * return new MyMouseInputHandler();
1502 * }
1503 * public class MyMouseInputHandler extends MouseInputHandler {
1504 * public void mouseMoved(MouseEvent e) {
1505 * // do some extra work when the mouse moves
1506 * super.mouseMoved(e);
1507 * }
1508 * }
1509 * }
1510 * </pre>
1511 *
1512 * @see MouseInputHandler
1513 * @see #installUI
1514 */
1515 protected MouseInputListener createMouseInputListener() {
1516 return getHandler();
1517 }
1518
1519 /**
1520 * This inner class is marked "public" due to a compiler bug.
1521 * This class should be treated as a "protected" inner class.
1522 * Instantiate it only within subclasses of BasicTableUI.
1523 */
1524 public class FocusHandler implements FocusListener {
1525 protected void repaintCellFocus() {
1526 getHandler().repaintCellFocus();
1527 }
1528
1529 /* The focusGained() focusLost() methods run when the JList
1530 * focus changes.
1531 */
1532
1533 public void focusGained(FocusEvent e) {
1534 getHandler().focusGained(e);
1535 }
1536
1537 public void focusLost(FocusEvent e) {
1538 getHandler().focusLost(e);
1539 }
1540 }
1541
1542 protected FocusListener createFocusListener() {
1543 return getHandler();
1544 }
1545
1546 /**
1547 * The ListSelectionListener that's added to the JLists selection
1548 * model at installUI time, and whenever the JList.selectionModel property
1549 * changes. When the selection changes we repaint the affected rows.
1550 * <p>
1551 * <strong>Warning:</strong>
1552 * Serialized objects of this class will not be compatible with
1553 * future Swing releases. The current serialization support is
1554 * appropriate for short term storage or RMI between applications running
1555 * the same version of Swing. As of 1.4, support for long term storage
1556 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1557 * has been added to the <code>java.beans</code> package.
1558 * Please see {@link java.beans.XMLEncoder}.
1559 *
1560 * @see #createListSelectionListener
1561 * @see #getCellBounds
1562 * @see #installUI
1563 */
1564 public class ListSelectionHandler implements ListSelectionListener {
1565 public void valueChanged(ListSelectionEvent e) {
1566 getHandler().valueChanged(e);
1567 }
1568 }
1569
1570 /**
1571 * Creates an instance of ListSelectionHandler that's added to
1572 * the JLists by selectionModel as needed. Subclasses can override
1573 * this method to return a custom ListSelectionListener, e.g.
1574 * <pre>
1575 * class MyListUI extends BasicListUI {
1576 * protected ListSelectionListener <b>createListSelectionListener</b>() {
1577 * return new MySelectionListener();
1578 * }
1579 * public class MySelectionListener extends ListSelectionHandler {
1580 * public void valueChanged(ListSelectionEvent e) {
1581 * // do some extra work when the selection changes
1582 * super.valueChange(e);
1583 * }
1584 * }
1585 * }
1586 * </pre>
1587 *
1588 * @see ListSelectionHandler
1589 * @see #installUI
1590 */
1591 protected ListSelectionListener createListSelectionListener() {
1592 return getHandler();
1593 }
1594
1595 private void redrawList() {
1596 list.revalidate();
1597 list.repaint();
1598 }
1599
1600 /**
1601 * The ListDataListener that's added to the JLists model at
1602 * installUI time, and whenever the JList.model property changes.
1603 * <p>
1604 * <strong>Warning:</strong>
1605 * Serialized objects of this class will not be compatible with
1606 * future Swing releases. The current serialization support is
1607 * appropriate for short term storage or RMI between applications running
1608 * the same version of Swing. As of 1.4, support for long term storage
1609 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1610 * has been added to the <code>java.beans</code> package.
1611 * Please see {@link java.beans.XMLEncoder}.
1612 *
1613 * @see JList#getModel
1614 * @see #maybeUpdateLayoutState
1615 * @see #createListDataListener
1616 * @see #installUI
1617 */
1618 public class ListDataHandler implements ListDataListener {
1619 public void intervalAdded(ListDataEvent e) {
1620 getHandler().intervalAdded(e);
1621 }
1622
1623 public void intervalRemoved(ListDataEvent e) {
1624 getHandler().intervalRemoved(e);
1625 }
1626
1627 public void contentsChanged(ListDataEvent e) {
1628 getHandler().contentsChanged(e);
1629 }
1630 }
1631
1632 /**
1633 * Creates an instance of ListDataListener that's added to
1634 * the JLists by model as needed. Subclasses can override
1635 * this method to return a custom ListDataListener, e.g.
1636 * <pre>
1637 * class MyListUI extends BasicListUI {
1638 * protected ListDataListener <b>createListDataListener</b>() {
1639 * return new MyListDataListener();
1640 * }
1641 * public class MyListDataListener extends ListDataHandler {
1642 * public void contentsChanged(ListDataEvent e) {
1643 * // do some extra work when the models contents change
1644 * super.contentsChange(e);
1645 * }
1646 * }
1647 * }
1648 * </pre>
1649 *
1650 * @see ListDataListener
1651 * @see JList#getModel
1652 * @see #installUI
1653 */
1654 protected ListDataListener createListDataListener() {
1655 return getHandler();
1656 }
1657
1658 /**
1659 * The PropertyChangeListener that's added to the JList at
1660 * installUI time. When the value of a JList property that
1661 * affects layout changes, we set a bit in updateLayoutStateNeeded.
1662 * If the JLists model changes we additionally remove our listeners
1663 * from the old model. Likewise for the JList selectionModel.
1664 * <p>
1665 * <strong>Warning:</strong>
1666 * Serialized objects of this class will not be compatible with
1667 * future Swing releases. The current serialization support is
1668 * appropriate for short term storage or RMI between applications running
1669 * the same version of Swing. As of 1.4, support for long term storage
1670 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1671 * has been added to the <code>java.beans</code> package.
1672 * Please see {@link java.beans.XMLEncoder}.
1673 *
1674 * @see #maybeUpdateLayoutState
1675 * @see #createPropertyChangeListener
1676 * @see #installUI
1677 */
1678 public class PropertyChangeHandler implements
1679 PropertyChangeListener {
1680 public void propertyChange(PropertyChangeEvent e) {
1681 getHandler().propertyChange(e);
1682 }
1683 }
1684
1685 /**
1686 * Creates an instance of PropertyChangeHandler that's added to
1687 * the JList by installUI(). Subclasses can override this method
1688 * to return a custom PropertyChangeListener, e.g.
1689 * <pre>
1690 * class MyListUI extends BasicListUI {
1691 * protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
1692 * return new MyPropertyChangeListener();
1693 * }
1694 * public class MyPropertyChangeListener extends PropertyChangeHandler {
1695 * public void propertyChange(PropertyChangeEvent e) {
1696 * if (e.getPropertyName().equals("model")) {
1697 * // do some extra work when the model changes
1698 * }
1699 * super.propertyChange(e);
1700 * }
1701 * }
1702 * }
1703 * </pre>
1704 *
1705 * @see PropertyChangeListener
1706 * @see #installUI
1707 */
1708 protected PropertyChangeListener createPropertyChangeListener() {
1709 return getHandler();
1710 }
1711
1712 /** Used by IncrementLeadSelectionAction. Indicates the action should
1713 * change the lead, and not select it. */
1714 private static final int CHANGE_LEAD = 0;
1715 /** Used by IncrementLeadSelectionAction. Indicates the action should
1716 * change the selection and lead. */
1717 private static final int CHANGE_SELECTION = 1;
1718 /** Used by IncrementLeadSelectionAction. Indicates the action should
1719 * extend the selection from the anchor to the next index. */
1720 private static final int EXTEND_SELECTION = 2;
1721
1722 private static class Actions extends UIAction {
1723 private static final String SELECT_PREVIOUS_COLUMN = "selectPreviousColumn";
1724 private static final String SELECT_PREVIOUS_COLUMN_EXTEND = "selectPreviousColumnExtendSelection";
1725 private static final String SELECT_PREVIOUS_COLUMN_CHANGE_LEAD = "selectPreviousColumnChangeLead";
1726 private static final String SELECT_NEXT_COLUMN = "selectNextColumn";
1727 private static final String SELECT_NEXT_COLUMN_EXTEND = "selectNextColumnExtendSelection";
1728 private static final String SELECT_NEXT_COLUMN_CHANGE_LEAD = "selectNextColumnChangeLead";
1729 private static final String SELECT_PREVIOUS_ROW = "selectPreviousRow";
1730 private static final String SELECT_PREVIOUS_ROW_EXTEND = "selectPreviousRowExtendSelection";
1731 private static final String SELECT_PREVIOUS_ROW_CHANGE_LEAD = "selectPreviousRowChangeLead";
1732 private static final String SELECT_NEXT_ROW = "selectNextRow";
1733 private static final String SELECT_NEXT_ROW_EXTEND = "selectNextRowExtendSelection";
1734 private static final String SELECT_NEXT_ROW_CHANGE_LEAD = "selectNextRowChangeLead";
1735 private static final String SELECT_FIRST_ROW = "selectFirstRow";
1736 private static final String SELECT_FIRST_ROW_EXTEND = "selectFirstRowExtendSelection";
1737 private static final String SELECT_FIRST_ROW_CHANGE_LEAD = "selectFirstRowChangeLead";
1738 private static final String SELECT_LAST_ROW = "selectLastRow";
1739 private static final String SELECT_LAST_ROW_EXTEND = "selectLastRowExtendSelection";
1740 private static final String SELECT_LAST_ROW_CHANGE_LEAD = "selectLastRowChangeLead";
1741 private static final String SCROLL_UP = "scrollUp";
1742 private static final String SCROLL_UP_EXTEND = "scrollUpExtendSelection";
1743 private static final String SCROLL_UP_CHANGE_LEAD = "scrollUpChangeLead";
1744 private static final String SCROLL_DOWN = "scrollDown";
1745 private static final String SCROLL_DOWN_EXTEND = "scrollDownExtendSelection";
1746 private static final String SCROLL_DOWN_CHANGE_LEAD = "scrollDownChangeLead";
1747 private static final String SELECT_ALL = "selectAll";
1748 private static final String CLEAR_SELECTION = "clearSelection";
1749
1750 // add the lead item to the selection without changing lead or anchor
1751 private static final String ADD_TO_SELECTION = "addToSelection";
1752
1753 // toggle the selected state of the lead item and move the anchor to it
1754 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
1755
1756 // extend the selection to the lead item
1757 private static final String EXTEND_TO = "extendTo";
1758
1759 // move the anchor to the lead and ensure only that item is selected
1760 private static final String MOVE_SELECTION_TO = "moveSelectionTo";
1761
1762 Actions(String name) {
1763 super (name);
1764 }
1765
1766 public void actionPerformed(ActionEvent e) {
1767 String name = getName();
1768 JList list = (JList) e.getSource();
1769 BasicListUI ui = (BasicListUI) BasicLookAndFeel
1770 .getUIOfType(list.getUI(), BasicListUI.class);
1771
1772 if (name == SELECT_PREVIOUS_COLUMN) {
1773 changeSelection(list, CHANGE_SELECTION,
1774 getNextColumnIndex(list, ui, -1), -1);
1775 } else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
1776 changeSelection(list, EXTEND_SELECTION,
1777 getNextColumnIndex(list, ui, -1), -1);
1778 } else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
1779 changeSelection(list, CHANGE_LEAD, getNextColumnIndex(
1780 list, ui, -1), -1);
1781 } else if (name == SELECT_NEXT_COLUMN) {
1782 changeSelection(list, CHANGE_SELECTION,
1783 getNextColumnIndex(list, ui, 1), 1);
1784 } else if (name == SELECT_NEXT_COLUMN_EXTEND) {
1785 changeSelection(list, EXTEND_SELECTION,
1786 getNextColumnIndex(list, ui, 1), 1);
1787 } else if (name == SELECT_NEXT_COLUMN_CHANGE_LEAD) {
1788 changeSelection(list, CHANGE_LEAD, getNextColumnIndex(
1789 list, ui, 1), 1);
1790 } else if (name == SELECT_PREVIOUS_ROW) {
1791 changeSelection(list, CHANGE_SELECTION, getNextIndex(
1792 list, ui, -1), -1);
1793 } else if (name == SELECT_PREVIOUS_ROW_EXTEND) {
1794 changeSelection(list, EXTEND_SELECTION, getNextIndex(
1795 list, ui, -1), -1);
1796 } else if (name == SELECT_PREVIOUS_ROW_CHANGE_LEAD) {
1797 changeSelection(list, CHANGE_LEAD, getNextIndex(list,
1798 ui, -1), -1);
1799 } else if (name == SELECT_NEXT_ROW) {
1800 changeSelection(list, CHANGE_SELECTION, getNextIndex(
1801 list, ui, 1), 1);
1802 } else if (name == SELECT_NEXT_ROW_EXTEND) {
1803 changeSelection(list, EXTEND_SELECTION, getNextIndex(
1804 list, ui, 1), 1);
1805 } else if (name == SELECT_NEXT_ROW_CHANGE_LEAD) {
1806 changeSelection(list, CHANGE_LEAD, getNextIndex(list,
1807 ui, 1), 1);
1808 } else if (name == SELECT_FIRST_ROW) {
1809 changeSelection(list, CHANGE_SELECTION, 0, -1);
1810 } else if (name == SELECT_FIRST_ROW_EXTEND) {
1811 changeSelection(list, EXTEND_SELECTION, 0, -1);
1812 } else if (name == SELECT_FIRST_ROW_CHANGE_LEAD) {
1813 changeSelection(list, CHANGE_LEAD, 0, -1);
1814 } else if (name == SELECT_LAST_ROW) {
1815 changeSelection(list, CHANGE_SELECTION, list.getModel()
1816 .getSize() - 1, 1);
1817 } else if (name == SELECT_LAST_ROW_EXTEND) {
1818 changeSelection(list, EXTEND_SELECTION, list.getModel()
1819 .getSize() - 1, 1);
1820 } else if (name == SELECT_LAST_ROW_CHANGE_LEAD) {
1821 changeSelection(list, CHANGE_LEAD, list.getModel()
1822 .getSize() - 1, 1);
1823 } else if (name == SCROLL_UP) {
1824 changeSelection(list, CHANGE_SELECTION,
1825 getNextPageIndex(list, -1), -1);
1826 } else if (name == SCROLL_UP_EXTEND) {
1827 changeSelection(list, EXTEND_SELECTION,
1828 getNextPageIndex(list, -1), -1);
1829 } else if (name == SCROLL_UP_CHANGE_LEAD) {
1830 changeSelection(list, CHANGE_LEAD, getNextPageIndex(
1831 list, -1), -1);
1832 } else if (name == SCROLL_DOWN) {
1833 changeSelection(list, CHANGE_SELECTION,
1834 getNextPageIndex(list, 1), 1);
1835 } else if (name == SCROLL_DOWN_EXTEND) {
1836 changeSelection(list, EXTEND_SELECTION,
1837 getNextPageIndex(list, 1), 1);
1838 } else if (name == SCROLL_DOWN_CHANGE_LEAD) {
1839 changeSelection(list, CHANGE_LEAD, getNextPageIndex(
1840 list, 1), 1);
1841 } else if (name == SELECT_ALL) {
1842 selectAll(list);
1843 } else if (name == CLEAR_SELECTION) {
1844 clearSelection(list);
1845 } else if (name == ADD_TO_SELECTION) {
1846 int index = adjustIndex(list.getSelectionModel()
1847 .getLeadSelectionIndex(), list);
1848
1849 if (!list.isSelectedIndex(index)) {
1850 int oldAnchor = list.getSelectionModel()
1851 .getAnchorSelectionIndex();
1852 list.setValueIsAdjusting(true);
1853 list.addSelectionInterval(index, index);
1854 list.getSelectionModel().setAnchorSelectionIndex(
1855 oldAnchor);
1856 list.setValueIsAdjusting(false);
1857 }
1858 } else if (name == TOGGLE_AND_ANCHOR) {
1859 int index = adjustIndex(list.getSelectionModel()
1860 .getLeadSelectionIndex(), list);
1861
1862 if (list.isSelectedIndex(index)) {
1863 list.removeSelectionInterval(index, index);
1864 } else {
1865 list.addSelectionInterval(index, index);
1866 }
1867 } else if (name == EXTEND_TO) {
1868 changeSelection(list, EXTEND_SELECTION, adjustIndex(
1869 list.getSelectionModel()
1870 .getLeadSelectionIndex(), list), 0);
1871 } else if (name == MOVE_SELECTION_TO) {
1872 changeSelection(list, CHANGE_SELECTION, adjustIndex(
1873 list.getSelectionModel()
1874 .getLeadSelectionIndex(), list), 0);
1875 }
1876 }
1877
1878 public boolean isEnabled(Object c) {
1879 Object name = getName();
1880 if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD
1881 || name == SELECT_NEXT_COLUMN_CHANGE_LEAD
1882 || name == SELECT_PREVIOUS_ROW_CHANGE_LEAD
1883 || name == SELECT_NEXT_ROW_CHANGE_LEAD
1884 || name == SELECT_FIRST_ROW_CHANGE_LEAD
1885 || name == SELECT_LAST_ROW_CHANGE_LEAD
1886 || name == SCROLL_UP_CHANGE_LEAD
1887 || name == SCROLL_DOWN_CHANGE_LEAD) {
1888
1889 // discontinuous selection actions are only enabled for
1890 // DefaultListSelectionModel
1891 return c != null
1892 && ((JList) c).getSelectionModel() instanceof DefaultListSelectionModel;
1893 }
1894
1895 return true;
1896 }
1897
1898 private void clearSelection(JList list) {
1899 list.clearSelection();
1900 }
1901
1902 private void selectAll(JList list) {
1903 int size = list.getModel().getSize();
1904 if (size > 0) {
1905 ListSelectionModel lsm = list.getSelectionModel();
1906 int lead = adjustIndex(lsm.getLeadSelectionIndex(),
1907 list);
1908
1909 if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
1910 if (lead == -1) {
1911 int min = adjustIndex(list
1912 .getMinSelectionIndex(), list);
1913 lead = (min == -1 ? 0 : min);
1914 }
1915
1916 list.setSelectionInterval(lead, lead);
1917 list.ensureIndexIsVisible(lead);
1918 } else {
1919 list.setValueIsAdjusting(true);
1920
1921 int anchor = adjustIndex(lsm
1922 .getAnchorSelectionIndex(), list);
1923
1924 list.setSelectionInterval(0, size - 1);
1925
1926 // this is done to restore the anchor and lead
1927 SwingUtilities2.setLeadAnchorWithoutSelection(lsm,
1928 anchor, lead);
1929
1930 list.setValueIsAdjusting(false);
1931 }
1932 }
1933 }
1934
1935 private int getNextPageIndex(JList list, int direction) {
1936 if (list.getModel().getSize() == 0) {
1937 return -1;
1938 }
1939
1940 int index = -1;
1941 Rectangle visRect = list.getVisibleRect();
1942 ListSelectionModel lsm = list.getSelectionModel();
1943 int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
1944 Rectangle leadRect = (lead == -1) ? new Rectangle() : list
1945 .getCellBounds(lead, lead);
1946
1947 if (list.getLayoutOrientation() == JList.VERTICAL_WRAP
1948 && list.getVisibleRowCount() <= 0) {
1949 if (!list.getComponentOrientation().isLeftToRight()) {
1950 direction = -direction;
1951 }
1952 // apply for horizontal scrolling: the step for next
1953 // page index is number of visible columns
1954 if (direction < 0) {
1955 // left
1956 visRect.x = leadRect.x + leadRect.width
1957 - visRect.width;
1958 Point p = new Point(visRect.x - 1, leadRect.y);
1959 index = list.locationToIndex(p);
1960 Rectangle cellBounds = list.getCellBounds(index,
1961 index);
1962 if (visRect.intersects(cellBounds)) {
1963 p.x = cellBounds.x - 1;
1964 index = list.locationToIndex(p);
1965 cellBounds = list.getCellBounds(index, index);
1966 }
1967 // this is necessary for right-to-left orientation only
1968 if (cellBounds.y != leadRect.y) {
1969 p.x = cellBounds.x + cellBounds.width;
1970 index = list.locationToIndex(p);
1971 }
1972 } else {
1973 // right
1974 visRect.x = leadRect.x;
1975 Point p = new Point(visRect.x + visRect.width,
1976 leadRect.y);
1977 index = list.locationToIndex(p);
1978 Rectangle cellBounds = list.getCellBounds(index,
1979 index);
1980 if (visRect.intersects(cellBounds)) {
1981 p.x = cellBounds.x + cellBounds.width;
1982 index = list.locationToIndex(p);
1983 cellBounds = list.getCellBounds(index, index);
1984 }
1985 if (cellBounds.y != leadRect.y) {
1986 p.x = cellBounds.x - 1;
1987 index = list.locationToIndex(p);
1988 }
1989 }
1990 } else {
1991 if (direction < 0) {
1992 // up
1993 // go to the first visible cell
1994 Point p = new Point(leadRect.x, visRect.y);
1995 index = list.locationToIndex(p);
1996 if (lead <= index) {
1997 // if lead is the first visible cell (or above it)
1998 // adjust the visible rect up
1999 visRect.y = leadRect.y + leadRect.height
2000 - visRect.height;
2001 p.y = visRect.y;
2002 index = list.locationToIndex(p);
2003 Rectangle cellBounds = list.getCellBounds(
2004 index, index);
2005 // go one cell down if first visible cell doesn't fit
2006 // into adjasted visible rectangle
2007 if (cellBounds.y < visRect.y) {
2008 p.y = cellBounds.y + cellBounds.height;
2009 index = list.locationToIndex(p);
2010 cellBounds = list.getCellBounds(index,
2011 index);
2012 }
2013 // if index isn't less then lead
2014 // try to go to cell previous to lead
2015 if (cellBounds.y >= leadRect.y) {
2016 p.y = leadRect.y - 1;
2017 index = list.locationToIndex(p);
2018 }
2019 }
2020 } else {
2021 // down
2022 // go to the last completely visible cell
2023 Point p = new Point(leadRect.x, visRect.y
2024 + visRect.height - 1);
2025 index = list.locationToIndex(p);
2026 Rectangle cellBounds = list.getCellBounds(index,
2027 index);
2028 // go up one cell if last visible cell doesn't fit
2029 // into visible rectangle
2030 if (cellBounds.y + cellBounds.height > visRect.y
2031 + visRect.height) {
2032 p.y = cellBounds.y - 1;
2033 index = list.locationToIndex(p);
2034 cellBounds = list.getCellBounds(index, index);
2035 index = Math.max(index, lead);
2036 }
2037
2038 if (lead >= index) {
2039 // if lead is the last completely visible index
2040 // (or below it) adjust the visible rect down
2041 visRect.y = leadRect.y;
2042 p.y = visRect.y + visRect.height - 1;
2043 index = list.locationToIndex(p);
2044 cellBounds = list.getCellBounds(index, index);
2045 // go one cell up if last visible cell doesn't fit
2046 // into adjasted visible rectangle
2047 if (cellBounds.y + cellBounds.height > visRect.y
2048 + visRect.height) {
2049 p.y = cellBounds.y - 1;
2050 index = list.locationToIndex(p);
2051 cellBounds = list.getCellBounds(index,
2052 index);
2053 }
2054 // if index isn't greater then lead
2055 // try to go to cell next after lead
2056 if (cellBounds.y <= leadRect.y) {
2057 p.y = leadRect.y + leadRect.height;
2058 index = list.locationToIndex(p);
2059 }
2060 }
2061 }
2062 }
2063 return index;
2064 }
2065
2066 private void changeSelection(JList list, int type, int index,
2067 int direction) {
2068 if (index >= 0 && index < list.getModel().getSize()) {
2069 ListSelectionModel lsm = list.getSelectionModel();
2070
2071 // CHANGE_LEAD is only valid with multiple interval selection
2072 if (type == CHANGE_LEAD
2073 && list.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
2074
2075 type = CHANGE_SELECTION;
2076 }
2077
2078 // IMPORTANT - This needs to happen before the index is changed.
2079 // This is because JFileChooser, which uses JList, also scrolls
2080 // the selected item into view. If that happens first, then
2081 // this method becomes a no-op.
2082 adjustScrollPositionIfNecessary(list, index, direction);
2083
2084 if (type == EXTEND_SELECTION) {
2085 int anchor = adjustIndex(lsm
2086 .getAnchorSelectionIndex(), list);
2087 if (anchor == -1) {
2088 anchor = 0;
2089 }
2090
2091 list.setSelectionInterval(anchor, index);
2092 } else if (type == CHANGE_SELECTION) {
2093 list.setSelectedIndex(index);
2094 } else {
2095 // casting should be safe since the action is only enabled
2096 // for DefaultListSelectionModel
2097 ((DefaultListSelectionModel) lsm)
2098 .moveLeadSelectionIndex(index);
2099 }
2100 }
2101 }
2102
2103 /**
2104 * When scroll down makes selected index the last completely visible
2105 * index. When scroll up makes selected index the first visible index.
2106 * Adjust visible rectangle respect to list's component orientation.
2107 */
2108 private void adjustScrollPositionIfNecessary(JList list,
2109 int index, int direction) {
2110 if (direction == 0) {
2111 return;
2112 }
2113 Rectangle cellBounds = list.getCellBounds(index, index);
2114 Rectangle visRect = list.getVisibleRect();
2115 if (cellBounds != null && !visRect.contains(cellBounds)) {
2116 if (list.getLayoutOrientation() == JList.VERTICAL_WRAP
2117 && list.getVisibleRowCount() <= 0) {
2118 // horizontal
2119 if (list.getComponentOrientation().isLeftToRight()) {
2120 if (direction > 0) {
2121 // right for left-to-right
2122 int x = Math.max(0, cellBounds.x
2123 + cellBounds.width - visRect.width);
2124 int startIndex = list
2125 .locationToIndex(new Point(x,
2126 cellBounds.y));
2127 Rectangle startRect = list.getCellBounds(
2128 startIndex, startIndex);
2129 if (startRect.x < x
2130 && startRect.x < cellBounds.x) {
2131 startRect.x += startRect.width;
2132 startIndex = list
2133 .locationToIndex(startRect
2134 .getLocation());
2135 startRect = list.getCellBounds(
2136 startIndex, startIndex);
2137 }
2138 cellBounds = startRect;
2139 }
2140 cellBounds.width = visRect.width;
2141 } else {
2142 if (direction > 0) {
2143 // left for right-to-left
2144 int x = cellBounds.x + visRect.width;
2145 int rightIndex = list
2146 .locationToIndex(new Point(x,
2147 cellBounds.y));
2148 Rectangle rightRect = list.getCellBounds(
2149 rightIndex, rightIndex);
2150 if (rightRect.x + rightRect.width > x
2151 && rightRect.x > cellBounds.x) {
2152 rightRect.width = 0;
2153 }
2154 cellBounds.x = Math.max(0, rightRect.x
2155 + rightRect.width - visRect.width);
2156 cellBounds.width = visRect.width;
2157 } else {
2158 cellBounds.x += Math.max(0,
2159 cellBounds.width - visRect.width);
2160 // adjust width to fit into visible rectangle
2161 cellBounds.width = Math.min(
2162 cellBounds.width, visRect.width);
2163 }
2164 }
2165 } else {
2166 // vertical
2167 if (direction > 0) {
2168 //down
2169 int y = Math.max(0, cellBounds.y
2170 + cellBounds.height - visRect.height);
2171 int startIndex = list
2172 .locationToIndex(new Point(
2173 cellBounds.x, y));
2174 Rectangle startRect = list.getCellBounds(
2175 startIndex, startIndex);
2176 if (startRect.y < y
2177 && startRect.y < cellBounds.y) {
2178 startRect.y += startRect.height;
2179 startIndex = list.locationToIndex(startRect
2180 .getLocation());
2181 startRect = list.getCellBounds(startIndex,
2182 startIndex);
2183 }
2184 cellBounds = startRect;
2185 cellBounds.height = visRect.height;
2186 } else {
2187 // adjust height to fit into visible rectangle
2188 cellBounds.height = Math.min(cellBounds.height,
2189 visRect.height);
2190 }
2191 }
2192 list.scrollRectToVisible(cellBounds);
2193 }
2194 }
2195
2196 private int getNextColumnIndex(JList list, BasicListUI ui,
2197 int amount) {
2198 if (list.getLayoutOrientation() != JList.VERTICAL) {
2199 int index = adjustIndex(list.getLeadSelectionIndex(),
2200 list);
2201 int size = list.getModel().getSize();
2202
2203 if (index == -1) {
2204 return 0;
2205 } else if (size == 1) {
2206 // there's only one item so we should select it
2207 return 0;
2208 } else if (ui == null || ui.columnCount <= 1) {
2209 return -1;
2210 }
2211
2212 int column = ui.convertModelToColumn(index);
2213 int row = ui.convertModelToRow(index);
2214
2215 column += amount;
2216 if (column >= ui.columnCount || column < 0) {
2217 // No wrapping.
2218 return -1;
2219 }
2220 int maxRowCount = ui.getRowCount(column);
2221 if (row >= maxRowCount) {
2222 return -1;
2223 }
2224 return ui.getModelIndex(column, row);
2225 }
2226 // Won't change the selection.
2227 return -1;
2228 }
2229
2230 private int getNextIndex(JList list, BasicListUI ui, int amount) {
2231 int index = adjustIndex(list.getLeadSelectionIndex(), list);
2232 int size = list.getModel().getSize();
2233
2234 if (index == -1) {
2235 if (size > 0) {
2236 if (amount > 0) {
2237 index = 0;
2238 } else {
2239 index = size - 1;
2240 }
2241 }
2242 } else if (size == 1) {
2243 // there's only one item so we should select it
2244 index = 0;
2245 } else if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
2246 if (ui != null) {
2247 index += ui.columnCount * amount;
2248 }
2249 } else {
2250 index += amount;
2251 }
2252
2253 return index;
2254 }
2255 }
2256
2257 private class Handler implements FocusListener, KeyListener,
2258 ListDataListener, ListSelectionListener,
2259 MouseInputListener, PropertyChangeListener, BeforeDrag {
2260 //
2261 // KeyListener
2262 //
2263 private String prefix = "";
2264 private String typedString = "";
2265 private long lastTime = 0L;
2266
2267 /**
2268 * Invoked when a key has been typed.
2269 *
2270 * Moves the keyboard focus to the first element whose prefix matches the
2271 * sequence of alphanumeric keys pressed by the user with delay less
2272 * than value of <code>timeFactor</code> property (or 1000 milliseconds
2273 * if it is not defined). Subsequent same key presses move the keyboard
2274 * focus to the next object that starts with the same letter until another
2275 * key is pressed, then it is treated as the prefix with appropriate number
2276 * of the same letters followed by first typed another letter.
2277 */
2278 public void keyTyped(KeyEvent e) {
2279 JList src = (JList) e.getSource();
2280 ListModel model = src.getModel();
2281
2282 if (model.getSize() == 0 || e.isAltDown()
2283 || e.isControlDown() || e.isMetaDown()
2284 || isNavigationKey(e)) {
2285 // Nothing to select
2286 return;
2287 }
2288 boolean startingFromSelection = true;
2289
2290 char c = e.getKeyChar();
2291
2292 long time = e.getWhen();
2293 int startIndex = adjustIndex(src.getLeadSelectionIndex(),
2294 list);
2295 if (time - lastTime < timeFactor) {
2296 typedString += c;
2297 if ((prefix.length() == 1) && (c == prefix.charAt(0))) {
2298 // Subsequent same key presses move the keyboard focus to the next
2299 // object that starts with the same letter.
2300 startIndex++;
2301 } else {
2302 prefix = typedString;
2303 }
2304 } else {
2305 startIndex++;
2306 typedString = "" + c;
2307 prefix = typedString;
2308 }
2309 lastTime = time;
2310
2311 if (startIndex < 0 || startIndex >= model.getSize()) {
2312 startingFromSelection = false;
2313 startIndex = 0;
2314 }
2315 int index = src.getNextMatch(prefix, startIndex,
2316 Position.Bias.Forward);
2317 if (index >= 0) {
2318 src.setSelectedIndex(index);
2319 src.ensureIndexIsVisible(index);
2320 } else if (startingFromSelection) { // wrap
2321 index = src.getNextMatch(prefix, 0,
2322 Position.Bias.Forward);
2323 if (index >= 0) {
2324 src.setSelectedIndex(index);
2325 src.ensureIndexIsVisible(index);
2326 }
2327 }
2328 }
2329
2330 /**
2331 * Invoked when a key has been pressed.
2332 *
2333 * Checks to see if the key event is a navigation key to prevent
2334 * dispatching these keys for the first letter navigation.
2335 */
2336 public void keyPressed(KeyEvent e) {
2337 if (isNavigationKey(e)) {
2338 prefix = "";
2339 typedString = "";
2340 lastTime = 0L;
2341 }
2342 }
2343
2344 /**
2345 * Invoked when a key has been released.
2346 * See the class description for {@link KeyEvent} for a definition of
2347 * a key released event.
2348 */
2349 public void keyReleased(KeyEvent e) {
2350 }
2351
2352 /**
2353 * Returns whether or not the supplied key event maps to a key that is used for
2354 * navigation. This is used for optimizing key input by only passing non-
2355 * navigation keys to the first letter navigation mechanism.
2356 */
2357 private boolean isNavigationKey(KeyEvent event) {
2358 InputMap inputMap = list
2359 .getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
2360 KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
2361
2362 if (inputMap != null && inputMap.get(key) != null) {
2363 return true;
2364 }
2365 return false;
2366 }
2367
2368 //
2369 // PropertyChangeListener
2370 //
2371 public void propertyChange(PropertyChangeEvent e) {
2372 String propertyName = e.getPropertyName();
2373
2374 /* If the JList.model property changes, remove our listener,
2375 * listDataListener from the old model and add it to the new one.
2376 */
2377 if (propertyName == "model") {
2378 ListModel oldModel = (ListModel) e.getOldValue();
2379 ListModel newModel = (ListModel) e.getNewValue();
2380 if (oldModel != null) {
2381 oldModel.removeListDataListener(listDataListener);
2382 }
2383 if (newModel != null) {
2384 newModel.addListDataListener(listDataListener);
2385 }
2386 updateLayoutStateNeeded |= modelChanged;
2387 redrawList();
2388 }
2389
2390 /* If the JList.selectionModel property changes, remove our listener,
2391 * listSelectionListener from the old selectionModel and add it to the new one.
2392 */
2393 else if (propertyName == "selectionModel") {
2394 ListSelectionModel oldModel = (ListSelectionModel) e
2395 .getOldValue();
2396 ListSelectionModel newModel = (ListSelectionModel) e
2397 .getNewValue();
2398 if (oldModel != null) {
2399 oldModel
2400 .removeListSelectionListener(listSelectionListener);
2401 }
2402 if (newModel != null) {
2403 newModel
2404 .addListSelectionListener(listSelectionListener);
2405 }
2406 updateLayoutStateNeeded |= modelChanged;
2407 redrawList();
2408 } else if (propertyName == "cellRenderer") {
2409 updateLayoutStateNeeded |= cellRendererChanged;
2410 redrawList();
2411 } else if (propertyName == "font") {
2412 updateLayoutStateNeeded |= fontChanged;
2413 redrawList();
2414 } else if (propertyName == "prototypeCellValue") {
2415 updateLayoutStateNeeded |= prototypeCellValueChanged;
2416 redrawList();
2417 } else if (propertyName == "fixedCellHeight") {
2418 updateLayoutStateNeeded |= fixedCellHeightChanged;
2419 redrawList();
2420 } else if (propertyName == "fixedCellWidth") {
2421 updateLayoutStateNeeded |= fixedCellWidthChanged;
2422 redrawList();
2423 } else if (propertyName == "selectionForeground") {
2424 list.repaint();
2425 } else if (propertyName == "selectionBackground") {
2426 list.repaint();
2427 } else if ("layoutOrientation" == propertyName) {
2428 updateLayoutStateNeeded |= layoutOrientationChanged;
2429 layoutOrientation = list.getLayoutOrientation();
2430 redrawList();
2431 } else if ("visibleRowCount" == propertyName) {
2432 if (layoutOrientation != JList.VERTICAL) {
2433 updateLayoutStateNeeded |= layoutOrientationChanged;
2434 redrawList();
2435 }
2436 } else if ("componentOrientation" == propertyName) {
2437 isLeftToRight = list.getComponentOrientation()
2438 .isLeftToRight();
2439 updateLayoutStateNeeded |= componentOrientationChanged;
2440 redrawList();
2441
2442 InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
2443 SwingUtilities.replaceUIInputMap(list,
2444 JComponent.WHEN_FOCUSED, inputMap);
2445 } else if ("List.isFileList" == propertyName) {
2446 updateIsFileList();
2447 redrawList();
2448 } else if ("dropLocation" == propertyName) {
2449 JList.DropLocation oldValue = (JList.DropLocation) e
2450 .getOldValue();
2451 repaintDropLocation(oldValue);
2452 repaintDropLocation(list.getDropLocation());
2453 }
2454 }
2455
2456 private void repaintDropLocation(JList.DropLocation loc) {
2457 if (loc == null) {
2458 return;
2459 }
2460
2461 Rectangle r;
2462
2463 if (loc.isInsert()) {
2464 r = getDropLineRect(loc);
2465 } else {
2466 r = getCellBounds(list, loc.getIndex());
2467 }
2468
2469 if (r != null) {
2470 list.repaint(r);
2471 }
2472 }
2473
2474 //
2475 // ListDataListener
2476 //
2477 public void intervalAdded(ListDataEvent e) {
2478 updateLayoutStateNeeded = modelChanged;
2479
2480 int minIndex = Math.min(e.getIndex0(), e.getIndex1());
2481 int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
2482
2483 /* Sync the SelectionModel with the DataModel.
2484 */
2485
2486 ListSelectionModel sm = list.getSelectionModel();
2487 if (sm != null) {
2488 sm.insertIndexInterval(minIndex, maxIndex - minIndex
2489 + 1, true);
2490 }
2491
2492 /* Repaint the entire list, from the origin of
2493 * the first added cell, to the bottom of the
2494 * component.
2495 */
2496 redrawList();
2497 }
2498
2499 public void intervalRemoved(ListDataEvent e) {
2500 updateLayoutStateNeeded = modelChanged;
2501
2502 /* Sync the SelectionModel with the DataModel.
2503 */
2504
2505 ListSelectionModel sm = list.getSelectionModel();
2506 if (sm != null) {
2507 sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
2508 }
2509
2510 /* Repaint the entire list, from the origin of
2511 * the first removed cell, to the bottom of the
2512 * component.
2513 */
2514
2515 redrawList();
2516 }
2517
2518 public void contentsChanged(ListDataEvent e) {
2519 updateLayoutStateNeeded = modelChanged;
2520 redrawList();
2521 }
2522
2523 //
2524 // ListSelectionListener
2525 //
2526 public void valueChanged(ListSelectionEvent e) {
2527 maybeUpdateLayoutState();
2528
2529 int size = list.getModel().getSize();
2530 int firstIndex = Math.min(size - 1, Math.max(e
2531 .getFirstIndex(), 0));
2532 int lastIndex = Math.min(size - 1, Math.max(e
2533 .getLastIndex(), 0));
2534
2535 Rectangle bounds = getCellBounds(list, firstIndex,
2536 lastIndex);
2537
2538 if (bounds != null) {
2539 list.repaint(bounds.x, bounds.y, bounds.width,
2540 bounds.height);
2541 }
2542 }
2543
2544 //
2545 // MouseListener
2546 //
2547 public void mouseClicked(MouseEvent e) {
2548 }
2549
2550 public void mouseEntered(MouseEvent e) {
2551 }
2552
2553 public void mouseExited(MouseEvent e) {
2554 }
2555
2556 // Whether or not the mouse press (which is being considered as part
2557 // of a drag sequence) also caused the selection change to be fully
2558 // processed.
2559 private boolean dragPressDidSelection;
2560
2561 public void mousePressed(MouseEvent e) {
2562 if (SwingUtilities2.shouldIgnore(e, list)) {
2563 return;
2564 }
2565
2566 boolean dragEnabled = list.getDragEnabled();
2567 boolean grabFocus = true;
2568
2569 // different behavior if drag is enabled
2570 if (dragEnabled) {
2571 int row = SwingUtilities2.loc2IndexFileList(list, e
2572 .getPoint());
2573 // if we have a valid row and this is a drag initiating event
2574 if (row != -1 && DragRecognitionSupport.mousePressed(e)) {
2575 dragPressDidSelection = false;
2576
2577 if (e.isControlDown()) {
2578 // do nothing for control - will be handled on release
2579 // or when drag starts
2580 return;
2581 } else if (!e.isShiftDown()
2582 && list.isSelectedIndex(row)) {
2583 // clicking on something that's already selected
2584 // and need to make it the lead now
2585 list.addSelectionInterval(row, row);
2586 return;
2587 }
2588
2589 // could be a drag initiating event - don't grab focus
2590 grabFocus = false;
2591
2592 dragPressDidSelection = true;
2593 }
2594 } else {
2595 // When drag is enabled mouse drags won't change the selection
2596 // in the list, so we only set the isAdjusting flag when it's
2597 // not enabled
2598 list.setValueIsAdjusting(true);
2599 }
2600
2601 if (grabFocus) {
2602 SwingUtilities2.adjustFocus(list);
2603 }
2604
2605 adjustSelection(e);
2606 }
2607
2608 private void adjustSelection(MouseEvent e) {
2609 int row = SwingUtilities2.loc2IndexFileList(list, e
2610 .getPoint());
2611 if (row < 0) {
2612 // If shift is down in multi-select, we should do nothing.
2613 // For single select or non-shift-click, clear the selection
2614 if (isFileList
2615 && e.getID() == MouseEvent.MOUSE_PRESSED
2616 && (!e.isShiftDown() || list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
2617 list.clearSelection();
2618 }
2619 } else {
2620 int anchorIndex = adjustIndex(list
2621 .getAnchorSelectionIndex(), list);
2622 boolean anchorSelected;
2623 if (anchorIndex == -1) {
2624 anchorIndex = 0;
2625 anchorSelected = false;
2626 } else {
2627 anchorSelected = list.isSelectedIndex(anchorIndex);
2628 }
2629
2630 if (e.isControlDown()) {
2631 if (e.isShiftDown()) {
2632 if (anchorSelected) {
2633 list.addSelectionInterval(anchorIndex, row);
2634 } else {
2635 list.removeSelectionInterval(anchorIndex,
2636 row);
2637 if (isFileList) {
2638 list.addSelectionInterval(row, row);
2639 list.getSelectionModel()
2640 .setAnchorSelectionIndex(
2641 anchorIndex);
2642 }
2643 }
2644 } else if (list.isSelectedIndex(row)) {
2645 list.removeSelectionInterval(row, row);
2646 } else {
2647 list.addSelectionInterval(row, row);
2648 }
2649 } else if (e.isShiftDown()) {
2650 list.setSelectionInterval(anchorIndex, row);
2651 } else {
2652 list.setSelectionInterval(row, row);
2653 }
2654 }
2655 }
2656
2657 public void dragStarting(MouseEvent me) {
2658 if (me.isControlDown()) {
2659 int row = SwingUtilities2.loc2IndexFileList(list, me
2660 .getPoint());
2661 list.addSelectionInterval(row, row);
2662 }
2663 }
2664
2665 public void mouseDragged(MouseEvent e) {
2666 if (SwingUtilities2.shouldIgnore(e, list)) {
2667 return;
2668 }
2669
2670 if (list.getDragEnabled()) {
2671 DragRecognitionSupport.mouseDragged(e, this );
2672 return;
2673 }
2674
2675 if (e.isShiftDown() || e.isControlDown()) {
2676 return;
2677 }
2678
2679 int row = locationToIndex(list, e.getPoint());
2680 if (row != -1) {
2681 // 4835633. Dragging onto a File should not select it.
2682 if (isFileList) {
2683 return;
2684 }
2685 Rectangle cellBounds = getCellBounds(list, row, row);
2686 if (cellBounds != null) {
2687 list.scrollRectToVisible(cellBounds);
2688 list.setSelectionInterval(row, row);
2689 }
2690 }
2691 }
2692
2693 public void mouseMoved(MouseEvent e) {
2694 }
2695
2696 public void mouseReleased(MouseEvent e) {
2697 if (SwingUtilities2.shouldIgnore(e, list)) {
2698 return;
2699 }
2700
2701 if (list.getDragEnabled()) {
2702 MouseEvent me = DragRecognitionSupport.mouseReleased(e);
2703 if (me != null) {
2704 SwingUtilities2.adjustFocus(list);
2705 if (!dragPressDidSelection) {
2706 adjustSelection(me);
2707 }
2708 }
2709 } else {
2710 list.setValueIsAdjusting(false);
2711 }
2712 }
2713
2714 //
2715 // FocusListener
2716 //
2717 protected void repaintCellFocus() {
2718 int leadIndex = adjustIndex(list.getLeadSelectionIndex(),
2719 list);
2720 if (leadIndex != -1) {
2721 Rectangle r = getCellBounds(list, leadIndex, leadIndex);
2722 if (r != null) {
2723 list.repaint(r.x, r.y, r.width, r.height);
2724 }
2725 }
2726 }
2727
2728 /* The focusGained() focusLost() methods run when the JList
2729 * focus changes.
2730 */
2731
2732 public void focusGained(FocusEvent e) {
2733 repaintCellFocus();
2734 }
2735
2736 public void focusLost(FocusEvent e) {
2737 repaintCellFocus();
2738 }
2739 }
2740
2741 private static int adjustIndex(int index, JList list) {
2742 return index < list.getModel().getSize() ? index : -1;
2743 }
2744
2745 private static final TransferHandler defaultTransferHandler = new ListTransferHandler();
2746
2747 static class ListTransferHandler extends TransferHandler implements
2748 UIResource {
2749
2750 /**
2751 * Create a Transferable to use as the source for a data transfer.
2752 *
2753 * @param c The component holding the data to be transfered. This
2754 * argument is provided to enable sharing of TransferHandlers by
2755 * multiple components.
2756 * @return The representation of the data to be transfered.
2757 *
2758 */
2759 protected Transferable createTransferable(JComponent c) {
2760 if (c instanceof JList) {
2761 JList list = (JList) c;
2762 Object[] values = list.getSelectedValues();
2763
2764 if (values == null || values.length == 0) {
2765 return null;
2766 }
2767
2768 StringBuffer plainBuf = new StringBuffer();
2769 StringBuffer htmlBuf = new StringBuffer();
2770
2771 htmlBuf.append("<html>\n<body>\n<ul>\n");
2772
2773 for (int i = 0; i < values.length; i++) {
2774 Object obj = values[i];
2775 String val = ((obj == null) ? "" : obj.toString());
2776 plainBuf.append(val + "\n");
2777 htmlBuf.append(" <li>" + val + "\n");
2778 }
2779
2780 // remove the last newline
2781 plainBuf.deleteCharAt(plainBuf.length() - 1);
2782 htmlBuf.append("</ul>\n</body>\n</html>");
2783
2784 return new BasicTransferable(plainBuf.toString(),
2785 htmlBuf.toString());
2786 }
2787
2788 return null;
2789 }
2790
2791 public int getSourceActions(JComponent c) {
2792 return COPY;
2793 }
2794
2795 }
2796 }
|