| /* |
| * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code 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 General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| 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"); |
| |
| /** |
| * The instance of {@code JList}. |
| */ |
| protected JList<Object> list = null; |
| /** |
| * The instance of {@code CellRendererPane}. |
| */ |
| protected CellRendererPane rendererPane; |
| |
| // Listeners that this UI attaches to the JList |
| /** |
| * {@code FocusListener} that attached to {@code JList}. |
| */ |
| protected FocusListener focusListener; |
| /** |
| * {@code MouseInputListener} that attached to {@code JList}. |
| */ |
| protected MouseInputListener mouseInputListener; |
| /** |
| * {@code ListSelectionListener} that attached to {@code JList}. |
| */ |
| protected ListSelectionListener listSelectionListener; |
| /** |
| * {@code ListDataListener} that attached to {@code JList}. |
| */ |
| protected ListDataListener listDataListener; |
| /** |
| * {@code PropertyChangeListener} that attached to {@code JList}. |
| */ |
| protected PropertyChangeListener propertyChangeListener; |
| private Handler handler; |
| |
| /** |
| * The array of cells' height |
| */ |
| protected int[] cellHeights = null; |
| /** |
| * The height of cell. |
| */ |
| protected int cellHeight = -1; |
| /** |
| * The width of cell. |
| */ |
| protected int cellWidth = -1; |
| /** |
| * The value represents changes to {@code JList} model. |
| */ |
| 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. |
| */ |
| |
| /** |
| * The bit relates to model changed property. |
| */ |
| protected static final int modelChanged = 1 << 0; |
| /** |
| * The bit relates to selection model changed property. |
| */ |
| protected static final int selectionModelChanged = 1 << 1; |
| /** |
| * The bit relates to font changed property. |
| */ |
| protected static final int fontChanged = 1 << 2; |
| /** |
| * The bit relates to fixed cell width changed property. |
| */ |
| protected static final int fixedCellWidthChanged = 1 << 3; |
| /** |
| * The bit relates to fixed cell height changed property. |
| */ |
| protected static final int fixedCellHeightChanged = 1 << 4; |
| /** |
| * The bit relates to prototype cell value changed property. |
| */ |
| protected static final int prototypeCellValueChanged = 1 << 5; |
| /** |
| * The bit relates to cell renderer changed property. |
| */ |
| protected static final int cellRendererChanged = 1 << 6; |
| private static final int layoutOrientationChanged = 1 << 7; |
| private static final int heightChanged = 1 << 8; |
| private static final int widthChanged = 1 << 9; |
| private static final 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 {@code CellRendererPane} to paint it. |
| * Subclasses may want to override this method rather than {@code paint()}. |
| * |
| * @param g an instance of {@code Graphics} |
| * @param row a row |
| * @param rowBounds a bounding rectangle to render to |
| * @param cellRenderer a list of {@code ListCellRenderer} |
| * @param dataModel a list model |
| * @param selModel a selection model |
| * @param leadIndex a lead index |
| * @see #paint |
| */ |
| protected void paintCell( |
| Graphics g, |
| int row, |
| Rectangle rowBounds, |
| ListCellRenderer<Object> cellRenderer, |
| ListModel<Object> 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<Object> renderer = list.getCellRenderer(); |
| ListModel<Object> 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) { |
| @SuppressWarnings("unchecked") |
| ListCellRenderer<Object> 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 class="striped"> |
| * <caption>Describes the preferred size for each layout orientation |
| * </caption> |
| * <thead> |
| * <tr><th>Layout Orientation</th><th>Preferred Size</th></tr> |
| * </thead> |
| * <tbody> |
| * <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. |
| * </tbody> |
| * </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<Object> 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<Object> 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) { |
| @SuppressWarnings("unchecked") |
| ListCellRenderer<Object> tmp = (ListCellRenderer)(UIManager.get("List.cellRenderer")); |
| list.setCellRenderer(tmp); |
| } |
| |
| 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) |
| { |
| @SuppressWarnings("unchecked") |
| JList<Object> tmp = (JList)c; |
| list = tmp; |
| |
| 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 {@code BasicListUI}. |
| * {@code BasicListUI} delegates are allocated one per {@code JList}. |
| * |
| * @param list a component |
| * @return a new {@code 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. |
| * |
| * @param row a row |
| * @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 {@code JList} relative coordinate to the row that contains it, |
| * based on the current layout. If {@code y0} doesn't fall within any row, |
| * return -1. |
| * |
| * @param y0 a relative Y coordinate |
| * @return the row that contains y0, or -1 |
| * @see #getRowHeight |
| * @see #updateLayoutState |
| */ |
| protected int convertYToRow(int y0) |
| { |
| return convertLocationToRow(0, y0, false); |
| } |
| |
| |
| /** |
| * Return the {@code JList} relative Y coordinate of the origin of the specified |
| * row or -1 if row isn't valid. |
| * |
| * @param row a row |
| * @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<Object> dataModel = list.getModel(); |
| int dataModelSize = dataModel.getSize(); |
| ListCellRenderer<Object> 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 |
| */ |
| @SuppressWarnings("serial") // Same-version serialization only |
| 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 {@code MouseInputListener}. |
| * The delegate is added to the corresponding {@code java.awt.Component} listener |
| * lists at {@code installUI()} time. Subclasses can override this method to return |
| * a custom {@code 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> |
| * |
| * @return an instance of {@code MouseInputListener} |
| * @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 |
| { |
| /** |
| * Repaints focused cells. |
| */ |
| 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); |
| } |
| } |
| |
| /** |
| * Returns an instance of {@code FocusListener}. |
| * |
| * @return an instance of {@code FocusListener} |
| */ |
| 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 |
| */ |
| @SuppressWarnings("serial") // Same-version serialization only |
| public class ListSelectionHandler implements ListSelectionListener |
| { |
| public void valueChanged(ListSelectionEvent e) |
| { |
| getHandler().valueChanged(e); |
| } |
| } |
| |
| |
| /** |
| * Creates an instance of {@code ListSelectionHandler} that's added to |
| * the {@code JLists} by selectionModel as needed. Subclasses can override |
| * this method to return a custom {@code 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> |
| * |
| * @return an instance of {@code ListSelectionHandler} |
| * @see ListSelectionHandler |
| * @see #installUI |
| */ |
| protected ListSelectionListener createListSelectionListener() { |
| return getHandler(); |
| } |
| |
| |
| private void redrawList() { |
| list.revalidate(); |
| list.repaint(); |
| } |
| |
| |
| /** |
| * The {@code ListDataListener} that's added to the {@code JLists} model at |
| * {@code 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 |
| */ |
| @SuppressWarnings("serial") // Same-version serialization only |
| 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 {@code ListDataListener} that's added to |
| * the {@code JLists} by model as needed. Subclasses can override |
| * this method to return a custom {@code 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> |
| * |
| * @return an instance of {@code ListDataListener} |
| * @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 |
| */ |
| @SuppressWarnings("serial") // Same-version serialization only |
| public class PropertyChangeHandler implements PropertyChangeListener |
| { |
| public void propertyChange(PropertyChangeEvent e) |
| { |
| getHandler().propertyChange(e); |
| } |
| } |
| |
| |
| /** |
| * Creates an instance of {@code PropertyChangeHandler} that's added to |
| * the {@code JList} by {@code installUI()}. Subclasses can override this method |
| * to return a custom {@code 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> |
| * |
| * @return an instance of {@code PropertyChangeHandler} |
| * @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(); |
| @SuppressWarnings("unchecked") |
| JList<Object> 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); |
| } |
| } |
| |
| @Override |
| public boolean accept(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") { |
| @SuppressWarnings("unchecked") |
| ListModel<?> oldModel = (ListModel)e.getOldValue(); |
| @SuppressWarnings("unchecked") |
| 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(); |
| |
| @SuppressWarnings("serial") // Superclass is a JDK-implementation class |
| 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. |
| * |
| */ |
| @SuppressWarnings("deprecation") |
| 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; |
| } |
| |
| StringBuilder plainStr = new StringBuilder(); |
| StringBuilder htmlStr = new StringBuilder(); |
| |
| htmlStr.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()); |
| plainStr.append(val).append('\n'); |
| htmlStr.append(" <li>").append(val).append('\n'); |
| } |
| |
| // remove the last newline |
| plainStr.deleteCharAt(plainStr.length() - 1); |
| htmlStr.append("</ul>\n</body>\n</html>"); |
| |
| return new BasicTransferable(plainStr.toString(), htmlStr.toString()); |
| } |
| |
| return null; |
| } |
| |
| public int getSourceActions(JComponent c) { |
| return COPY; |
| } |
| |
| } |
| } |