org.jdesktop.swingx.plaf.basic.core.BasicXListUI Maven / Gradle / Ivy
/*
* $Id$
*
* Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.jdesktop.swingx.plaf.basic.core;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.Action;
import javax.swing.CellRendererPane;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListSelectionModel;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicListUI;
import javax.swing.text.Position;
import org.jdesktop.swingx.JXList;
import org.jdesktop.swingx.SwingXUtilities;
import org.jdesktop.swingx.plaf.LookAndFeelUtils;
import org.jdesktop.swingx.plaf.UIAction;
import org.jdesktop.swingx.plaf.basic.core.DragRecognitionSupport.BeforeDrag;
//import sun.swing.DefaultLookup;
//import sun.swing.SwingUtilities2;
//import sun.swing.UIAction;
/**
* An extensible implementation of {@code ListUI} for JXList.
* {@code BasicXListUI} instances cannot be shared between multiple
* lists.
*
* The heart of added functionality is to support sorting/filtering, that is keep
* model-selection and RowSorter state synchronized. The details are delegated to a ListSortUI,
* but this class is responsible to manage the sortUI on changes of list properties, model and
* view selection (same strategy as in JXTable).
*
* Note: this delegate is mostly a 1:1 copy of BasicListUI. The difference is that
* it accesses the list elements and list elementCount exclusively through the
* JXList api. This allows a clean implementation of sorting/filtering.
*
* The differences (goal was to touch as little code as possible as this needs
* to be updated on every change to core until that is changed to not access
* the list's model directly, sigh) for core functionality:
*
* - extracted method for list.getModel().getSize (for the delegate class and
* all contained static classes) and use that method exclusively
*
- similar for remaining list.getModel(): implemented wrapping listModel
* which messages the list
*
- rename key for shared actionMap to keep core list actions separate
* (just in case somebody wants both) - they point to the wrong delegate
*
- replaced references to SwingUtilities2 in sun packages by references to
* c&p'ed methods in SwingXUtilities
*
- replaced storage of shared Input/ActionMap in defaultLookup by direct
* storage in UIManager.
*
*
* Differences to achieve extended functionality:
*
* - added methods to un/-installSortUI and call in un/installUI(component)
*
- changed PropertyChangeHandler to call a) hasHandledPropertyChange to
* allow this class to replace super handler functinality and
* b) updateSortUI after handling all not-sorter related properties.
*
- changed createPropertyChangeListener to return a PropertyChangeHandler
*
- changed ListDataHandler to check if event handled by SortUI and delegate
* to handler only if not
*
- changed createListDataListener to return a ListDataHandler
*
- changed ListSelectionHandler to check if event handled by SortUI and
* delegate to handler only if not
*
changed createListSelectionListener to return a ListSelectionHandler
*
* Differences for bug fixes (due to incorrectly extending super):
*
* - Issue #1495-swingx: getBaseline throughs NPE
*
*
* Note: extension of core (instead of implement from scratch) is to keep
* external (?) code working which expects a ui delegate of type BasicSomething.
* LAF implementors with a custom ListUI extending BasicListUI should be able to
* add support for JXList by adding a separate CustomXListUI extending this, same
* as the default with parent changed. Beware: custom code must not
* call access the model directly or - if they insist - convert the row index to
* account for sorting/filtering! That's the whole point of this class.
*
* @version 1.127 12/02/08
* @author Hans Muller
* @author Philip Milne
* @author Shannon Hickey (drag and drop)
*/
public class BasicXListUI extends BasicListUI
{
private static final StringBuilder BASELINE_COMPONENT_KEY =
new StringBuilder("List.baselineComponent");
protected JXList 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;
// FIXME - JW LazyActionMap copy is in different package ... move here?
public 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());
}
//-------------------- X-Wrapper
private ListModel modelX;
private ListSortUI sortUI;
/**
* Compatibility Wrapper: a synthetic model which delegates to list api and throws
* @return
*/
protected ListModel getViewModel() {
if (modelX == null) {
modelX = new ListModel() {
@Override
public int getSize() {
return ((JXList) list).getElementCount();
}
@Override
public Object getElementAt(int index) {
return ((JXList) list).getElementAt(index);
}
@Override
public void addListDataListener(ListDataListener l) {
throw new UnsupportedOperationException("this is a synthetic model wrapper");
}
@Override
public void removeListDataListener(ListDataListener l) {
throw new UnsupportedOperationException("this is a synthetic model wrapper");
}
};
}
return modelX;
}
/**
* @return
*/
protected int getElementCount() {
return ((JXList) list).getElementCount();
}
protected Object getElementAt(int viewIndex) {
return ((JXList) list).getElementAt(viewIndex);
}
/**
* Fix for Issue #1495: NPE on getBaseline.
*
* As per contract, that methods needs to throw Exceptions on illegal
* parameters. As we by-pass super, need to do the check and throw
* ouerselves.
*
* @param c
* @param width
* @param height
*
* @throws IllegalArgumentException if width or height < 0
* @throws NPE if c == null
*/
protected void checkBaselinePrecondition(JComponent c, int width, int height) {
if (c == null) {
throw new NullPointerException("Component must be non-null");
}
if (width < 0 || height < 0) {
throw new IllegalArgumentException(
"Width and height must be >= 0");
}
}
//--------------- api to support/control sorting/filtering
protected ListSortUI getSortUI() {
return sortUI;
}
/**
* Installs SortUI if the list has a rowSorter. Does nothing if not.
*/
protected void installSortUI() {
if (list.getRowSorter() == null) return;
sortUI = new ListSortUI(list, list.getRowSorter());
}
/**
* Dispose and null's the sortUI if installed. Does nothing if not.
*/
protected void uninstallSortUI() {
if (sortUI == null) return;
sortUI.dispose();
sortUI = null;
}
/**
* Called from the PropertyChangeHandler.
*
* @param property the name of the changed property.
*/
protected void updateSortUI(String property) {
if ("rowSorter".equals(property)) {
updateSortUIToRowSorterProperty();
}
}
/**
*
*/
private void updateSortUIToRowSorterProperty() {
uninstallSortUI();
installSortUI();
}
/**
* Returns a boolean indicating whether or not the event has been processed
* by the sortUI.
* @param e
* @return
*/
protected boolean processedBySortUI(ListDataEvent e) {
if (sortUI == null)
return false;
sortUI.modelChanged(e);
updateLayoutStateNeeded = modelChanged;
redrawList();
return true;
}
/**
* Returns a boolean indicating whether or not the event has been processed
* by the sortUI.
* @param e
* @return
*/
protected boolean processedBySortUI(ListSelectionEvent e) {
if (sortUI == null) return false;
sortUI.viewSelectionChanged(e);
list.repaint();
return true;
}
//--------------------- enhanced support
/**
* Invalidates the cell size cache and revalidates/-paints the list.
*
*/
public void invalidateCellSizeCache() {
updateLayoutStateNeeded |= 1;
redrawList();
}
//--------------------- core copy
/**
* 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 = getViewModel();
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;
}
// PENDING JW: revisit ... side-effects?
// Color c = DefaultLookup.getColor(list, this, "List.dropLineColor", null);
Color c = UIManager.getColor("List.dropLineColor");
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 = getElementCount();
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);
checkBaselinePrecondition(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.
*
* Layout Orientation Preferred Size
*
* JList.VERTICAL
* 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.
*
*
* JList.VERTICAL_WRAP
* 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.
*
* JList.HORIZONTAL_WRAP
* 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.
* 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 JList
(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.
*
* 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 Insets
are determined from
* list.getInsets()
.
*
* @param c The JList component.
* @return The total size of the list.
*/
public Dimension getPreferredSize(JComponent c) {
maybeUpdateLayoutState();
int lastRow = getElementCount() - 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) < getElementCount()) {
s += 1;
list.setSelectedIndex(s);
list.ensureIndexIsVisible(s);
}
}
/**
* Registers the keyboard bindings on the JList
that the
* BasicXListUI
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, BasicXListUI.class,
"XList.actionMap");
}
InputMap getInputMap(int condition) {
if (condition == JComponent.WHEN_FOCUSED) {
// PENDING JW: side-effect when reverting to ui manager? revisit!
InputMap keyMap = (InputMap) UIManager.get("List.focusInputMap");
// InputMap keyMap = (InputMap)DefaultLookup.get(
// list, this, "List.focusInputMap");
InputMap rtlKeyMap;
if (isLeftToRight ||
((rtlKeyMap = (InputMap) UIManager.get("List.focusInputMap.RightToLeft"))
== null)) {
// ((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
* installKeyboardActions
.
* 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);
}
/**
* Create and install 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());
// JW: here we really want the model
ListModel model = list.getModel();
if (model != null) {
model.addListDataListener(listDataListener);
}
ListSelectionModel selectionModel = list.getSelectionModel();
if (selectionModel != null) {
selectionModel.addListSelectionListener(listSelectionListener);
}
}
/**
* Remove the listeners for 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;
}
/**
* Initialize JList properties, e.g. font, foreground, and background,
* and add 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);
}
}
}
}
/**
* Set the JList properties that haven't been explicitly overridden to
* null. A property is considered overridden if its current value
* is not a 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 this.list
by calling installDefaults()
,
* installListeners()
, and installKeyboardActions()
* in order.
*
* @see #installDefaults
* @see #installListeners
* @see #installKeyboardActions
*/
public void installUI(JComponent c)
{
list = (JXList)c;
layoutOrientation = list.getLayoutOrientation();
rendererPane = new CellRendererPane();
list.add(rendererPane);
columnCount = 1;
updateLayoutStateNeeded = modelChanged;
isLeftToRight = list.getComponentOrientation().isLeftToRight();
installDefaults();
installListeners();
installKeyboardActions();
installSortUI();
}
/**
* Uninitializes this.list
by calling uninstallListeners()
,
* uninstallKeyboardActions()
, and uninstallDefaults()
* in order. Sets this.list to null.
*
* @see #uninstallListeners
* @see #uninstallKeyboardActions
* @see #uninstallDefaults
*/
public void uninstallUI(JComponent c)
{
uninstallSortUI();
uninstallListeners();
uninstallDefaults();
uninstallKeyboardActions();
cellWidth = cellHeight = -1;
cellHeights = null;
listWidth = listHeight = -1;
list.remove(rendererPane);
rendererPane = null;
list = null;
}
/**
* Returns a new instance of BasicXListUI. BasicXListUI 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 BasicXListUI();
}
/**
* {@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 >= getElementCount()) {
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 index
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 >= getElementCount()) {
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 = getElementCount();
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 getElementCount();
}
if (column >= columnCount) {
return -1;
}
if (layoutOrientation == JList.VERTICAL_WRAP) {
if (column < (columnCount - 1)) {
return rowsPerColumn;
}
return getElementCount() - (columnCount - 1) *
rowsPerColumn;
}
// JList.HORIZONTAL_WRAP
int diff = columnCount - (columnCount * rowsPerColumn -
getElementCount());
if (column >= diff) {
return Math.max(0, rowsPerColumn - 1);
}
return rowsPerColumn;
}
/**
* Returns the model index for the specified display location.
* If column
xrow
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(getElementCount() - 1, rowsPerColumn *
column + Math.min(row, rowsPerColumn-1));
case JList.HORIZONTAL_WRAP:
return Math.min(getElementCount() - 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 index
will be
* displayed in..
*/
private int convertModelToRow(int index) {
int size = getElementCount();
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 index
will be
* displayed in.
*/
private int convertModelToColumn(int index) {
int size = getElementCount();
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[getElementCount()];
}
/* 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 = getViewModel();
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.
*
* This updates the rowsPerColumn,
columnCount
,
* preferredHeight
and potentially cellHeight
* instance variables.
*/
private void updateHorizontalLayoutState(int fixedCellWidth,
int fixedCellHeight) {
int visRows = list.getVisibleRowCount();
int dataModelSize = getElementCount();
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().
*
* Warning:
* 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 JavaBeansTM
* has been added to the java.beans
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.
*
* class MyListUI extends BasicXListUI {
* protected MouseInputListener createMouseInputListener() {
* return new MyMouseInputHandler();
* }
* public class MyMouseInputHandler extends MouseInputHandler {
* public void mouseMoved(MouseEvent e) {
* // do some extra work when the mouse moves
* super.mouseMoved(e);
* }
* }
* }
*
*
* @see MouseInputHandler
* @see #installUI
*/
protected MouseInputListener createMouseInputListener() {
return getHandler();
}
/**
* This inner class is marked "public" due to a compiler bug.
* This class should be treated as a "protected" inner class.
* Instantiate it only within subclasses of BasicTableUI.
*/
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.
*
* Warning:
* 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 JavaBeansTM
* has been added to the java.beans
package.
* Please see {@link java.beans.XMLEncoder}.
*
* @see #createListSelectionListener
* @see #getCellBounds
* @see #installUI
*/
public class ListSelectionHandler implements ListSelectionListener
{
public void valueChanged(ListSelectionEvent e)
{
if (processedBySortUI(e)) return;
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.
*
* class MyListUI extends BasicXListUI {
* protected ListSelectionListener createListSelectionListener() {
* return new MySelectionListener();
* }
* public class MySelectionListener extends ListSelectionHandler {
* public void valueChanged(ListSelectionEvent e) {
* // do some extra work when the selection changes
* super.valueChange(e);
* }
* }
* }
*
*
* @see ListSelectionHandler
* @see #installUI
*/
protected ListSelectionListener createListSelectionListener() {
return new ListSelectionHandler();
}
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.
*
* Warning:
* 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 JavaBeansTM
* has been added to the java.beans
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) {
if (processedBySortUI(e))
return;
getHandler().intervalAdded(e);
}
public void intervalRemoved(ListDataEvent e) {
if (processedBySortUI(e))
return;
getHandler().intervalRemoved(e);
}
public void contentsChanged(ListDataEvent e) {
if (processedBySortUI(e))
return;
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.
*
* class MyListUI extends BasicXListUI {
* protected ListDataListener createListDataListener() {
* 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);
* }
* }
* }
*
*
* @see ListDataListener
* @see JList#getModel
* @see #installUI
*/
protected ListDataListener createListDataListener() {
return new ListDataHandler();
}
/**
* 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.
*
* Warning:
* 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 JavaBeansTM
* has been added to the java.beans
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);
updateSortUI(e.getPropertyName());
}
}
/**
* 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.
*
* class MyListUI extends BasicXListUI {
* protected PropertyChangeListener createPropertyChangeListener() {
* 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);
* }
* }
* }
*
*
* @see PropertyChangeListener
* @see #installUI
*/
protected PropertyChangeListener createPropertyChangeListener() {
return new PropertyChangeHandler();
}
/** 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;
// PENDING JW: this is not a complete replacement of sun.UIAction ...
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();
BasicXListUI ui = (BasicXListUI)LookAndFeelUtils.getUIOfType(
list.getUI(), BasicXListUI.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,
getElementCount(list) - 1, 1);
}
else if (name == SELECT_LAST_ROW_EXTEND) {
changeSelection(list, EXTEND_SELECTION,
getElementCount(list) - 1, 1);
}
else if (name == SELECT_LAST_ROW_CHANGE_LEAD) {
changeSelection(list, CHANGE_LEAD,
getElementCount(list) - 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);
}
}
/**
* @param list
* @return
*/
private int getElementCount(JList list) {
return ((JXList) list).getElementCount();
}
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 = getElementCount(list);
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
SwingXUtilities.setLeadAnchorWithoutSelection(lsm, anchor, lead);
list.setValueIsAdjusting(false);
}
}
}
private int getNextPageIndex(JList list, int direction) {
if (getElementCount(list) == 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 < getElementCount(list)) {
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
if (lsm instanceof 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, BasicXListUI ui,
int amount) {
if (list.getLayoutOrientation() != JList.VERTICAL) {
int index = adjustIndex(list.getLeadSelectionIndex(), list);
int size = getElementCount(list);
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, BasicXListUI ui, int amount) {
int index = adjustIndex(list.getLeadSelectionIndex(), list);
int size = getElementCount(list);
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 timeFactor
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 anothe letter.
*/
public void keyTyped(KeyEvent e) {
JList src = (JList)e.getSource();
if (getElementCount() == 0 || e.isAltDown() || e.isControlDown() || e.isMetaDown() ||
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 >= getElementCount()) {
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 == "cellRenderer") {
updateLayoutStateNeeded |= cellRendererChanged;
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 = getElementCount();
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 (SwingXUtilities.shouldIgnore(e, list)) {
return;
}
boolean dragEnabled = list.getDragEnabled();
boolean grabFocus = true;
// different behavior if drag is enabled
if (dragEnabled) {
// PENDING JW: this isn't aware of sorting/filtering - fix!
int row = SwingXUtilities.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 (e.isControlDown()) {
// 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) {
SwingXUtilities.adjustFocus(list);
}
adjustSelection(e);
}
private void adjustSelection(MouseEvent e) {
// PENDING JW: this isn't aware of sorting/filtering - fix!
int row = SwingXUtilities.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 (e.isControlDown()) {
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 (me.isControlDown()) {
// PENDING JW: this isn't aware of sorting/filtering - fix!
int row = SwingXUtilities.loc2IndexFileList(list, me.getPoint());
list.addSelectionInterval(row, row);
}
}
public void mouseDragged(MouseEvent e) {
if (SwingXUtilities.shouldIgnore(e, list)) {
return;
}
if (list.getDragEnabled()) {
DragRecognitionSupport.mouseDragged(e, this);
return;
}
if (e.isShiftDown() || e.isControlDown()) {
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 (SwingXUtilities.shouldIgnore(e, list)) {
return;
}
if (list.getDragEnabled()) {
MouseEvent me = DragRecognitionSupport.mouseReleased(e);
if (me != null) {
SwingXUtilities.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 < ((JXList) list).getElementCount() ? 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("\n\n\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(" - " + val + "\n");
}
// remove the last newline
plainBuf.deleteCharAt(plainBuf.length() - 1);
htmlBuf.append("
\n\n");
return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
}
return null;
}
public int getSourceActions(JComponent c) {
return COPY;
}
}
}