You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2881 lines
104 KiB
2881 lines
104 KiB
/*
|
|
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
package javax.swing.plaf.basic;
|
|
|
|
import sun.swing.DefaultLookup;
|
|
import sun.swing.UIAction;
|
|
|
|
import javax.swing.*;
|
|
import javax.swing.event.*;
|
|
import javax.swing.plaf.*;
|
|
import javax.swing.text.Position;
|
|
|
|
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import java.awt.datatransfer.Transferable;
|
|
import java.awt.geom.Point2D;
|
|
|
|
import java.beans.PropertyChangeListener;
|
|
import java.beans.PropertyChangeEvent;
|
|
|
|
import sun.swing.SwingUtilities2;
|
|
import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
|
|
|
|
/**
|
|
* An extensible implementation of {@code ListUI}.
|
|
* <p>
|
|
* {@code BasicListUI} instances cannot be shared between multiple
|
|
* lists.
|
|
*
|
|
* @author Hans Muller
|
|
* @author Philip Milne
|
|
* @author Shannon Hickey (drag and drop)
|
|
*/
|
|
public class BasicListUI extends ListUI
|
|
{
|
|
private static final StringBuilder BASELINE_COMPONENT_KEY =
|
|
new StringBuilder("List.baselineComponent");
|
|
|
|
protected JList list = null;
|
|
protected CellRendererPane rendererPane;
|
|
|
|
// Listeners that this UI attaches to the JList
|
|
protected FocusListener focusListener;
|
|
protected MouseInputListener mouseInputListener;
|
|
protected ListSelectionListener listSelectionListener;
|
|
protected ListDataListener listDataListener;
|
|
protected PropertyChangeListener propertyChangeListener;
|
|
private Handler handler;
|
|
|
|
protected int[] cellHeights = null;
|
|
protected int cellHeight = -1;
|
|
protected int cellWidth = -1;
|
|
protected int updateLayoutStateNeeded = modelChanged;
|
|
/**
|
|
* Height of the list. When asked to paint, if the current size of
|
|
* the list differs, this will update the layout state.
|
|
*/
|
|
private int listHeight;
|
|
|
|
/**
|
|
* Width of the list. When asked to paint, if the current size of
|
|
* the list differs, this will update the layout state.
|
|
*/
|
|
private int listWidth;
|
|
|
|
/**
|
|
* The layout orientation of the list.
|
|
*/
|
|
private int layoutOrientation;
|
|
|
|
// Following ivars are used if the list is laying out horizontally
|
|
|
|
/**
|
|
* Number of columns to create.
|
|
*/
|
|
private int columnCount;
|
|
/**
|
|
* Preferred height to make the list, this is only used if the
|
|
* the list is layed out horizontally.
|
|
*/
|
|
private int preferredHeight;
|
|
/**
|
|
* Number of rows per column. This is only used if the row height is
|
|
* fixed.
|
|
*/
|
|
private int rowsPerColumn;
|
|
|
|
/**
|
|
* The time factor to treate the series of typed alphanumeric key
|
|
* as prefix for first letter navigation.
|
|
*/
|
|
private long timeFactor = 1000L;
|
|
|
|
/**
|
|
* Local cache of JList's client property "List.isFileList"
|
|
*/
|
|
private boolean isFileList = false;
|
|
|
|
/**
|
|
* Local cache of JList's component orientation property
|
|
*/
|
|
private boolean isLeftToRight = true;
|
|
|
|
/* The bits below define JList property changes that affect layout.
|
|
* When one of these properties changes we set a bit in
|
|
* updateLayoutStateNeeded. The change is dealt with lazily, see
|
|
* maybeUpdateLayoutState. Changes to the JLists model, e.g. the
|
|
* models length changed, are handled similarly, see DataListener.
|
|
*/
|
|
|
|
protected final static int modelChanged = 1 << 0;
|
|
protected final static int selectionModelChanged = 1 << 1;
|
|
protected final static int fontChanged = 1 << 2;
|
|
protected final static int fixedCellWidthChanged = 1 << 3;
|
|
protected final static int fixedCellHeightChanged = 1 << 4;
|
|
protected final static int prototypeCellValueChanged = 1 << 5;
|
|
protected final static int cellRendererChanged = 1 << 6;
|
|
private final static int layoutOrientationChanged = 1 << 7;
|
|
private final static int heightChanged = 1 << 8;
|
|
private final static int widthChanged = 1 << 9;
|
|
private final static int componentOrientationChanged = 1 << 10;
|
|
|
|
private static final int DROP_LINE_THICKNESS = 2;
|
|
|
|
static void loadActionMap(LazyActionMap map) {
|
|
map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN));
|
|
map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_EXTEND));
|
|
map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD));
|
|
map.put(new Actions(Actions.SELECT_NEXT_COLUMN));
|
|
map.put(new Actions(Actions.SELECT_NEXT_COLUMN_EXTEND));
|
|
map.put(new Actions(Actions.SELECT_NEXT_COLUMN_CHANGE_LEAD));
|
|
map.put(new Actions(Actions.SELECT_PREVIOUS_ROW));
|
|
map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_EXTEND));
|
|
map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_CHANGE_LEAD));
|
|
map.put(new Actions(Actions.SELECT_NEXT_ROW));
|
|
map.put(new Actions(Actions.SELECT_NEXT_ROW_EXTEND));
|
|
map.put(new Actions(Actions.SELECT_NEXT_ROW_CHANGE_LEAD));
|
|
map.put(new Actions(Actions.SELECT_FIRST_ROW));
|
|
map.put(new Actions(Actions.SELECT_FIRST_ROW_EXTEND));
|
|
map.put(new Actions(Actions.SELECT_FIRST_ROW_CHANGE_LEAD));
|
|
map.put(new Actions(Actions.SELECT_LAST_ROW));
|
|
map.put(new Actions(Actions.SELECT_LAST_ROW_EXTEND));
|
|
map.put(new Actions(Actions.SELECT_LAST_ROW_CHANGE_LEAD));
|
|
map.put(new Actions(Actions.SCROLL_UP));
|
|
map.put(new Actions(Actions.SCROLL_UP_EXTEND));
|
|
map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
|
|
map.put(new Actions(Actions.SCROLL_DOWN));
|
|
map.put(new Actions(Actions.SCROLL_DOWN_EXTEND));
|
|
map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
|
|
map.put(new Actions(Actions.SELECT_ALL));
|
|
map.put(new Actions(Actions.CLEAR_SELECTION));
|
|
map.put(new Actions(Actions.ADD_TO_SELECTION));
|
|
map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
|
|
map.put(new Actions(Actions.EXTEND_TO));
|
|
map.put(new Actions(Actions.MOVE_SELECTION_TO));
|
|
|
|
map.put(TransferHandler.getCutAction().getValue(Action.NAME),
|
|
TransferHandler.getCutAction());
|
|
map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
|
|
TransferHandler.getCopyAction());
|
|
map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
|
|
TransferHandler.getPasteAction());
|
|
}
|
|
|
|
/**
|
|
* Paint one List cell: compute the relevant state, get the "rubber stamp"
|
|
* cell renderer component, and then use the CellRendererPane to paint it.
|
|
* Subclasses may want to override this method rather than paint().
|
|
*
|
|
* @see #paint
|
|
*/
|
|
protected void paintCell(
|
|
Graphics g,
|
|
int row,
|
|
Rectangle rowBounds,
|
|
ListCellRenderer cellRenderer,
|
|
ListModel dataModel,
|
|
ListSelectionModel selModel,
|
|
int leadIndex)
|
|
{
|
|
Object value = dataModel.getElementAt(row);
|
|
boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
|
|
boolean isSelected = selModel.isSelectedIndex(row);
|
|
|
|
Component rendererComponent =
|
|
cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
|
|
|
|
int cx = rowBounds.x;
|
|
int cy = rowBounds.y;
|
|
int cw = rowBounds.width;
|
|
int ch = rowBounds.height;
|
|
|
|
if (isFileList) {
|
|
// Shrink renderer to preferred size. This is mostly used on Windows
|
|
// where selection is only shown around the file name, instead of
|
|
// across the whole list cell.
|
|
int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
|
|
if (!isLeftToRight) {
|
|
cx += (cw - w);
|
|
}
|
|
cw = w;
|
|
}
|
|
|
|
rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
|
|
}
|
|
|
|
|
|
/**
|
|
* Paint the rows that intersect the Graphics objects clipRect. This
|
|
* method calls paintCell as necessary. Subclasses
|
|
* may want to override these methods.
|
|
*
|
|
* @see #paintCell
|
|
*/
|
|
public void paint(Graphics g, JComponent c) {
|
|
Shape clip = g.getClip();
|
|
paintImpl(g, c);
|
|
g.setClip(clip);
|
|
|
|
paintDropLine(g);
|
|
}
|
|
|
|
private void paintImpl(Graphics g, JComponent c)
|
|
{
|
|
switch (layoutOrientation) {
|
|
case JList.VERTICAL_WRAP:
|
|
if (list.getHeight() != listHeight) {
|
|
updateLayoutStateNeeded |= heightChanged;
|
|
redrawList();
|
|
}
|
|
break;
|
|
case JList.HORIZONTAL_WRAP:
|
|
if (list.getWidth() != listWidth) {
|
|
updateLayoutStateNeeded |= widthChanged;
|
|
redrawList();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
maybeUpdateLayoutState();
|
|
|
|
ListCellRenderer renderer = list.getCellRenderer();
|
|
ListModel dataModel = list.getModel();
|
|
ListSelectionModel selModel = list.getSelectionModel();
|
|
int size;
|
|
|
|
if ((renderer == null) || (size = dataModel.getSize()) == 0) {
|
|
return;
|
|
}
|
|
|
|
// Determine how many columns we need to paint
|
|
Rectangle paintBounds = g.getClipBounds();
|
|
|
|
int startColumn, endColumn;
|
|
if (c.getComponentOrientation().isLeftToRight()) {
|
|
startColumn = convertLocationToColumn(paintBounds.x,
|
|
paintBounds.y);
|
|
endColumn = convertLocationToColumn(paintBounds.x +
|
|
paintBounds.width,
|
|
paintBounds.y);
|
|
} else {
|
|
startColumn = convertLocationToColumn(paintBounds.x +
|
|
paintBounds.width,
|
|
paintBounds.y);
|
|
endColumn = convertLocationToColumn(paintBounds.x,
|
|
paintBounds.y);
|
|
}
|
|
int maxY = paintBounds.y + paintBounds.height;
|
|
int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
|
|
int rowIncrement = (layoutOrientation == JList.HORIZONTAL_WRAP) ?
|
|
columnCount : 1;
|
|
|
|
|
|
for (int colCounter = startColumn; colCounter <= endColumn;
|
|
colCounter++) {
|
|
// And then how many rows in this columnn
|
|
int row = convertLocationToRowInColumn(paintBounds.y, colCounter);
|
|
int rowCount = getRowCount(colCounter);
|
|
int index = getModelIndex(colCounter, row);
|
|
Rectangle rowBounds = getCellBounds(list, index, index);
|
|
|
|
if (rowBounds == null) {
|
|
// Not valid, bail!
|
|
return;
|
|
}
|
|
while (row < rowCount && rowBounds.y < maxY &&
|
|
index < size) {
|
|
rowBounds.height = getHeight(colCounter, row);
|
|
g.setClip(rowBounds.x, rowBounds.y, rowBounds.width,
|
|
rowBounds.height);
|
|
g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width,
|
|
paintBounds.height);
|
|
paintCell(g, index, rowBounds, renderer, dataModel, selModel,
|
|
leadIndex);
|
|
rowBounds.y += rowBounds.height;
|
|
index += rowIncrement;
|
|
row++;
|
|
}
|
|
}
|
|
// Empty out the renderer pane, allowing renderers to be gc'ed.
|
|
rendererPane.removeAll();
|
|
}
|
|
|
|
private void paintDropLine(Graphics g) {
|
|
JList.DropLocation loc = list.getDropLocation();
|
|
if (loc == null || !loc.isInsert()) {
|
|
return;
|
|
}
|
|
|
|
Color c = DefaultLookup.getColor(list, this, "List.dropLineColor", null);
|
|
if (c != null) {
|
|
g.setColor(c);
|
|
Rectangle rect = getDropLineRect(loc);
|
|
g.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
}
|
|
}
|
|
|
|
private Rectangle getDropLineRect(JList.DropLocation loc) {
|
|
int size = list.getModel().getSize();
|
|
|
|
if (size == 0) {
|
|
Insets insets = list.getInsets();
|
|
if (layoutOrientation == JList.HORIZONTAL_WRAP) {
|
|
if (isLeftToRight) {
|
|
return new Rectangle(insets.left, insets.top, DROP_LINE_THICKNESS, 20);
|
|
} else {
|
|
return new Rectangle(list.getWidth() - DROP_LINE_THICKNESS - insets.right,
|
|
insets.top, DROP_LINE_THICKNESS, 20);
|
|
}
|
|
} else {
|
|
return new Rectangle(insets.left, insets.top,
|
|
list.getWidth() - insets.left - insets.right,
|
|
DROP_LINE_THICKNESS);
|
|
}
|
|
}
|
|
|
|
Rectangle rect = null;
|
|
int index = loc.getIndex();
|
|
boolean decr = false;
|
|
|
|
if (layoutOrientation == JList.HORIZONTAL_WRAP) {
|
|
if (index == size) {
|
|
decr = true;
|
|
} else if (index != 0 && convertModelToRow(index)
|
|
!= convertModelToRow(index - 1)) {
|
|
|
|
Rectangle prev = getCellBounds(list, index - 1);
|
|
Rectangle me = getCellBounds(list, index);
|
|
Point p = loc.getDropPoint();
|
|
|
|
if (isLeftToRight) {
|
|
decr = Point2D.distance(prev.x + prev.width,
|
|
prev.y + (int)(prev.height / 2.0),
|
|
p.x, p.y)
|
|
< Point2D.distance(me.x,
|
|
me.y + (int)(me.height / 2.0),
|
|
p.x, p.y);
|
|
} else {
|
|
decr = Point2D.distance(prev.x,
|
|
prev.y + (int)(prev.height / 2.0),
|
|
p.x, p.y)
|
|
< Point2D.distance(me.x + me.width,
|
|
me.y + (int)(prev.height / 2.0),
|
|
p.x, p.y);
|
|
}
|
|
}
|
|
|
|
if (decr) {
|
|
index--;
|
|
rect = getCellBounds(list, index);
|
|
if (isLeftToRight) {
|
|
rect.x += rect.width;
|
|
} else {
|
|
rect.x -= DROP_LINE_THICKNESS;
|
|
}
|
|
} else {
|
|
rect = getCellBounds(list, index);
|
|
if (!isLeftToRight) {
|
|
rect.x += rect.width - DROP_LINE_THICKNESS;
|
|
}
|
|
}
|
|
|
|
if (rect.x >= list.getWidth()) {
|
|
rect.x = list.getWidth() - DROP_LINE_THICKNESS;
|
|
} else if (rect.x < 0) {
|
|
rect.x = 0;
|
|
}
|
|
|
|
rect.width = DROP_LINE_THICKNESS;
|
|
} else if (layoutOrientation == JList.VERTICAL_WRAP) {
|
|
if (index == size) {
|
|
index--;
|
|
rect = getCellBounds(list, index);
|
|
rect.y += rect.height;
|
|
} else if (index != 0 && convertModelToColumn(index)
|
|
!= convertModelToColumn(index - 1)) {
|
|
|
|
Rectangle prev = getCellBounds(list, index - 1);
|
|
Rectangle me = getCellBounds(list, index);
|
|
Point p = loc.getDropPoint();
|
|
if (Point2D.distance(prev.x + (int)(prev.width / 2.0),
|
|
prev.y + prev.height,
|
|
p.x, p.y)
|
|
< Point2D.distance(me.x + (int)(me.width / 2.0),
|
|
me.y,
|
|
p.x, p.y)) {
|
|
|
|
index--;
|
|
rect = getCellBounds(list, index);
|
|
rect.y += rect.height;
|
|
} else {
|
|
rect = getCellBounds(list, index);
|
|
}
|
|
} else {
|
|
rect = getCellBounds(list, index);
|
|
}
|
|
|
|
if (rect.y >= list.getHeight()) {
|
|
rect.y = list.getHeight() - DROP_LINE_THICKNESS;
|
|
}
|
|
|
|
rect.height = DROP_LINE_THICKNESS;
|
|
} else {
|
|
if (index == size) {
|
|
index--;
|
|
rect = getCellBounds(list, index);
|
|
rect.y += rect.height;
|
|
} else {
|
|
rect = getCellBounds(list, index);
|
|
}
|
|
|
|
if (rect.y >= list.getHeight()) {
|
|
rect.y = list.getHeight() - DROP_LINE_THICKNESS;
|
|
}
|
|
|
|
rect.height = DROP_LINE_THICKNESS;
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
/**
|
|
* Returns the baseline.
|
|
*
|
|
* @throws NullPointerException {@inheritDoc}
|
|
* @throws IllegalArgumentException {@inheritDoc}
|
|
* @see javax.swing.JComponent#getBaseline(int, int)
|
|
* @since 1.6
|
|
*/
|
|
public int getBaseline(JComponent c, int width, int height) {
|
|
super.getBaseline(c, width, height);
|
|
int rowHeight = list.getFixedCellHeight();
|
|
UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
|
|
Component renderer = (Component)lafDefaults.get(
|
|
BASELINE_COMPONENT_KEY);
|
|
if (renderer == null) {
|
|
ListCellRenderer lcr = (ListCellRenderer)UIManager.get(
|
|
"List.cellRenderer");
|
|
|
|
// fix for 6711072 some LAFs like Nimbus do not provide this
|
|
// UIManager key and we should not through a NPE here because of it
|
|
if (lcr == null) {
|
|
lcr = new DefaultListCellRenderer();
|
|
}
|
|
renderer = lcr.getListCellRendererComponent(
|
|
list, "a", -1, false, false);
|
|
lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
|
|
}
|
|
renderer.setFont(list.getFont());
|
|
// JList actually has much more complex behavior here.
|
|
// If rowHeight != -1 the rowHeight is either the max of all cell
|
|
// heights (layout orientation != VERTICAL), or is variable depending
|
|
// upon the cell. We assume a default size.
|
|
// We could theoretically query the real renderer, but that would
|
|
// not work for an empty model and the results may vary with
|
|
// the content.
|
|
if (rowHeight == -1) {
|
|
rowHeight = renderer.getPreferredSize().height;
|
|
}
|
|
return renderer.getBaseline(Integer.MAX_VALUE, rowHeight) +
|
|
list.getInsets().top;
|
|
}
|
|
|
|
/**
|
|
* Returns an enum indicating how the baseline of the component
|
|
* changes as the size changes.
|
|
*
|
|
* @throws NullPointerException {@inheritDoc}
|
|
* @see javax.swing.JComponent#getBaseline(int, int)
|
|
* @since 1.6
|
|
*/
|
|
public Component.BaselineResizeBehavior getBaselineResizeBehavior(
|
|
JComponent c) {
|
|
super.getBaselineResizeBehavior(c);
|
|
return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
|
|
}
|
|
|
|
/**
|
|
* The preferredSize of the list depends upon the layout orientation.
|
|
* <table summary="Describes the preferred size for each layout orientation">
|
|
* <tr><th>Layout Orientation</th><th>Preferred Size</th></tr>
|
|
* <tr>
|
|
* <td>JList.VERTICAL
|
|
* <td>The preferredSize of the list is total height of the rows
|
|
* and the maximum width of the cells. If JList.fixedCellHeight
|
|
* is specified then the total height of the rows is just
|
|
* (cellVerticalMargins + fixedCellHeight) * model.getSize() where
|
|
* rowVerticalMargins is the space we allocate for drawing
|
|
* the yellow focus outline. Similarly if fixedCellWidth is
|
|
* specified then we just use that.
|
|
* </td>
|
|
* <tr>
|
|
* <td>JList.VERTICAL_WRAP
|
|
* <td>If the visible row count is greater than zero, the preferredHeight
|
|
* is the maximum cell height * visibleRowCount. If the visible row
|
|
* count is <= 0, the preferred height is either the current height
|
|
* of the list, or the maximum cell height, whichever is
|
|
* bigger. The preferred width is than the maximum cell width *
|
|
* number of columns needed. Where the number of columns needs is
|
|
* list.height / max cell height. Max cell height is either the fixed
|
|
* cell height, or is determined by iterating through all the cells
|
|
* to find the maximum height from the ListCellRenderer.
|
|
* <tr>
|
|
* <td>JList.HORIZONTAL_WRAP
|
|
* <td>If the visible row count is greater than zero, the preferredHeight
|
|
* is the maximum cell height * adjustedRowCount. Where
|
|
* visibleRowCount is used to determine the number of columns.
|
|
* Because this lays out horizontally the number of rows is
|
|
* then determined from the column count. For example, lets say
|
|
* you have a model with 10 items and the visible row count is 8.
|
|
* The number of columns needed to display this is 2, but you no
|
|
* longer need 8 rows to display this, you only need 5, thus
|
|
* the adjustedRowCount is 5.
|
|
* <p>If the visible row
|
|
* count is <= 0, the preferred height is dictated by the
|
|
* number of columns, which will be as many as can fit in the width
|
|
* of the <code>JList</code> (width / max cell width), with at
|
|
* least one column. The preferred height then becomes the
|
|
* model size / number of columns * maximum cell height.
|
|
* Max cell height is either the fixed
|
|
* cell height, or is determined by iterating through all the cells
|
|
* to find the maximum height from the ListCellRenderer.
|
|
* </table>
|
|
* The above specifies the raw preferred width and height. The resulting
|
|
* preferred width is the above width + insets.left + insets.right and
|
|
* the resulting preferred height is the above height + insets.top +
|
|
* insets.bottom. Where the <code>Insets</code> are determined from
|
|
* <code>list.getInsets()</code>.
|
|
*
|
|
* @param c The JList component.
|
|
* @return The total size of the list.
|
|
*/
|
|
public Dimension getPreferredSize(JComponent c) {
|
|
maybeUpdateLayoutState();
|
|
|
|
int lastRow = list.getModel().getSize() - 1;
|
|
if (lastRow < 0) {
|
|
return new Dimension(0, 0);
|
|
}
|
|
|
|
Insets insets = list.getInsets();
|
|
int width = cellWidth * columnCount + insets.left + insets.right;
|
|
int height;
|
|
|
|
if (layoutOrientation != JList.VERTICAL) {
|
|
height = preferredHeight;
|
|
}
|
|
else {
|
|
Rectangle bounds = getCellBounds(list, lastRow);
|
|
|
|
if (bounds != null) {
|
|
height = bounds.y + bounds.height + insets.bottom;
|
|
}
|
|
else {
|
|
height = 0;
|
|
}
|
|
}
|
|
return new Dimension(width, height);
|
|
}
|
|
|
|
|
|
/**
|
|
* Selected the previous row and force it to be visible.
|
|
*
|
|
* @see JList#ensureIndexIsVisible
|
|
*/
|
|
protected void selectPreviousIndex() {
|
|
int s = list.getSelectedIndex();
|
|
if(s > 0) {
|
|
s -= 1;
|
|
list.setSelectedIndex(s);
|
|
list.ensureIndexIsVisible(s);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Selected the previous row and force it to be visible.
|
|
*
|
|
* @see JList#ensureIndexIsVisible
|
|
*/
|
|
protected void selectNextIndex()
|
|
{
|
|
int s = list.getSelectedIndex();
|
|
if((s + 1) < list.getModel().getSize()) {
|
|
s += 1;
|
|
list.setSelectedIndex(s);
|
|
list.ensureIndexIsVisible(s);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Registers the keyboard bindings on the <code>JList</code> that the
|
|
* <code>BasicListUI</code> is associated with. This method is called at
|
|
* installUI() time.
|
|
*
|
|
* @see #installUI
|
|
*/
|
|
protected void installKeyboardActions() {
|
|
InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
|
|
|
|
SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
|
|
inputMap);
|
|
|
|
LazyActionMap.installLazyActionMap(list, BasicListUI.class,
|
|
"List.actionMap");
|
|
}
|
|
|
|
InputMap getInputMap(int condition) {
|
|
if (condition == JComponent.WHEN_FOCUSED) {
|
|
InputMap keyMap = (InputMap)DefaultLookup.get(
|
|
list, this, "List.focusInputMap");
|
|
InputMap rtlKeyMap;
|
|
|
|
if (isLeftToRight ||
|
|
((rtlKeyMap = (InputMap)DefaultLookup.get(list, this,
|
|
"List.focusInputMap.RightToLeft")) == null)) {
|
|
return keyMap;
|
|
} else {
|
|
rtlKeyMap.setParent(keyMap);
|
|
return rtlKeyMap;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Unregisters keyboard actions installed from
|
|
* <code>installKeyboardActions</code>.
|
|
* This method is called at uninstallUI() time - subclassess should
|
|
* ensure that all of the keyboard actions registered at installUI
|
|
* time are removed here.
|
|
*
|
|
* @see #installUI
|
|
*/
|
|
protected void uninstallKeyboardActions() {
|
|
SwingUtilities.replaceUIActionMap(list, null);
|
|
SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null);
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates and installs the listeners for the JList, its model, and its
|
|
* selectionModel. This method is called at installUI() time.
|
|
*
|
|
* @see #installUI
|
|
* @see #uninstallListeners
|
|
*/
|
|
protected void installListeners()
|
|
{
|
|
TransferHandler th = list.getTransferHandler();
|
|
if (th == null || th instanceof UIResource) {
|
|
list.setTransferHandler(defaultTransferHandler);
|
|
// default TransferHandler doesn't support drop
|
|
// so we don't want drop handling
|
|
if (list.getDropTarget() instanceof UIResource) {
|
|
list.setDropTarget(null);
|
|
}
|
|
}
|
|
|
|
focusListener = createFocusListener();
|
|
mouseInputListener = createMouseInputListener();
|
|
propertyChangeListener = createPropertyChangeListener();
|
|
listSelectionListener = createListSelectionListener();
|
|
listDataListener = createListDataListener();
|
|
|
|
list.addFocusListener(focusListener);
|
|
list.addMouseListener(mouseInputListener);
|
|
list.addMouseMotionListener(mouseInputListener);
|
|
list.addPropertyChangeListener(propertyChangeListener);
|
|
list.addKeyListener(getHandler());
|
|
|
|
ListModel model = list.getModel();
|
|
if (model != null) {
|
|
model.addListDataListener(listDataListener);
|
|
}
|
|
|
|
ListSelectionModel selectionModel = list.getSelectionModel();
|
|
if (selectionModel != null) {
|
|
selectionModel.addListSelectionListener(listSelectionListener);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes the listeners from the JList, its model, and its
|
|
* selectionModel. All of the listener fields, are reset to
|
|
* null here. This method is called at uninstallUI() time,
|
|
* it should be kept in sync with installListeners.
|
|
*
|
|
* @see #uninstallUI
|
|
* @see #installListeners
|
|
*/
|
|
protected void uninstallListeners()
|
|
{
|
|
list.removeFocusListener(focusListener);
|
|
list.removeMouseListener(mouseInputListener);
|
|
list.removeMouseMotionListener(mouseInputListener);
|
|
list.removePropertyChangeListener(propertyChangeListener);
|
|
list.removeKeyListener(getHandler());
|
|
|
|
ListModel model = list.getModel();
|
|
if (model != null) {
|
|
model.removeListDataListener(listDataListener);
|
|
}
|
|
|
|
ListSelectionModel selectionModel = list.getSelectionModel();
|
|
if (selectionModel != null) {
|
|
selectionModel.removeListSelectionListener(listSelectionListener);
|
|
}
|
|
|
|
focusListener = null;
|
|
mouseInputListener = null;
|
|
listSelectionListener = null;
|
|
listDataListener = null;
|
|
propertyChangeListener = null;
|
|
handler = null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initializes list properties such as font, foreground, and background,
|
|
* and adds the CellRendererPane. The font, foreground, and background
|
|
* properties are only set if their current value is either null
|
|
* or a UIResource, other properties are set if the current
|
|
* value is null.
|
|
*
|
|
* @see #uninstallDefaults
|
|
* @see #installUI
|
|
* @see CellRendererPane
|
|
*/
|
|
protected void installDefaults()
|
|
{
|
|
list.setLayout(null);
|
|
|
|
LookAndFeel.installBorder(list, "List.border");
|
|
|
|
LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
|
|
|
|
LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
|
|
|
|
if (list.getCellRenderer() == null) {
|
|
list.setCellRenderer((ListCellRenderer)(UIManager.get("List.cellRenderer")));
|
|
}
|
|
|
|
Color sbg = list.getSelectionBackground();
|
|
if (sbg == null || sbg instanceof UIResource) {
|
|
list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
|
|
}
|
|
|
|
Color sfg = list.getSelectionForeground();
|
|
if (sfg == null || sfg instanceof UIResource) {
|
|
list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
|
|
}
|
|
|
|
Long l = (Long)UIManager.get("List.timeFactor");
|
|
timeFactor = (l!=null) ? l.longValue() : 1000L;
|
|
|
|
updateIsFileList();
|
|
}
|
|
|
|
private void updateIsFileList() {
|
|
boolean b = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
|
|
if (b != isFileList) {
|
|
isFileList = b;
|
|
Font oldFont = list.getFont();
|
|
if (oldFont == null || oldFont instanceof UIResource) {
|
|
Font newFont = UIManager.getFont(b ? "FileChooser.listFont" : "List.font");
|
|
if (newFont != null && newFont != oldFont) {
|
|
list.setFont(newFont);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the list properties that have not been explicitly overridden to
|
|
* {@code null}. A property is considered overridden if its current value
|
|
* is not a {@code UIResource}.
|
|
*
|
|
* @see #installDefaults
|
|
* @see #uninstallUI
|
|
* @see CellRendererPane
|
|
*/
|
|
protected void uninstallDefaults()
|
|
{
|
|
LookAndFeel.uninstallBorder(list);
|
|
if (list.getFont() instanceof UIResource) {
|
|
list.setFont(null);
|
|
}
|
|
if (list.getForeground() instanceof UIResource) {
|
|
list.setForeground(null);
|
|
}
|
|
if (list.getBackground() instanceof UIResource) {
|
|
list.setBackground(null);
|
|
}
|
|
if (list.getSelectionBackground() instanceof UIResource) {
|
|
list.setSelectionBackground(null);
|
|
}
|
|
if (list.getSelectionForeground() instanceof UIResource) {
|
|
list.setSelectionForeground(null);
|
|
}
|
|
if (list.getCellRenderer() instanceof UIResource) {
|
|
list.setCellRenderer(null);
|
|
}
|
|
if (list.getTransferHandler() instanceof UIResource) {
|
|
list.setTransferHandler(null);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
|
|
* <code>installListeners()</code>, and <code>installKeyboardActions()</code>
|
|
* in order.
|
|
*
|
|
* @see #installDefaults
|
|
* @see #installListeners
|
|
* @see #installKeyboardActions
|
|
*/
|
|
public void installUI(JComponent c)
|
|
{
|
|
list = (JList)c;
|
|
|
|
layoutOrientation = list.getLayoutOrientation();
|
|
|
|
rendererPane = new CellRendererPane();
|
|
list.add(rendererPane);
|
|
|
|
columnCount = 1;
|
|
|
|
updateLayoutStateNeeded = modelChanged;
|
|
isLeftToRight = list.getComponentOrientation().isLeftToRight();
|
|
|
|
installDefaults();
|
|
installListeners();
|
|
installKeyboardActions();
|
|
}
|
|
|
|
|
|
/**
|
|
* Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
|
|
* <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
|
|
* in order. Sets this.list to null.
|
|
*
|
|
* @see #uninstallListeners
|
|
* @see #uninstallKeyboardActions
|
|
* @see #uninstallDefaults
|
|
*/
|
|
public void uninstallUI(JComponent c)
|
|
{
|
|
uninstallListeners();
|
|
uninstallDefaults();
|
|
uninstallKeyboardActions();
|
|
|
|
cellWidth = cellHeight = -1;
|
|
cellHeights = null;
|
|
|
|
listWidth = listHeight = -1;
|
|
|
|
list.remove(rendererPane);
|
|
rendererPane = null;
|
|
list = null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a new instance of BasicListUI. BasicListUI delegates are
|
|
* allocated one per JList.
|
|
*
|
|
* @return A new ListUI implementation for the Windows look and feel.
|
|
*/
|
|
public static ComponentUI createUI(JComponent list) {
|
|
return new BasicListUI();
|
|
}
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
* @throws NullPointerException {@inheritDoc}
|
|
*/
|
|
public int locationToIndex(JList list, Point location) {
|
|
maybeUpdateLayoutState();
|
|
return convertLocationToModel(location.x, location.y);
|
|
}
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public Point indexToLocation(JList list, int index) {
|
|
maybeUpdateLayoutState();
|
|
Rectangle rect = getCellBounds(list, index, index);
|
|
|
|
if (rect != null) {
|
|
return new Point(rect.x, rect.y);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public Rectangle getCellBounds(JList list, int index1, int index2) {
|
|
maybeUpdateLayoutState();
|
|
|
|
int minIndex = Math.min(index1, index2);
|
|
int maxIndex = Math.max(index1, index2);
|
|
|
|
if (minIndex >= list.getModel().getSize()) {
|
|
return null;
|
|
}
|
|
|
|
Rectangle minBounds = getCellBounds(list, minIndex);
|
|
|
|
if (minBounds == null) {
|
|
return null;
|
|
}
|
|
if (minIndex == maxIndex) {
|
|
return minBounds;
|
|
}
|
|
Rectangle maxBounds = getCellBounds(list, maxIndex);
|
|
|
|
if (maxBounds != null) {
|
|
if (layoutOrientation == JList.HORIZONTAL_WRAP) {
|
|
int minRow = convertModelToRow(minIndex);
|
|
int maxRow = convertModelToRow(maxIndex);
|
|
|
|
if (minRow != maxRow) {
|
|
minBounds.x = 0;
|
|
minBounds.width = list.getWidth();
|
|
}
|
|
}
|
|
else if (minBounds.x != maxBounds.x) {
|
|
// Different columns
|
|
minBounds.y = 0;
|
|
minBounds.height = list.getHeight();
|
|
}
|
|
minBounds.add(maxBounds);
|
|
}
|
|
return minBounds;
|
|
}
|
|
|
|
/**
|
|
* Gets the bounds of the specified model index, returning the resulting
|
|
* bounds, or null if <code>index</code> is not valid.
|
|
*/
|
|
private Rectangle getCellBounds(JList list, int index) {
|
|
maybeUpdateLayoutState();
|
|
|
|
int row = convertModelToRow(index);
|
|
int column = convertModelToColumn(index);
|
|
|
|
if (row == -1 || column == -1) {
|
|
return null;
|
|
}
|
|
|
|
Insets insets = list.getInsets();
|
|
int x;
|
|
int w = cellWidth;
|
|
int y = insets.top;
|
|
int h;
|
|
switch (layoutOrientation) {
|
|
case JList.VERTICAL_WRAP:
|
|
case JList.HORIZONTAL_WRAP:
|
|
if (isLeftToRight) {
|
|
x = insets.left + column * cellWidth;
|
|
} else {
|
|
x = list.getWidth() - insets.right - (column+1) * cellWidth;
|
|
}
|
|
y += cellHeight * row;
|
|
h = cellHeight;
|
|
break;
|
|
default:
|
|
x = insets.left;
|
|
if (cellHeights == null) {
|
|
y += (cellHeight * row);
|
|
}
|
|
else if (row >= cellHeights.length) {
|
|
y = 0;
|
|
}
|
|
else {
|
|
for(int i = 0; i < row; i++) {
|
|
y += cellHeights[i];
|
|
}
|
|
}
|
|
w = list.getWidth() - (insets.left + insets.right);
|
|
h = getRowHeight(index);
|
|
break;
|
|
}
|
|
return new Rectangle(x, y, w, h);
|
|
}
|
|
|
|
/**
|
|
* Returns the height of the specified row based on the current layout.
|
|
*
|
|
* @return The specified row height or -1 if row isn't valid.
|
|
* @see #convertYToRow
|
|
* @see #convertRowToY
|
|
* @see #updateLayoutState
|
|
*/
|
|
protected int getRowHeight(int row)
|
|
{
|
|
return getHeight(0, row);
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert the JList relative coordinate to the row that contains it,
|
|
* based on the current layout. If y0 doesn't fall within any row,
|
|
* return -1.
|
|
*
|
|
* @return The row that contains y0, or -1.
|
|
* @see #getRowHeight
|
|
* @see #updateLayoutState
|
|
*/
|
|
protected int convertYToRow(int y0)
|
|
{
|
|
return convertLocationToRow(0, y0, false);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the JList relative Y coordinate of the origin of the specified
|
|
* row or -1 if row isn't valid.
|
|
*
|
|
* @return The Y coordinate of the origin of row, or -1.
|
|
* @see #getRowHeight
|
|
* @see #updateLayoutState
|
|
*/
|
|
protected int convertRowToY(int row)
|
|
{
|
|
if (row >= getRowCount(0) || row < 0) {
|
|
return -1;
|
|
}
|
|
Rectangle bounds = getCellBounds(list, row, row);
|
|
return bounds.y;
|
|
}
|
|
|
|
/**
|
|
* Returns the height of the cell at the passed in location.
|
|
*/
|
|
private int getHeight(int column, int row) {
|
|
if (column < 0 || column > columnCount || row < 0) {
|
|
return -1;
|
|
}
|
|
if (layoutOrientation != JList.VERTICAL) {
|
|
return cellHeight;
|
|
}
|
|
if (row >= list.getModel().getSize()) {
|
|
return -1;
|
|
}
|
|
return (cellHeights == null) ? cellHeight :
|
|
((row < cellHeights.length) ? cellHeights[row] : -1);
|
|
}
|
|
|
|
/**
|
|
* Returns the row at location x/y.
|
|
*
|
|
* @param closest If true and the location doesn't exactly match a
|
|
* particular location, this will return the closest row.
|
|
*/
|
|
private int convertLocationToRow(int x, int y0, boolean closest) {
|
|
int size = list.getModel().getSize();
|
|
|
|
if (size <= 0) {
|
|
return -1;
|
|
}
|
|
Insets insets = list.getInsets();
|
|
if (cellHeights == null) {
|
|
int row = (cellHeight == 0) ? 0 :
|
|
((y0 - insets.top) / cellHeight);
|
|
if (closest) {
|
|
if (row < 0) {
|
|
row = 0;
|
|
}
|
|
else if (row >= size) {
|
|
row = size - 1;
|
|
}
|
|
}
|
|
return row;
|
|
}
|
|
else if (size > cellHeights.length) {
|
|
return -1;
|
|
}
|
|
else {
|
|
int y = insets.top;
|
|
int row = 0;
|
|
|
|
if (closest && y0 < y) {
|
|
return 0;
|
|
}
|
|
int i;
|
|
for (i = 0; i < size; i++) {
|
|
if ((y0 >= y) && (y0 < y + cellHeights[i])) {
|
|
return row;
|
|
}
|
|
y += cellHeights[i];
|
|
row += 1;
|
|
}
|
|
return i - 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the closest row that starts at the specified y-location
|
|
* in the passed in column.
|
|
*/
|
|
private int convertLocationToRowInColumn(int y, int column) {
|
|
int x = 0;
|
|
|
|
if (layoutOrientation != JList.VERTICAL) {
|
|
if (isLeftToRight) {
|
|
x = column * cellWidth;
|
|
} else {
|
|
x = list.getWidth() - (column+1)*cellWidth - list.getInsets().right;
|
|
}
|
|
}
|
|
return convertLocationToRow(x, y, true);
|
|
}
|
|
|
|
/**
|
|
* Returns the closest location to the model index of the passed in
|
|
* location.
|
|
*/
|
|
private int convertLocationToModel(int x, int y) {
|
|
int row = convertLocationToRow(x, y, true);
|
|
int column = convertLocationToColumn(x, y);
|
|
|
|
if (row >= 0 && column >= 0) {
|
|
return getModelIndex(column, row);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of rows in the given column.
|
|
*/
|
|
private int getRowCount(int column) {
|
|
if (column < 0 || column >= columnCount) {
|
|
return -1;
|
|
}
|
|
if (layoutOrientation == JList.VERTICAL ||
|
|
(column == 0 && columnCount == 1)) {
|
|
return list.getModel().getSize();
|
|
}
|
|
if (column >= columnCount) {
|
|
return -1;
|
|
}
|
|
if (layoutOrientation == JList.VERTICAL_WRAP) {
|
|
if (column < (columnCount - 1)) {
|
|
return rowsPerColumn;
|
|
}
|
|
return list.getModel().getSize() - (columnCount - 1) *
|
|
rowsPerColumn;
|
|
}
|
|
// JList.HORIZONTAL_WRAP
|
|
int diff = columnCount - (columnCount * rowsPerColumn -
|
|
list.getModel().getSize());
|
|
|
|
if (column >= diff) {
|
|
return Math.max(0, rowsPerColumn - 1);
|
|
}
|
|
return rowsPerColumn;
|
|
}
|
|
|
|
/**
|
|
* Returns the model index for the specified display location.
|
|
* If <code>column</code>x<code>row</code> is beyond the length of the
|
|
* model, this will return the model size - 1.
|
|
*/
|
|
private int getModelIndex(int column, int row) {
|
|
switch (layoutOrientation) {
|
|
case JList.VERTICAL_WRAP:
|
|
return Math.min(list.getModel().getSize() - 1, rowsPerColumn *
|
|
column + Math.min(row, rowsPerColumn-1));
|
|
case JList.HORIZONTAL_WRAP:
|
|
return Math.min(list.getModel().getSize() - 1, row * columnCount +
|
|
column);
|
|
default:
|
|
return row;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the closest column to the passed in location.
|
|
*/
|
|
private int convertLocationToColumn(int x, int y) {
|
|
if (cellWidth > 0) {
|
|
if (layoutOrientation == JList.VERTICAL) {
|
|
return 0;
|
|
}
|
|
Insets insets = list.getInsets();
|
|
int col;
|
|
if (isLeftToRight) {
|
|
col = (x - insets.left) / cellWidth;
|
|
} else {
|
|
col = (list.getWidth() - x - insets.right - 1) / cellWidth;
|
|
}
|
|
if (col < 0) {
|
|
return 0;
|
|
}
|
|
else if (col >= columnCount) {
|
|
return columnCount - 1;
|
|
}
|
|
return col;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Returns the row that the model index <code>index</code> will be
|
|
* displayed in..
|
|
*/
|
|
private int convertModelToRow(int index) {
|
|
int size = list.getModel().getSize();
|
|
|
|
if ((index < 0) || (index >= size)) {
|
|
return -1;
|
|
}
|
|
|
|
if (layoutOrientation != JList.VERTICAL && columnCount > 1 &&
|
|
rowsPerColumn > 0) {
|
|
if (layoutOrientation == JList.VERTICAL_WRAP) {
|
|
return index % rowsPerColumn;
|
|
}
|
|
return index / columnCount;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
/**
|
|
* Returns the column that the model index <code>index</code> will be
|
|
* displayed in.
|
|
*/
|
|
private int convertModelToColumn(int index) {
|
|
int size = list.getModel().getSize();
|
|
|
|
if ((index < 0) || (index >= size)) {
|
|
return -1;
|
|
}
|
|
|
|
if (layoutOrientation != JList.VERTICAL && rowsPerColumn > 0 &&
|
|
columnCount > 1) {
|
|
if (layoutOrientation == JList.VERTICAL_WRAP) {
|
|
return index / rowsPerColumn;
|
|
}
|
|
return index % columnCount;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* If updateLayoutStateNeeded is non zero, call updateLayoutState() and reset
|
|
* updateLayoutStateNeeded. This method should be called by methods
|
|
* before doing any computation based on the geometry of the list.
|
|
* For example it's the first call in paint() and getPreferredSize().
|
|
*
|
|
* @see #updateLayoutState
|
|
*/
|
|
protected void maybeUpdateLayoutState()
|
|
{
|
|
if (updateLayoutStateNeeded != 0) {
|
|
updateLayoutState();
|
|
updateLayoutStateNeeded = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Recompute the value of cellHeight or cellHeights based
|
|
* and cellWidth, based on the current font and the current
|
|
* values of fixedCellWidth, fixedCellHeight, and prototypeCellValue.
|
|
*
|
|
* @see #maybeUpdateLayoutState
|
|
*/
|
|
protected void updateLayoutState()
|
|
{
|
|
/* If both JList fixedCellWidth and fixedCellHeight have been
|
|
* set, then initialize cellWidth and cellHeight, and set
|
|
* cellHeights to null.
|
|
*/
|
|
|
|
int fixedCellHeight = list.getFixedCellHeight();
|
|
int fixedCellWidth = list.getFixedCellWidth();
|
|
|
|
cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
|
|
|
|
if (fixedCellHeight != -1) {
|
|
cellHeight = fixedCellHeight;
|
|
cellHeights = null;
|
|
}
|
|
else {
|
|
cellHeight = -1;
|
|
cellHeights = new int[list.getModel().getSize()];
|
|
}
|
|
|
|
/* If either of JList fixedCellWidth and fixedCellHeight haven't
|
|
* been set, then initialize cellWidth and cellHeights by
|
|
* scanning through the entire model. Note: if the renderer is
|
|
* null, we just set cellWidth and cellHeights[*] to zero,
|
|
* if they're not set already.
|
|
*/
|
|
|
|
if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
|
|
|
|
ListModel dataModel = list.getModel();
|
|
int dataModelSize = dataModel.getSize();
|
|
ListCellRenderer renderer = list.getCellRenderer();
|
|
|
|
if (renderer != null) {
|
|
for(int index = 0; index < dataModelSize; index++) {
|
|
Object value = dataModel.getElementAt(index);
|
|
Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
|
|
rendererPane.add(c);
|
|
Dimension cellSize = c.getPreferredSize();
|
|
if (fixedCellWidth == -1) {
|
|
cellWidth = Math.max(cellSize.width, cellWidth);
|
|
}
|
|
if (fixedCellHeight == -1) {
|
|
cellHeights[index] = cellSize.height;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (cellWidth == -1) {
|
|
cellWidth = 0;
|
|
}
|
|
if (cellHeights == null) {
|
|
cellHeights = new int[dataModelSize];
|
|
}
|
|
for(int index = 0; index < dataModelSize; index++) {
|
|
cellHeights[index] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
columnCount = 1;
|
|
if (layoutOrientation != JList.VERTICAL) {
|
|
updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invoked when the list is layed out horizontally to determine how
|
|
* many columns to create.
|
|
* <p>
|
|
* This updates the <code>rowsPerColumn, </code><code>columnCount</code>,
|
|
* <code>preferredHeight</code> and potentially <code>cellHeight</code>
|
|
* instance variables.
|
|
*/
|
|
private void updateHorizontalLayoutState(int fixedCellWidth,
|
|
int fixedCellHeight) {
|
|
int visRows = list.getVisibleRowCount();
|
|
int dataModelSize = list.getModel().getSize();
|
|
Insets insets = list.getInsets();
|
|
|
|
listHeight = list.getHeight();
|
|
listWidth = list.getWidth();
|
|
|
|
if (dataModelSize == 0) {
|
|
rowsPerColumn = columnCount = 0;
|
|
preferredHeight = insets.top + insets.bottom;
|
|
return;
|
|
}
|
|
|
|
int height;
|
|
|
|
if (fixedCellHeight != -1) {
|
|
height = fixedCellHeight;
|
|
}
|
|
else {
|
|
// Determine the max of the renderer heights.
|
|
int maxHeight = 0;
|
|
if (cellHeights.length > 0) {
|
|
maxHeight = cellHeights[cellHeights.length - 1];
|
|
for (int counter = cellHeights.length - 2;
|
|
counter >= 0; counter--) {
|
|
maxHeight = Math.max(maxHeight, cellHeights[counter]);
|
|
}
|
|
}
|
|
height = cellHeight = maxHeight;
|
|
cellHeights = null;
|
|
}
|
|
// The number of rows is either determined by the visible row
|
|
// count, or by the height of the list.
|
|
rowsPerColumn = dataModelSize;
|
|
if (visRows > 0) {
|
|
rowsPerColumn = visRows;
|
|
columnCount = Math.max(1, dataModelSize / rowsPerColumn);
|
|
if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
|
|
dataModelSize % rowsPerColumn != 0) {
|
|
columnCount++;
|
|
}
|
|
if (layoutOrientation == JList.HORIZONTAL_WRAP) {
|
|
// Because HORIZONTAL_WRAP flows differently, the
|
|
// rowsPerColumn needs to be adjusted.
|
|
rowsPerColumn = (dataModelSize / columnCount);
|
|
if (dataModelSize % columnCount > 0) {
|
|
rowsPerColumn++;
|
|
}
|
|
}
|
|
}
|
|
else if (layoutOrientation == JList.VERTICAL_WRAP && height != 0) {
|
|
rowsPerColumn = Math.max(1, (listHeight - insets.top -
|
|
insets.bottom) / height);
|
|
columnCount = Math.max(1, dataModelSize / rowsPerColumn);
|
|
if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
|
|
dataModelSize % rowsPerColumn != 0) {
|
|
columnCount++;
|
|
}
|
|
}
|
|
else if (layoutOrientation == JList.HORIZONTAL_WRAP && cellWidth > 0 &&
|
|
listWidth > 0) {
|
|
columnCount = Math.max(1, (listWidth - insets.left -
|
|
insets.right) / cellWidth);
|
|
rowsPerColumn = dataModelSize / columnCount;
|
|
if (dataModelSize % columnCount > 0) {
|
|
rowsPerColumn++;
|
|
}
|
|
}
|
|
preferredHeight = rowsPerColumn * cellHeight + insets.top +
|
|
insets.bottom;
|
|
}
|
|
|
|
private Handler getHandler() {
|
|
if (handler == null) {
|
|
handler = new Handler();
|
|
}
|
|
return handler;
|
|
}
|
|
|
|
/**
|
|
* Mouse input, and focus handling for JList. An instance of this
|
|
* class is added to the appropriate java.awt.Component lists
|
|
* at installUI() time. Note keyboard input is handled with JComponent
|
|
* KeyboardActions, see installKeyboardActions().
|
|
* <p>
|
|
* <strong>Warning:</strong>
|
|
* Serialized objects of this class will not be compatible with
|
|
* future Swing releases. The current serialization support is
|
|
* appropriate for short term storage or RMI between applications running
|
|
* the same version of Swing. As of 1.4, support for long term storage
|
|
* of all JavaBeans™
|
|
* has been added to the <code>java.beans</code> package.
|
|
* Please see {@link java.beans.XMLEncoder}.
|
|
*
|
|
* @see #createMouseInputListener
|
|
* @see #installKeyboardActions
|
|
* @see #installUI
|
|
*/
|
|
public class MouseInputHandler implements MouseInputListener
|
|
{
|
|
public void mouseClicked(MouseEvent e) {
|
|
getHandler().mouseClicked(e);
|
|
}
|
|
|
|
public void mouseEntered(MouseEvent e) {
|
|
getHandler().mouseEntered(e);
|
|
}
|
|
|
|
public void mouseExited(MouseEvent e) {
|
|
getHandler().mouseExited(e);
|
|
}
|
|
|
|
public void mousePressed(MouseEvent e) {
|
|
getHandler().mousePressed(e);
|
|
}
|
|
|
|
public void mouseDragged(MouseEvent e) {
|
|
getHandler().mouseDragged(e);
|
|
}
|
|
|
|
public void mouseMoved(MouseEvent e) {
|
|
getHandler().mouseMoved(e);
|
|
}
|
|
|
|
public void mouseReleased(MouseEvent e) {
|
|
getHandler().mouseReleased(e);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates a delegate that implements MouseInputListener.
|
|
* The delegate is added to the corresponding java.awt.Component listener
|
|
* lists at installUI() time. Subclasses can override this method to return
|
|
* a custom MouseInputListener, e.g.
|
|
* <pre>
|
|
* class MyListUI extends BasicListUI {
|
|
* protected MouseInputListener <b>createMouseInputListener</b>() {
|
|
* return new MyMouseInputHandler();
|
|
* }
|
|
* public class MyMouseInputHandler extends MouseInputHandler {
|
|
* public void mouseMoved(MouseEvent e) {
|
|
* // do some extra work when the mouse moves
|
|
* super.mouseMoved(e);
|
|
* }
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @see MouseInputHandler
|
|
* @see #installUI
|
|
*/
|
|
protected MouseInputListener createMouseInputListener() {
|
|
return getHandler();
|
|
}
|
|
|
|
/**
|
|
* This class should be treated as a "protected" inner class.
|
|
* Instantiate it only within subclasses of {@code BasicListUI}.
|
|
*/
|
|
public class FocusHandler implements FocusListener
|
|
{
|
|
protected void repaintCellFocus()
|
|
{
|
|
getHandler().repaintCellFocus();
|
|
}
|
|
|
|
/* The focusGained() focusLost() methods run when the JList
|
|
* focus changes.
|
|
*/
|
|
|
|
public void focusGained(FocusEvent e) {
|
|
getHandler().focusGained(e);
|
|
}
|
|
|
|
public void focusLost(FocusEvent e) {
|
|
getHandler().focusLost(e);
|
|
}
|
|
}
|
|
|
|
protected FocusListener createFocusListener() {
|
|
return getHandler();
|
|
}
|
|
|
|
/**
|
|
* The ListSelectionListener that's added to the JLists selection
|
|
* model at installUI time, and whenever the JList.selectionModel property
|
|
* changes. When the selection changes we repaint the affected rows.
|
|
* <p>
|
|
* <strong>Warning:</strong>
|
|
* Serialized objects of this class will not be compatible with
|
|
* future Swing releases. The current serialization support is
|
|
* appropriate for short term storage or RMI between applications running
|
|
* the same version of Swing. As of 1.4, support for long term storage
|
|
* of all JavaBeans™
|
|
* has been added to the <code>java.beans</code> package.
|
|
* Please see {@link java.beans.XMLEncoder}.
|
|
*
|
|
* @see #createListSelectionListener
|
|
* @see #getCellBounds
|
|
* @see #installUI
|
|
*/
|
|
public class ListSelectionHandler implements ListSelectionListener
|
|
{
|
|
public void valueChanged(ListSelectionEvent e)
|
|
{
|
|
getHandler().valueChanged(e);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates an instance of ListSelectionHandler that's added to
|
|
* the JLists by selectionModel as needed. Subclasses can override
|
|
* this method to return a custom ListSelectionListener, e.g.
|
|
* <pre>
|
|
* class MyListUI extends BasicListUI {
|
|
* protected ListSelectionListener <b>createListSelectionListener</b>() {
|
|
* return new MySelectionListener();
|
|
* }
|
|
* public class MySelectionListener extends ListSelectionHandler {
|
|
* public void valueChanged(ListSelectionEvent e) {
|
|
* // do some extra work when the selection changes
|
|
* super.valueChange(e);
|
|
* }
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @see ListSelectionHandler
|
|
* @see #installUI
|
|
*/
|
|
protected ListSelectionListener createListSelectionListener() {
|
|
return getHandler();
|
|
}
|
|
|
|
|
|
private void redrawList() {
|
|
list.revalidate();
|
|
list.repaint();
|
|
}
|
|
|
|
|
|
/**
|
|
* The ListDataListener that's added to the JLists model at
|
|
* installUI time, and whenever the JList.model property changes.
|
|
* <p>
|
|
* <strong>Warning:</strong>
|
|
* Serialized objects of this class will not be compatible with
|
|
* future Swing releases. The current serialization support is
|
|
* appropriate for short term storage or RMI between applications running
|
|
* the same version of Swing. As of 1.4, support for long term storage
|
|
* of all JavaBeans™
|
|
* has been added to the <code>java.beans</code> package.
|
|
* Please see {@link java.beans.XMLEncoder}.
|
|
*
|
|
* @see JList#getModel
|
|
* @see #maybeUpdateLayoutState
|
|
* @see #createListDataListener
|
|
* @see #installUI
|
|
*/
|
|
public class ListDataHandler implements ListDataListener
|
|
{
|
|
public void intervalAdded(ListDataEvent e) {
|
|
getHandler().intervalAdded(e);
|
|
}
|
|
|
|
|
|
public void intervalRemoved(ListDataEvent e)
|
|
{
|
|
getHandler().intervalRemoved(e);
|
|
}
|
|
|
|
|
|
public void contentsChanged(ListDataEvent e) {
|
|
getHandler().contentsChanged(e);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates an instance of ListDataListener that's added to
|
|
* the JLists by model as needed. Subclasses can override
|
|
* this method to return a custom ListDataListener, e.g.
|
|
* <pre>
|
|
* class MyListUI extends BasicListUI {
|
|
* protected ListDataListener <b>createListDataListener</b>() {
|
|
* return new MyListDataListener();
|
|
* }
|
|
* public class MyListDataListener extends ListDataHandler {
|
|
* public void contentsChanged(ListDataEvent e) {
|
|
* // do some extra work when the models contents change
|
|
* super.contentsChange(e);
|
|
* }
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @see ListDataListener
|
|
* @see JList#getModel
|
|
* @see #installUI
|
|
*/
|
|
protected ListDataListener createListDataListener() {
|
|
return getHandler();
|
|
}
|
|
|
|
|
|
/**
|
|
* The PropertyChangeListener that's added to the JList at
|
|
* installUI time. When the value of a JList property that
|
|
* affects layout changes, we set a bit in updateLayoutStateNeeded.
|
|
* If the JLists model changes we additionally remove our listeners
|
|
* from the old model. Likewise for the JList selectionModel.
|
|
* <p>
|
|
* <strong>Warning:</strong>
|
|
* Serialized objects of this class will not be compatible with
|
|
* future Swing releases. The current serialization support is
|
|
* appropriate for short term storage or RMI between applications running
|
|
* the same version of Swing. As of 1.4, support for long term storage
|
|
* of all JavaBeans™
|
|
* has been added to the <code>java.beans</code> package.
|
|
* Please see {@link java.beans.XMLEncoder}.
|
|
*
|
|
* @see #maybeUpdateLayoutState
|
|
* @see #createPropertyChangeListener
|
|
* @see #installUI
|
|
*/
|
|
public class PropertyChangeHandler implements PropertyChangeListener
|
|
{
|
|
public void propertyChange(PropertyChangeEvent e)
|
|
{
|
|
getHandler().propertyChange(e);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates an instance of PropertyChangeHandler that's added to
|
|
* the JList by installUI(). Subclasses can override this method
|
|
* to return a custom PropertyChangeListener, e.g.
|
|
* <pre>
|
|
* class MyListUI extends BasicListUI {
|
|
* protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
|
|
* return new MyPropertyChangeListener();
|
|
* }
|
|
* public class MyPropertyChangeListener extends PropertyChangeHandler {
|
|
* public void propertyChange(PropertyChangeEvent e) {
|
|
* if (e.getPropertyName().equals("model")) {
|
|
* // do some extra work when the model changes
|
|
* }
|
|
* super.propertyChange(e);
|
|
* }
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @see PropertyChangeListener
|
|
* @see #installUI
|
|
*/
|
|
protected PropertyChangeListener createPropertyChangeListener() {
|
|
return getHandler();
|
|
}
|
|
|
|
/** Used by IncrementLeadSelectionAction. Indicates the action should
|
|
* change the lead, and not select it. */
|
|
private static final int CHANGE_LEAD = 0;
|
|
/** Used by IncrementLeadSelectionAction. Indicates the action should
|
|
* change the selection and lead. */
|
|
private static final int CHANGE_SELECTION = 1;
|
|
/** Used by IncrementLeadSelectionAction. Indicates the action should
|
|
* extend the selection from the anchor to the next index. */
|
|
private static final int EXTEND_SELECTION = 2;
|
|
|
|
|
|
private static class Actions extends UIAction {
|
|
private static final String SELECT_PREVIOUS_COLUMN =
|
|
"selectPreviousColumn";
|
|
private static final String SELECT_PREVIOUS_COLUMN_EXTEND =
|
|
"selectPreviousColumnExtendSelection";
|
|
private static final String SELECT_PREVIOUS_COLUMN_CHANGE_LEAD =
|
|
"selectPreviousColumnChangeLead";
|
|
private static final String SELECT_NEXT_COLUMN = "selectNextColumn";
|
|
private static final String SELECT_NEXT_COLUMN_EXTEND =
|
|
"selectNextColumnExtendSelection";
|
|
private static final String SELECT_NEXT_COLUMN_CHANGE_LEAD =
|
|
"selectNextColumnChangeLead";
|
|
private static final String SELECT_PREVIOUS_ROW = "selectPreviousRow";
|
|
private static final String SELECT_PREVIOUS_ROW_EXTEND =
|
|
"selectPreviousRowExtendSelection";
|
|
private static final String SELECT_PREVIOUS_ROW_CHANGE_LEAD =
|
|
"selectPreviousRowChangeLead";
|
|
private static final String SELECT_NEXT_ROW = "selectNextRow";
|
|
private static final String SELECT_NEXT_ROW_EXTEND =
|
|
"selectNextRowExtendSelection";
|
|
private static final String SELECT_NEXT_ROW_CHANGE_LEAD =
|
|
"selectNextRowChangeLead";
|
|
private static final String SELECT_FIRST_ROW = "selectFirstRow";
|
|
private static final String SELECT_FIRST_ROW_EXTEND =
|
|
"selectFirstRowExtendSelection";
|
|
private static final String SELECT_FIRST_ROW_CHANGE_LEAD =
|
|
"selectFirstRowChangeLead";
|
|
private static final String SELECT_LAST_ROW = "selectLastRow";
|
|
private static final String SELECT_LAST_ROW_EXTEND =
|
|
"selectLastRowExtendSelection";
|
|
private static final String SELECT_LAST_ROW_CHANGE_LEAD =
|
|
"selectLastRowChangeLead";
|
|
private static final String SCROLL_UP = "scrollUp";
|
|
private static final String SCROLL_UP_EXTEND =
|
|
"scrollUpExtendSelection";
|
|
private static final String SCROLL_UP_CHANGE_LEAD =
|
|
"scrollUpChangeLead";
|
|
private static final String SCROLL_DOWN = "scrollDown";
|
|
private static final String SCROLL_DOWN_EXTEND =
|
|
"scrollDownExtendSelection";
|
|
private static final String SCROLL_DOWN_CHANGE_LEAD =
|
|
"scrollDownChangeLead";
|
|
private static final String SELECT_ALL = "selectAll";
|
|
private static final String CLEAR_SELECTION = "clearSelection";
|
|
|
|
// add the lead item to the selection without changing lead or anchor
|
|
private static final String ADD_TO_SELECTION = "addToSelection";
|
|
|
|
// toggle the selected state of the lead item and move the anchor to it
|
|
private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
|
|
|
|
// extend the selection to the lead item
|
|
private static final String EXTEND_TO = "extendTo";
|
|
|
|
// move the anchor to the lead and ensure only that item is selected
|
|
private static final String MOVE_SELECTION_TO = "moveSelectionTo";
|
|
|
|
Actions(String name) {
|
|
super(name);
|
|
}
|
|
public void actionPerformed(ActionEvent e) {
|
|
String name = getName();
|
|
JList list = (JList)e.getSource();
|
|
BasicListUI ui = (BasicListUI)BasicLookAndFeel.getUIOfType(
|
|
list.getUI(), BasicListUI.class);
|
|
|
|
if (name == SELECT_PREVIOUS_COLUMN) {
|
|
changeSelection(list, CHANGE_SELECTION,
|
|
getNextColumnIndex(list, ui, -1), -1);
|
|
}
|
|
else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
|
|
changeSelection(list, EXTEND_SELECTION,
|
|
getNextColumnIndex(list, ui, -1), -1);
|
|
}
|
|
else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
|
|
changeSelection(list, CHANGE_LEAD,
|
|
getNextColumnIndex(list, ui, -1), -1);
|
|
}
|
|
else if (name == SELECT_NEXT_COLUMN) {
|
|
changeSelection(list, CHANGE_SELECTION,
|
|
getNextColumnIndex(list, ui, 1), 1);
|
|
}
|
|
else if (name == SELECT_NEXT_COLUMN_EXTEND) {
|
|
changeSelection(list, EXTEND_SELECTION,
|
|
getNextColumnIndex(list, ui, 1), 1);
|
|
}
|
|
else if (name == SELECT_NEXT_COLUMN_CHANGE_LEAD) {
|
|
changeSelection(list, CHANGE_LEAD,
|
|
getNextColumnIndex(list, ui, 1), 1);
|
|
}
|
|
else if (name == SELECT_PREVIOUS_ROW) {
|
|
changeSelection(list, CHANGE_SELECTION,
|
|
getNextIndex(list, ui, -1), -1);
|
|
}
|
|
else if (name == SELECT_PREVIOUS_ROW_EXTEND) {
|
|
changeSelection(list, EXTEND_SELECTION,
|
|
getNextIndex(list, ui, -1), -1);
|
|
}
|
|
else if (name == SELECT_PREVIOUS_ROW_CHANGE_LEAD) {
|
|
changeSelection(list, CHANGE_LEAD,
|
|
getNextIndex(list, ui, -1), -1);
|
|
}
|
|
else if (name == SELECT_NEXT_ROW) {
|
|
changeSelection(list, CHANGE_SELECTION,
|
|
getNextIndex(list, ui, 1), 1);
|
|
}
|
|
else if (name == SELECT_NEXT_ROW_EXTEND) {
|
|
changeSelection(list, EXTEND_SELECTION,
|
|
getNextIndex(list, ui, 1), 1);
|
|
}
|
|
else if (name == SELECT_NEXT_ROW_CHANGE_LEAD) {
|
|
changeSelection(list, CHANGE_LEAD,
|
|
getNextIndex(list, ui, 1), 1);
|
|
}
|
|
else if (name == SELECT_FIRST_ROW) {
|
|
changeSelection(list, CHANGE_SELECTION, 0, -1);
|
|
}
|
|
else if (name == SELECT_FIRST_ROW_EXTEND) {
|
|
changeSelection(list, EXTEND_SELECTION, 0, -1);
|
|
}
|
|
else if (name == SELECT_FIRST_ROW_CHANGE_LEAD) {
|
|
changeSelection(list, CHANGE_LEAD, 0, -1);
|
|
}
|
|
else if (name == SELECT_LAST_ROW) {
|
|
changeSelection(list, CHANGE_SELECTION,
|
|
list.getModel().getSize() - 1, 1);
|
|
}
|
|
else if (name == SELECT_LAST_ROW_EXTEND) {
|
|
changeSelection(list, EXTEND_SELECTION,
|
|
list.getModel().getSize() - 1, 1);
|
|
}
|
|
else if (name == SELECT_LAST_ROW_CHANGE_LEAD) {
|
|
changeSelection(list, CHANGE_LEAD,
|
|
list.getModel().getSize() - 1, 1);
|
|
}
|
|
else if (name == SCROLL_UP) {
|
|
changeSelection(list, CHANGE_SELECTION,
|
|
getNextPageIndex(list, -1), -1);
|
|
}
|
|
else if (name == SCROLL_UP_EXTEND) {
|
|
changeSelection(list, EXTEND_SELECTION,
|
|
getNextPageIndex(list, -1), -1);
|
|
}
|
|
else if (name == SCROLL_UP_CHANGE_LEAD) {
|
|
changeSelection(list, CHANGE_LEAD,
|
|
getNextPageIndex(list, -1), -1);
|
|
}
|
|
else if (name == SCROLL_DOWN) {
|
|
changeSelection(list, CHANGE_SELECTION,
|
|
getNextPageIndex(list, 1), 1);
|
|
}
|
|
else if (name == SCROLL_DOWN_EXTEND) {
|
|
changeSelection(list, EXTEND_SELECTION,
|
|
getNextPageIndex(list, 1), 1);
|
|
}
|
|
else if (name == SCROLL_DOWN_CHANGE_LEAD) {
|
|
changeSelection(list, CHANGE_LEAD,
|
|
getNextPageIndex(list, 1), 1);
|
|
}
|
|
else if (name == SELECT_ALL) {
|
|
selectAll(list);
|
|
}
|
|
else if (name == CLEAR_SELECTION) {
|
|
clearSelection(list);
|
|
}
|
|
else if (name == ADD_TO_SELECTION) {
|
|
int index = adjustIndex(
|
|
list.getSelectionModel().getLeadSelectionIndex(), list);
|
|
|
|
if (!list.isSelectedIndex(index)) {
|
|
int oldAnchor = list.getSelectionModel().getAnchorSelectionIndex();
|
|
list.setValueIsAdjusting(true);
|
|
list.addSelectionInterval(index, index);
|
|
list.getSelectionModel().setAnchorSelectionIndex(oldAnchor);
|
|
list.setValueIsAdjusting(false);
|
|
}
|
|
}
|
|
else if (name == TOGGLE_AND_ANCHOR) {
|
|
int index = adjustIndex(
|
|
list.getSelectionModel().getLeadSelectionIndex(), list);
|
|
|
|
if (list.isSelectedIndex(index)) {
|
|
list.removeSelectionInterval(index, index);
|
|
} else {
|
|
list.addSelectionInterval(index, index);
|
|
}
|
|
}
|
|
else if (name == EXTEND_TO) {
|
|
changeSelection(
|
|
list, EXTEND_SELECTION,
|
|
adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list),
|
|
0);
|
|
}
|
|
else if (name == MOVE_SELECTION_TO) {
|
|
changeSelection(
|
|
list, CHANGE_SELECTION,
|
|
adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list),
|
|
0);
|
|
}
|
|
}
|
|
|
|
public boolean isEnabled(Object c) {
|
|
Object name = getName();
|
|
if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD ||
|
|
name == SELECT_NEXT_COLUMN_CHANGE_LEAD ||
|
|
name == SELECT_PREVIOUS_ROW_CHANGE_LEAD ||
|
|
name == SELECT_NEXT_ROW_CHANGE_LEAD ||
|
|
name == SELECT_FIRST_ROW_CHANGE_LEAD ||
|
|
name == SELECT_LAST_ROW_CHANGE_LEAD ||
|
|
name == SCROLL_UP_CHANGE_LEAD ||
|
|
name == SCROLL_DOWN_CHANGE_LEAD) {
|
|
|
|
// discontinuous selection actions are only enabled for
|
|
// DefaultListSelectionModel
|
|
return c != null && ((JList)c).getSelectionModel()
|
|
instanceof DefaultListSelectionModel;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void clearSelection(JList list) {
|
|
list.clearSelection();
|
|
}
|
|
|
|
private void selectAll(JList list) {
|
|
int size = list.getModel().getSize();
|
|
if (size > 0) {
|
|
ListSelectionModel lsm = list.getSelectionModel();
|
|
int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
|
|
|
|
if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
|
|
if (lead == -1) {
|
|
int min = adjustIndex(list.getMinSelectionIndex(), list);
|
|
lead = (min == -1 ? 0 : min);
|
|
}
|
|
|
|
list.setSelectionInterval(lead, lead);
|
|
list.ensureIndexIsVisible(lead);
|
|
} else {
|
|
list.setValueIsAdjusting(true);
|
|
|
|
int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
|
|
|
|
list.setSelectionInterval(0, size - 1);
|
|
|
|
// this is done to restore the anchor and lead
|
|
SwingUtilities2.setLeadAnchorWithoutSelection(lsm, anchor, lead);
|
|
|
|
list.setValueIsAdjusting(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private int getNextPageIndex(JList list, int direction) {
|
|
if (list.getModel().getSize() == 0) {
|
|
return -1;
|
|
}
|
|
|
|
int index = -1;
|
|
Rectangle visRect = list.getVisibleRect();
|
|
ListSelectionModel lsm = list.getSelectionModel();
|
|
int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
|
|
Rectangle leadRect =
|
|
(lead==-1) ? new Rectangle() : list.getCellBounds(lead, lead);
|
|
|
|
if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
|
|
list.getVisibleRowCount() <= 0) {
|
|
if (!list.getComponentOrientation().isLeftToRight()) {
|
|
direction = -direction;
|
|
}
|
|
// apply for horizontal scrolling: the step for next
|
|
// page index is number of visible columns
|
|
if (direction < 0) {
|
|
// left
|
|
visRect.x = leadRect.x + leadRect.width - visRect.width;
|
|
Point p = new Point(visRect.x - 1, leadRect.y);
|
|
index = list.locationToIndex(p);
|
|
Rectangle cellBounds = list.getCellBounds(index, index);
|
|
if (visRect.intersects(cellBounds)) {
|
|
p.x = cellBounds.x - 1;
|
|
index = list.locationToIndex(p);
|
|
cellBounds = list.getCellBounds(index, index);
|
|
}
|
|
// this is necessary for right-to-left orientation only
|
|
if (cellBounds.y != leadRect.y) {
|
|
p.x = cellBounds.x + cellBounds.width;
|
|
index = list.locationToIndex(p);
|
|
}
|
|
}
|
|
else {
|
|
// right
|
|
visRect.x = leadRect.x;
|
|
Point p = new Point(visRect.x + visRect.width, leadRect.y);
|
|
index = list.locationToIndex(p);
|
|
Rectangle cellBounds = list.getCellBounds(index, index);
|
|
if (visRect.intersects(cellBounds)) {
|
|
p.x = cellBounds.x + cellBounds.width;
|
|
index = list.locationToIndex(p);
|
|
cellBounds = list.getCellBounds(index, index);
|
|
}
|
|
if (cellBounds.y != leadRect.y) {
|
|
p.x = cellBounds.x - 1;
|
|
index = list.locationToIndex(p);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (direction < 0) {
|
|
// up
|
|
// go to the first visible cell
|
|
Point p = new Point(leadRect.x, visRect.y);
|
|
index = list.locationToIndex(p);
|
|
if (lead <= index) {
|
|
// if lead is the first visible cell (or above it)
|
|
// adjust the visible rect up
|
|
visRect.y = leadRect.y + leadRect.height - visRect.height;
|
|
p.y = visRect.y;
|
|
index = list.locationToIndex(p);
|
|
Rectangle cellBounds = list.getCellBounds(index, index);
|
|
// go one cell down if first visible cell doesn't fit
|
|
// into adjasted visible rectangle
|
|
if (cellBounds.y < visRect.y) {
|
|
p.y = cellBounds.y + cellBounds.height;
|
|
index = list.locationToIndex(p);
|
|
cellBounds = list.getCellBounds(index, index);
|
|
}
|
|
// if index isn't less then lead
|
|
// try to go to cell previous to lead
|
|
if (cellBounds.y >= leadRect.y) {
|
|
p.y = leadRect.y - 1;
|
|
index = list.locationToIndex(p);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// down
|
|
// go to the last completely visible cell
|
|
Point p = new Point(leadRect.x,
|
|
visRect.y + visRect.height - 1);
|
|
index = list.locationToIndex(p);
|
|
Rectangle cellBounds = list.getCellBounds(index, index);
|
|
// go up one cell if last visible cell doesn't fit
|
|
// into visible rectangle
|
|
if (cellBounds.y + cellBounds.height >
|
|
visRect.y + visRect.height) {
|
|
p.y = cellBounds.y - 1;
|
|
index = list.locationToIndex(p);
|
|
cellBounds = list.getCellBounds(index, index);
|
|
index = Math.max(index, lead);
|
|
}
|
|
|
|
if (lead >= index) {
|
|
// if lead is the last completely visible index
|
|
// (or below it) adjust the visible rect down
|
|
visRect.y = leadRect.y;
|
|
p.y = visRect.y + visRect.height - 1;
|
|
index = list.locationToIndex(p);
|
|
cellBounds = list.getCellBounds(index, index);
|
|
// go one cell up if last visible cell doesn't fit
|
|
// into adjasted visible rectangle
|
|
if (cellBounds.y + cellBounds.height >
|
|
visRect.y + visRect.height) {
|
|
p.y = cellBounds.y - 1;
|
|
index = list.locationToIndex(p);
|
|
cellBounds = list.getCellBounds(index, index);
|
|
}
|
|
// if index isn't greater then lead
|
|
// try to go to cell next after lead
|
|
if (cellBounds.y <= leadRect.y) {
|
|
p.y = leadRect.y + leadRect.height;
|
|
index = list.locationToIndex(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
private void changeSelection(JList list, int type,
|
|
int index, int direction) {
|
|
if (index >= 0 && index < list.getModel().getSize()) {
|
|
ListSelectionModel lsm = list.getSelectionModel();
|
|
|
|
// CHANGE_LEAD is only valid with multiple interval selection
|
|
if (type == CHANGE_LEAD &&
|
|
list.getSelectionMode()
|
|
!= ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
|
|
|
|
type = CHANGE_SELECTION;
|
|
}
|
|
|
|
// IMPORTANT - This needs to happen before the index is changed.
|
|
// This is because JFileChooser, which uses JList, also scrolls
|
|
// the selected item into view. If that happens first, then
|
|
// this method becomes a no-op.
|
|
adjustScrollPositionIfNecessary(list, index, direction);
|
|
|
|
if (type == EXTEND_SELECTION) {
|
|
int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
|
|
if (anchor == -1) {
|
|
anchor = 0;
|
|
}
|
|
|
|
list.setSelectionInterval(anchor, index);
|
|
}
|
|
else if (type == CHANGE_SELECTION) {
|
|
list.setSelectedIndex(index);
|
|
}
|
|
else {
|
|
// casting should be safe since the action is only enabled
|
|
// for DefaultListSelectionModel
|
|
((DefaultListSelectionModel)lsm).moveLeadSelectionIndex(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When scroll down makes selected index the last completely visible
|
|
* index. When scroll up makes selected index the first visible index.
|
|
* Adjust visible rectangle respect to list's component orientation.
|
|
*/
|
|
private void adjustScrollPositionIfNecessary(JList list, int index,
|
|
int direction) {
|
|
if (direction == 0) {
|
|
return;
|
|
}
|
|
Rectangle cellBounds = list.getCellBounds(index, index);
|
|
Rectangle visRect = list.getVisibleRect();
|
|
if (cellBounds != null && !visRect.contains(cellBounds)) {
|
|
if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
|
|
list.getVisibleRowCount() <= 0) {
|
|
// horizontal
|
|
if (list.getComponentOrientation().isLeftToRight()) {
|
|
if (direction > 0) {
|
|
// right for left-to-right
|
|
int x =Math.max(0,
|
|
cellBounds.x + cellBounds.width - visRect.width);
|
|
int startIndex =
|
|
list.locationToIndex(new Point(x, cellBounds.y));
|
|
Rectangle startRect = list.getCellBounds(startIndex,
|
|
startIndex);
|
|
if (startRect.x < x && startRect.x < cellBounds.x) {
|
|
startRect.x += startRect.width;
|
|
startIndex =
|
|
list.locationToIndex(startRect.getLocation());
|
|
startRect = list.getCellBounds(startIndex,
|
|
startIndex);
|
|
}
|
|
cellBounds = startRect;
|
|
}
|
|
cellBounds.width = visRect.width;
|
|
}
|
|
else {
|
|
if (direction > 0) {
|
|
// left for right-to-left
|
|
int x = cellBounds.x + visRect.width;
|
|
int rightIndex =
|
|
list.locationToIndex(new Point(x, cellBounds.y));
|
|
Rectangle rightRect = list.getCellBounds(rightIndex,
|
|
rightIndex);
|
|
if (rightRect.x + rightRect.width > x &&
|
|
rightRect.x > cellBounds.x) {
|
|
rightRect.width = 0;
|
|
}
|
|
cellBounds.x = Math.max(0,
|
|
rightRect.x + rightRect.width - visRect.width);
|
|
cellBounds.width = visRect.width;
|
|
}
|
|
else {
|
|
cellBounds.x += Math.max(0,
|
|
cellBounds.width - visRect.width);
|
|
// adjust width to fit into visible rectangle
|
|
cellBounds.width = Math.min(cellBounds.width,
|
|
visRect.width);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// vertical
|
|
if (direction > 0 &&
|
|
(cellBounds.y < visRect.y ||
|
|
cellBounds.y + cellBounds.height
|
|
> visRect.y + visRect.height)) {
|
|
//down
|
|
int y = Math.max(0,
|
|
cellBounds.y + cellBounds.height - visRect.height);
|
|
int startIndex =
|
|
list.locationToIndex(new Point(cellBounds.x, y));
|
|
Rectangle startRect = list.getCellBounds(startIndex,
|
|
startIndex);
|
|
if (startRect.y < y && startRect.y < cellBounds.y) {
|
|
startRect.y += startRect.height;
|
|
startIndex =
|
|
list.locationToIndex(startRect.getLocation());
|
|
startRect =
|
|
list.getCellBounds(startIndex, startIndex);
|
|
}
|
|
cellBounds = startRect;
|
|
cellBounds.height = visRect.height;
|
|
}
|
|
else {
|
|
// adjust height to fit into visible rectangle
|
|
cellBounds.height = Math.min(cellBounds.height, visRect.height);
|
|
}
|
|
}
|
|
list.scrollRectToVisible(cellBounds);
|
|
}
|
|
}
|
|
|
|
private int getNextColumnIndex(JList list, BasicListUI ui,
|
|
int amount) {
|
|
if (list.getLayoutOrientation() != JList.VERTICAL) {
|
|
int index = adjustIndex(list.getLeadSelectionIndex(), list);
|
|
int size = list.getModel().getSize();
|
|
|
|
if (index == -1) {
|
|
return 0;
|
|
} else if (size == 1) {
|
|
// there's only one item so we should select it
|
|
return 0;
|
|
} else if (ui == null || ui.columnCount <= 1) {
|
|
return -1;
|
|
}
|
|
|
|
int column = ui.convertModelToColumn(index);
|
|
int row = ui.convertModelToRow(index);
|
|
|
|
column += amount;
|
|
if (column >= ui.columnCount || column < 0) {
|
|
// No wrapping.
|
|
return -1;
|
|
}
|
|
int maxRowCount = ui.getRowCount(column);
|
|
if (row >= maxRowCount) {
|
|
return -1;
|
|
}
|
|
return ui.getModelIndex(column, row);
|
|
}
|
|
// Won't change the selection.
|
|
return -1;
|
|
}
|
|
|
|
private int getNextIndex(JList list, BasicListUI ui, int amount) {
|
|
int index = adjustIndex(list.getLeadSelectionIndex(), list);
|
|
int size = list.getModel().getSize();
|
|
|
|
if (index == -1) {
|
|
if (size > 0) {
|
|
if (amount > 0) {
|
|
index = 0;
|
|
}
|
|
else {
|
|
index = size - 1;
|
|
}
|
|
}
|
|
} else if (size == 1) {
|
|
// there's only one item so we should select it
|
|
index = 0;
|
|
} else if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
|
|
if (ui != null) {
|
|
index += ui.columnCount * amount;
|
|
}
|
|
} else {
|
|
index += amount;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
}
|
|
|
|
|
|
private class Handler implements FocusListener, KeyListener,
|
|
ListDataListener, ListSelectionListener,
|
|
MouseInputListener, PropertyChangeListener,
|
|
BeforeDrag {
|
|
//
|
|
// KeyListener
|
|
//
|
|
private String prefix = "";
|
|
private String typedString = "";
|
|
private long lastTime = 0L;
|
|
|
|
/**
|
|
* Invoked when a key has been typed.
|
|
*
|
|
* Moves the keyboard focus to the first element whose prefix matches the
|
|
* sequence of alphanumeric keys pressed by the user with delay less
|
|
* than value of <code>timeFactor</code> property (or 1000 milliseconds
|
|
* if it is not defined). Subsequent same key presses move the keyboard
|
|
* focus to the next object that starts with the same letter until another
|
|
* key is pressed, then it is treated as the prefix with appropriate number
|
|
* of the same letters followed by first typed another letter.
|
|
*/
|
|
public void keyTyped(KeyEvent e) {
|
|
JList src = (JList)e.getSource();
|
|
ListModel model = src.getModel();
|
|
|
|
if (model.getSize() == 0 || e.isAltDown() ||
|
|
BasicGraphicsUtils.isMenuShortcutKeyDown(e) ||
|
|
isNavigationKey(e)) {
|
|
// Nothing to select
|
|
return;
|
|
}
|
|
boolean startingFromSelection = true;
|
|
|
|
char c = e.getKeyChar();
|
|
|
|
long time = e.getWhen();
|
|
int startIndex = adjustIndex(src.getLeadSelectionIndex(), list);
|
|
if (time - lastTime < timeFactor) {
|
|
typedString += c;
|
|
if((prefix.length() == 1) && (c == prefix.charAt(0))) {
|
|
// Subsequent same key presses move the keyboard focus to the next
|
|
// object that starts with the same letter.
|
|
startIndex++;
|
|
} else {
|
|
prefix = typedString;
|
|
}
|
|
} else {
|
|
startIndex++;
|
|
typedString = "" + c;
|
|
prefix = typedString;
|
|
}
|
|
lastTime = time;
|
|
|
|
if (startIndex < 0 || startIndex >= model.getSize()) {
|
|
startingFromSelection = false;
|
|
startIndex = 0;
|
|
}
|
|
int index = src.getNextMatch(prefix, startIndex,
|
|
Position.Bias.Forward);
|
|
if (index >= 0) {
|
|
src.setSelectedIndex(index);
|
|
src.ensureIndexIsVisible(index);
|
|
} else if (startingFromSelection) { // wrap
|
|
index = src.getNextMatch(prefix, 0,
|
|
Position.Bias.Forward);
|
|
if (index >= 0) {
|
|
src.setSelectedIndex(index);
|
|
src.ensureIndexIsVisible(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invoked when a key has been pressed.
|
|
*
|
|
* Checks to see if the key event is a navigation key to prevent
|
|
* dispatching these keys for the first letter navigation.
|
|
*/
|
|
public void keyPressed(KeyEvent e) {
|
|
if ( isNavigationKey(e) ) {
|
|
prefix = "";
|
|
typedString = "";
|
|
lastTime = 0L;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invoked when a key has been released.
|
|
* See the class description for {@link KeyEvent} for a definition of
|
|
* a key released event.
|
|
*/
|
|
public void keyReleased(KeyEvent e) {
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not the supplied key event maps to a key that is used for
|
|
* navigation. This is used for optimizing key input by only passing non-
|
|
* navigation keys to the first letter navigation mechanism.
|
|
*/
|
|
private boolean isNavigationKey(KeyEvent event) {
|
|
InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
|
KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
|
|
|
|
if (inputMap != null && inputMap.get(key) != null) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// PropertyChangeListener
|
|
//
|
|
public void propertyChange(PropertyChangeEvent e) {
|
|
String propertyName = e.getPropertyName();
|
|
|
|
/* If the JList.model property changes, remove our listener,
|
|
* listDataListener from the old model and add it to the new one.
|
|
*/
|
|
if (propertyName == "model") {
|
|
ListModel oldModel = (ListModel)e.getOldValue();
|
|
ListModel newModel = (ListModel)e.getNewValue();
|
|
if (oldModel != null) {
|
|
oldModel.removeListDataListener(listDataListener);
|
|
}
|
|
if (newModel != null) {
|
|
newModel.addListDataListener(listDataListener);
|
|
}
|
|
updateLayoutStateNeeded |= modelChanged;
|
|
redrawList();
|
|
}
|
|
|
|
/* If the JList.selectionModel property changes, remove our listener,
|
|
* listSelectionListener from the old selectionModel and add it to the new one.
|
|
*/
|
|
else if (propertyName == "selectionModel") {
|
|
ListSelectionModel oldModel = (ListSelectionModel)e.getOldValue();
|
|
ListSelectionModel newModel = (ListSelectionModel)e.getNewValue();
|
|
if (oldModel != null) {
|
|
oldModel.removeListSelectionListener(listSelectionListener);
|
|
}
|
|
if (newModel != null) {
|
|
newModel.addListSelectionListener(listSelectionListener);
|
|
}
|
|
updateLayoutStateNeeded |= modelChanged;
|
|
redrawList();
|
|
}
|
|
else if (propertyName == "cellRenderer") {
|
|
updateLayoutStateNeeded |= cellRendererChanged;
|
|
redrawList();
|
|
}
|
|
else if (propertyName == "font") {
|
|
updateLayoutStateNeeded |= fontChanged;
|
|
redrawList();
|
|
}
|
|
else if (propertyName == "prototypeCellValue") {
|
|
updateLayoutStateNeeded |= prototypeCellValueChanged;
|
|
redrawList();
|
|
}
|
|
else if (propertyName == "fixedCellHeight") {
|
|
updateLayoutStateNeeded |= fixedCellHeightChanged;
|
|
redrawList();
|
|
}
|
|
else if (propertyName == "fixedCellWidth") {
|
|
updateLayoutStateNeeded |= fixedCellWidthChanged;
|
|
redrawList();
|
|
}
|
|
else if (propertyName == "selectionForeground") {
|
|
list.repaint();
|
|
}
|
|
else if (propertyName == "selectionBackground") {
|
|
list.repaint();
|
|
}
|
|
else if ("layoutOrientation" == propertyName) {
|
|
updateLayoutStateNeeded |= layoutOrientationChanged;
|
|
layoutOrientation = list.getLayoutOrientation();
|
|
redrawList();
|
|
}
|
|
else if ("visibleRowCount" == propertyName) {
|
|
if (layoutOrientation != JList.VERTICAL) {
|
|
updateLayoutStateNeeded |= layoutOrientationChanged;
|
|
redrawList();
|
|
}
|
|
}
|
|
else if ("componentOrientation" == propertyName) {
|
|
isLeftToRight = list.getComponentOrientation().isLeftToRight();
|
|
updateLayoutStateNeeded |= componentOrientationChanged;
|
|
redrawList();
|
|
|
|
InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
|
|
SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
|
|
inputMap);
|
|
} else if ("List.isFileList" == propertyName) {
|
|
updateIsFileList();
|
|
redrawList();
|
|
} else if ("dropLocation" == propertyName) {
|
|
JList.DropLocation oldValue = (JList.DropLocation)e.getOldValue();
|
|
repaintDropLocation(oldValue);
|
|
repaintDropLocation(list.getDropLocation());
|
|
}
|
|
}
|
|
|
|
private void repaintDropLocation(JList.DropLocation loc) {
|
|
if (loc == null) {
|
|
return;
|
|
}
|
|
|
|
Rectangle r;
|
|
|
|
if (loc.isInsert()) {
|
|
r = getDropLineRect(loc);
|
|
} else {
|
|
r = getCellBounds(list, loc.getIndex());
|
|
}
|
|
|
|
if (r != null) {
|
|
list.repaint(r);
|
|
}
|
|
}
|
|
|
|
//
|
|
// ListDataListener
|
|
//
|
|
public void intervalAdded(ListDataEvent e) {
|
|
updateLayoutStateNeeded = modelChanged;
|
|
|
|
int minIndex = Math.min(e.getIndex0(), e.getIndex1());
|
|
int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
|
|
|
|
/* Sync the SelectionModel with the DataModel.
|
|
*/
|
|
|
|
ListSelectionModel sm = list.getSelectionModel();
|
|
if (sm != null) {
|
|
sm.insertIndexInterval(minIndex, maxIndex - minIndex+1, true);
|
|
}
|
|
|
|
/* Repaint the entire list, from the origin of
|
|
* the first added cell, to the bottom of the
|
|
* component.
|
|
*/
|
|
redrawList();
|
|
}
|
|
|
|
|
|
public void intervalRemoved(ListDataEvent e)
|
|
{
|
|
updateLayoutStateNeeded = modelChanged;
|
|
|
|
/* Sync the SelectionModel with the DataModel.
|
|
*/
|
|
|
|
ListSelectionModel sm = list.getSelectionModel();
|
|
if (sm != null) {
|
|
sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
|
|
}
|
|
|
|
/* Repaint the entire list, from the origin of
|
|
* the first removed cell, to the bottom of the
|
|
* component.
|
|
*/
|
|
|
|
redrawList();
|
|
}
|
|
|
|
|
|
public void contentsChanged(ListDataEvent e) {
|
|
updateLayoutStateNeeded = modelChanged;
|
|
redrawList();
|
|
}
|
|
|
|
|
|
//
|
|
// ListSelectionListener
|
|
//
|
|
public void valueChanged(ListSelectionEvent e) {
|
|
maybeUpdateLayoutState();
|
|
|
|
int size = list.getModel().getSize();
|
|
int firstIndex = Math.min(size - 1, Math.max(e.getFirstIndex(), 0));
|
|
int lastIndex = Math.min(size - 1, Math.max(e.getLastIndex(), 0));
|
|
|
|
Rectangle bounds = getCellBounds(list, firstIndex, lastIndex);
|
|
|
|
if (bounds != null) {
|
|
list.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
}
|
|
}
|
|
|
|
//
|
|
// MouseListener
|
|
//
|
|
public void mouseClicked(MouseEvent e) {
|
|
}
|
|
|
|
public void mouseEntered(MouseEvent e) {
|
|
}
|
|
|
|
public void mouseExited(MouseEvent e) {
|
|
}
|
|
|
|
// Whether or not the mouse press (which is being considered as part
|
|
// of a drag sequence) also caused the selection change to be fully
|
|
// processed.
|
|
private boolean dragPressDidSelection;
|
|
|
|
public void mousePressed(MouseEvent e) {
|
|
if (SwingUtilities2.shouldIgnore(e, list)) {
|
|
return;
|
|
}
|
|
|
|
boolean dragEnabled = list.getDragEnabled();
|
|
boolean grabFocus = true;
|
|
|
|
// different behavior if drag is enabled
|
|
if (dragEnabled) {
|
|
int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
|
|
// if we have a valid row and this is a drag initiating event
|
|
if (row != -1 && DragRecognitionSupport.mousePressed(e)) {
|
|
dragPressDidSelection = false;
|
|
|
|
if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) {
|
|
// do nothing for control - will be handled on release
|
|
// or when drag starts
|
|
return;
|
|
} else if (!e.isShiftDown() && list.isSelectedIndex(row)) {
|
|
// clicking on something that's already selected
|
|
// and need to make it the lead now
|
|
list.addSelectionInterval(row, row);
|
|
return;
|
|
}
|
|
|
|
// could be a drag initiating event - don't grab focus
|
|
grabFocus = false;
|
|
|
|
dragPressDidSelection = true;
|
|
}
|
|
} else {
|
|
// When drag is enabled mouse drags won't change the selection
|
|
// in the list, so we only set the isAdjusting flag when it's
|
|
// not enabled
|
|
list.setValueIsAdjusting(true);
|
|
}
|
|
|
|
if (grabFocus) {
|
|
SwingUtilities2.adjustFocus(list);
|
|
}
|
|
|
|
adjustSelection(e);
|
|
}
|
|
|
|
private void adjustSelection(MouseEvent e) {
|
|
int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
|
|
if (row < 0) {
|
|
// If shift is down in multi-select, we should do nothing.
|
|
// For single select or non-shift-click, clear the selection
|
|
if (isFileList &&
|
|
e.getID() == MouseEvent.MOUSE_PRESSED &&
|
|
(!e.isShiftDown() ||
|
|
list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
|
|
list.clearSelection();
|
|
}
|
|
}
|
|
else {
|
|
int anchorIndex = adjustIndex(list.getAnchorSelectionIndex(), list);
|
|
boolean anchorSelected;
|
|
if (anchorIndex == -1) {
|
|
anchorIndex = 0;
|
|
anchorSelected = false;
|
|
} else {
|
|
anchorSelected = list.isSelectedIndex(anchorIndex);
|
|
}
|
|
|
|
if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) {
|
|
if (e.isShiftDown()) {
|
|
if (anchorSelected) {
|
|
list.addSelectionInterval(anchorIndex, row);
|
|
} else {
|
|
list.removeSelectionInterval(anchorIndex, row);
|
|
if (isFileList) {
|
|
list.addSelectionInterval(row, row);
|
|
list.getSelectionModel().setAnchorSelectionIndex(anchorIndex);
|
|
}
|
|
}
|
|
} else if (list.isSelectedIndex(row)) {
|
|
list.removeSelectionInterval(row, row);
|
|
} else {
|
|
list.addSelectionInterval(row, row);
|
|
}
|
|
} else if (e.isShiftDown()) {
|
|
list.setSelectionInterval(anchorIndex, row);
|
|
} else {
|
|
list.setSelectionInterval(row, row);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void dragStarting(MouseEvent me) {
|
|
if (BasicGraphicsUtils.isMenuShortcutKeyDown(me)) {
|
|
int row = SwingUtilities2.loc2IndexFileList(list, me.getPoint());
|
|
list.addSelectionInterval(row, row);
|
|
}
|
|
}
|
|
|
|
public void mouseDragged(MouseEvent e) {
|
|
if (SwingUtilities2.shouldIgnore(e, list)) {
|
|
return;
|
|
}
|
|
|
|
if (list.getDragEnabled()) {
|
|
DragRecognitionSupport.mouseDragged(e, this);
|
|
return;
|
|
}
|
|
|
|
if (e.isShiftDown() || BasicGraphicsUtils.isMenuShortcutKeyDown(e)) {
|
|
return;
|
|
}
|
|
|
|
int row = locationToIndex(list, e.getPoint());
|
|
if (row != -1) {
|
|
// 4835633. Dragging onto a File should not select it.
|
|
if (isFileList) {
|
|
return;
|
|
}
|
|
Rectangle cellBounds = getCellBounds(list, row, row);
|
|
if (cellBounds != null) {
|
|
list.scrollRectToVisible(cellBounds);
|
|
list.setSelectionInterval(row, row);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void mouseMoved(MouseEvent e) {
|
|
}
|
|
|
|
public void mouseReleased(MouseEvent e) {
|
|
if (SwingUtilities2.shouldIgnore(e, list)) {
|
|
return;
|
|
}
|
|
|
|
if (list.getDragEnabled()) {
|
|
MouseEvent me = DragRecognitionSupport.mouseReleased(e);
|
|
if (me != null) {
|
|
SwingUtilities2.adjustFocus(list);
|
|
if (!dragPressDidSelection) {
|
|
adjustSelection(me);
|
|
}
|
|
}
|
|
} else {
|
|
list.setValueIsAdjusting(false);
|
|
}
|
|
}
|
|
|
|
//
|
|
// FocusListener
|
|
//
|
|
protected void repaintCellFocus()
|
|
{
|
|
int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
|
|
if (leadIndex != -1) {
|
|
Rectangle r = getCellBounds(list, leadIndex, leadIndex);
|
|
if (r != null) {
|
|
list.repaint(r.x, r.y, r.width, r.height);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The focusGained() focusLost() methods run when the JList
|
|
* focus changes.
|
|
*/
|
|
|
|
public void focusGained(FocusEvent e) {
|
|
repaintCellFocus();
|
|
}
|
|
|
|
public void focusLost(FocusEvent e) {
|
|
repaintCellFocus();
|
|
}
|
|
}
|
|
|
|
private static int adjustIndex(int index, JList list) {
|
|
return index < list.getModel().getSize() ? index : -1;
|
|
}
|
|
|
|
private static final TransferHandler defaultTransferHandler = new ListTransferHandler();
|
|
|
|
static class ListTransferHandler extends TransferHandler implements UIResource {
|
|
|
|
/**
|
|
* Create a Transferable to use as the source for a data transfer.
|
|
*
|
|
* @param c The component holding the data to be transfered. This
|
|
* argument is provided to enable sharing of TransferHandlers by
|
|
* multiple components.
|
|
* @return The representation of the data to be transfered.
|
|
*
|
|
*/
|
|
protected Transferable createTransferable(JComponent c) {
|
|
if (c instanceof JList) {
|
|
JList list = (JList) c;
|
|
Object[] values = list.getSelectedValues();
|
|
|
|
if (values == null || values.length == 0) {
|
|
return null;
|
|
}
|
|
|
|
StringBuffer plainBuf = new StringBuffer();
|
|
StringBuffer htmlBuf = new StringBuffer();
|
|
|
|
htmlBuf.append("<html>\n<body>\n<ul>\n");
|
|
|
|
for (int i = 0; i < values.length; i++) {
|
|
Object obj = values[i];
|
|
String val = ((obj == null) ? "" : obj.toString());
|
|
plainBuf.append(val + "\n");
|
|
htmlBuf.append(" <li>" + val + "\n");
|
|
}
|
|
|
|
// remove the last newline
|
|
plainBuf.deleteCharAt(plainBuf.length() - 1);
|
|
htmlBuf.append("</ul>\n</body>\n</html>");
|
|
|
|
return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public int getSourceActions(JComponent c) {
|
|
return COPY;
|
|
}
|
|
|
|
}
|
|
}
|