42 import java.beans.PropertyChangeEvent;
43
44 import sun.swing.SwingUtilities2;
45 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
46
47 /**
48 * An extensible implementation of {@code ListUI}.
49 * <p>
50 * {@code BasicListUI} instances cannot be shared between multiple
51 * lists.
52 *
53 * @author Hans Muller
54 * @author Philip Milne
55 * @author Shannon Hickey (drag and drop)
56 */
57 public class BasicListUI extends ListUI
58 {
59 private static final StringBuilder BASELINE_COMPONENT_KEY =
60 new StringBuilder("List.baselineComponent");
61
62 protected JList list = null;
63 protected CellRendererPane rendererPane;
64
65 // Listeners that this UI attaches to the JList
66 protected FocusListener focusListener;
67 protected MouseInputListener mouseInputListener;
68 protected ListSelectionListener listSelectionListener;
69 protected ListDataListener listDataListener;
70 protected PropertyChangeListener propertyChangeListener;
71 private Handler handler;
72
73 protected int[] cellHeights = null;
74 protected int cellHeight = -1;
75 protected int cellWidth = -1;
76 protected int updateLayoutStateNeeded = modelChanged;
77 /**
78 * Height of the list. When asked to paint, if the current size of
79 * the list differs, this will update the layout state.
80 */
81 private int listHeight;
82
179
180 map.put(TransferHandler.getCutAction().getValue(Action.NAME),
181 TransferHandler.getCutAction());
182 map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
183 TransferHandler.getCopyAction());
184 map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
185 TransferHandler.getPasteAction());
186 }
187
188 /**
189 * Paint one List cell: compute the relevant state, get the "rubber stamp"
190 * cell renderer component, and then use the CellRendererPane to paint it.
191 * Subclasses may want to override this method rather than paint().
192 *
193 * @see #paint
194 */
195 protected void paintCell(
196 Graphics g,
197 int row,
198 Rectangle rowBounds,
199 ListCellRenderer cellRenderer,
200 ListModel dataModel,
201 ListSelectionModel selModel,
202 int leadIndex)
203 {
204 Object value = dataModel.getElementAt(row);
205 boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
206 boolean isSelected = selModel.isSelectedIndex(row);
207
208 Component rendererComponent =
209 cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
210
211 int cx = rowBounds.x;
212 int cy = rowBounds.y;
213 int cw = rowBounds.width;
214 int ch = rowBounds.height;
215
216 if (isFileList) {
217 // Shrink renderer to preferred size. This is mostly used on Windows
218 // where selection is only shown around the file name, instead of
219 // across the whole list cell.
220 int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
246 private void paintImpl(Graphics g, JComponent c)
247 {
248 switch (layoutOrientation) {
249 case JList.VERTICAL_WRAP:
250 if (list.getHeight() != listHeight) {
251 updateLayoutStateNeeded |= heightChanged;
252 redrawList();
253 }
254 break;
255 case JList.HORIZONTAL_WRAP:
256 if (list.getWidth() != listWidth) {
257 updateLayoutStateNeeded |= widthChanged;
258 redrawList();
259 }
260 break;
261 default:
262 break;
263 }
264 maybeUpdateLayoutState();
265
266 ListCellRenderer renderer = list.getCellRenderer();
267 ListModel dataModel = list.getModel();
268 ListSelectionModel selModel = list.getSelectionModel();
269 int size;
270
271 if ((renderer == null) || (size = dataModel.getSize()) == 0) {
272 return;
273 }
274
275 // Determine how many columns we need to paint
276 Rectangle paintBounds = g.getClipBounds();
277
278 int startColumn, endColumn;
279 if (c.getComponentOrientation().isLeftToRight()) {
280 startColumn = convertLocationToColumn(paintBounds.x,
281 paintBounds.y);
282 endColumn = convertLocationToColumn(paintBounds.x +
283 paintBounds.width,
284 paintBounds.y);
285 } else {
286 startColumn = convertLocationToColumn(paintBounds.x +
287 paintBounds.width,
461 }
462
463 return rect;
464 }
465
466 /**
467 * Returns the baseline.
468 *
469 * @throws NullPointerException {@inheritDoc}
470 * @throws IllegalArgumentException {@inheritDoc}
471 * @see javax.swing.JComponent#getBaseline(int, int)
472 * @since 1.6
473 */
474 public int getBaseline(JComponent c, int width, int height) {
475 super.getBaseline(c, width, height);
476 int rowHeight = list.getFixedCellHeight();
477 UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
478 Component renderer = (Component)lafDefaults.get(
479 BASELINE_COMPONENT_KEY);
480 if (renderer == null) {
481 ListCellRenderer lcr = (ListCellRenderer)UIManager.get(
482 "List.cellRenderer");
483
484 // fix for 6711072 some LAFs like Nimbus do not provide this
485 // UIManager key and we should not through a NPE here because of it
486 if (lcr == null) {
487 lcr = new DefaultListCellRenderer();
488 }
489 renderer = lcr.getListCellRendererComponent(
490 list, "a", -1, false, false);
491 lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
492 }
493 renderer.setFont(list.getFont());
494 // JList actually has much more complex behavior here.
495 // If rowHeight != -1 the rowHeight is either the max of all cell
496 // heights (layout orientation != VERTICAL), or is variable depending
497 // upon the cell. We assume a default size.
498 // We could theoretically query the real renderer, but that would
499 // not work for an empty model and the results may vary with
500 // the content.
501 if (rowHeight == -1) {
698 list.setTransferHandler(defaultTransferHandler);
699 // default TransferHandler doesn't support drop
700 // so we don't want drop handling
701 if (list.getDropTarget() instanceof UIResource) {
702 list.setDropTarget(null);
703 }
704 }
705
706 focusListener = createFocusListener();
707 mouseInputListener = createMouseInputListener();
708 propertyChangeListener = createPropertyChangeListener();
709 listSelectionListener = createListSelectionListener();
710 listDataListener = createListDataListener();
711
712 list.addFocusListener(focusListener);
713 list.addMouseListener(mouseInputListener);
714 list.addMouseMotionListener(mouseInputListener);
715 list.addPropertyChangeListener(propertyChangeListener);
716 list.addKeyListener(getHandler());
717
718 ListModel model = list.getModel();
719 if (model != null) {
720 model.addListDataListener(listDataListener);
721 }
722
723 ListSelectionModel selectionModel = list.getSelectionModel();
724 if (selectionModel != null) {
725 selectionModel.addListSelectionListener(listSelectionListener);
726 }
727 }
728
729
730 /**
731 * Removes the listeners from the JList, its model, and its
732 * selectionModel. All of the listener fields, are reset to
733 * null here. This method is called at uninstallUI() time,
734 * it should be kept in sync with installListeners.
735 *
736 * @see #uninstallUI
737 * @see #installListeners
738 */
739 protected void uninstallListeners()
740 {
741 list.removeFocusListener(focusListener);
742 list.removeMouseListener(mouseInputListener);
743 list.removeMouseMotionListener(mouseInputListener);
744 list.removePropertyChangeListener(propertyChangeListener);
745 list.removeKeyListener(getHandler());
746
747 ListModel model = list.getModel();
748 if (model != null) {
749 model.removeListDataListener(listDataListener);
750 }
751
752 ListSelectionModel selectionModel = list.getSelectionModel();
753 if (selectionModel != null) {
754 selectionModel.removeListSelectionListener(listSelectionListener);
755 }
756
757 focusListener = null;
758 mouseInputListener = null;
759 listSelectionListener = null;
760 listDataListener = null;
761 propertyChangeListener = null;
762 handler = null;
763 }
764
765
766 /**
767 * Initializes list properties such as font, foreground, and background,
768 * and adds the CellRendererPane. The font, foreground, and background
769 * properties are only set if their current value is either null
770 * or a UIResource, other properties are set if the current
771 * value is null.
772 *
773 * @see #uninstallDefaults
774 * @see #installUI
775 * @see CellRendererPane
776 */
777 protected void installDefaults()
778 {
779 list.setLayout(null);
780
781 LookAndFeel.installBorder(list, "List.border");
782
783 LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
784
785 LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
786
787 if (list.getCellRenderer() == null) {
788 list.setCellRenderer((ListCellRenderer)(UIManager.get("List.cellRenderer")));
789 }
790
791 Color sbg = list.getSelectionBackground();
792 if (sbg == null || sbg instanceof UIResource) {
793 list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
794 }
795
796 Color sfg = list.getSelectionForeground();
797 if (sfg == null || sfg instanceof UIResource) {
798 list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
799 }
800
801 Long l = (Long)UIManager.get("List.timeFactor");
802 timeFactor = (l!=null) ? l.longValue() : 1000L;
803
804 updateIsFileList();
805 }
806
807 private void updateIsFileList() {
808 boolean b = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
849 if (list.getCellRenderer() instanceof UIResource) {
850 list.setCellRenderer(null);
851 }
852 if (list.getTransferHandler() instanceof UIResource) {
853 list.setTransferHandler(null);
854 }
855 }
856
857
858 /**
859 * Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
860 * <code>installListeners()</code>, and <code>installKeyboardActions()</code>
861 * in order.
862 *
863 * @see #installDefaults
864 * @see #installListeners
865 * @see #installKeyboardActions
866 */
867 public void installUI(JComponent c)
868 {
869 list = (JList)c;
870
871 layoutOrientation = list.getLayoutOrientation();
872
873 rendererPane = new CellRendererPane();
874 list.add(rendererPane);
875
876 columnCount = 1;
877
878 updateLayoutStateNeeded = modelChanged;
879 isLeftToRight = list.getComponentOrientation().isLeftToRight();
880
881 installDefaults();
882 installListeners();
883 installKeyboardActions();
884 }
885
886
887 /**
888 * Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
889 * <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
908 rendererPane = null;
909 list = null;
910 }
911
912
913 /**
914 * Returns a new instance of BasicListUI. BasicListUI delegates are
915 * allocated one per JList.
916 *
917 * @return A new ListUI implementation for the Windows look and feel.
918 */
919 public static ComponentUI createUI(JComponent list) {
920 return new BasicListUI();
921 }
922
923
924 /**
925 * {@inheritDoc}
926 * @throws NullPointerException {@inheritDoc}
927 */
928 public int locationToIndex(JList list, Point location) {
929 maybeUpdateLayoutState();
930 return convertLocationToModel(location.x, location.y);
931 }
932
933
934 /**
935 * {@inheritDoc}
936 */
937 public Point indexToLocation(JList list, int index) {
938 maybeUpdateLayoutState();
939 Rectangle rect = getCellBounds(list, index, index);
940
941 if (rect != null) {
942 return new Point(rect.x, rect.y);
943 }
944 return null;
945 }
946
947
948 /**
949 * {@inheritDoc}
950 */
951 public Rectangle getCellBounds(JList list, int index1, int index2) {
952 maybeUpdateLayoutState();
953
954 int minIndex = Math.min(index1, index2);
955 int maxIndex = Math.max(index1, index2);
956
957 if (minIndex >= list.getModel().getSize()) {
958 return null;
959 }
960
961 Rectangle minBounds = getCellBounds(list, minIndex);
962
963 if (minBounds == null) {
964 return null;
965 }
966 if (minIndex == maxIndex) {
967 return minBounds;
968 }
969 Rectangle maxBounds = getCellBounds(list, maxIndex);
970
971 if (maxBounds != null) {
975
976 if (minRow != maxRow) {
977 minBounds.x = 0;
978 minBounds.width = list.getWidth();
979 }
980 }
981 else if (minBounds.x != maxBounds.x) {
982 // Different columns
983 minBounds.y = 0;
984 minBounds.height = list.getHeight();
985 }
986 minBounds.add(maxBounds);
987 }
988 return minBounds;
989 }
990
991 /**
992 * Gets the bounds of the specified model index, returning the resulting
993 * bounds, or null if <code>index</code> is not valid.
994 */
995 private Rectangle getCellBounds(JList list, int index) {
996 maybeUpdateLayoutState();
997
998 int row = convertModelToRow(index);
999 int column = convertModelToColumn(index);
1000
1001 if (row == -1 || column == -1) {
1002 return null;
1003 }
1004
1005 Insets insets = list.getInsets();
1006 int x;
1007 int w = cellWidth;
1008 int y = insets.top;
1009 int h;
1010 switch (layoutOrientation) {
1011 case JList.VERTICAL_WRAP:
1012 case JList.HORIZONTAL_WRAP:
1013 if (isLeftToRight) {
1014 x = insets.left + column * cellWidth;
1015 } else {
1334 cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
1335
1336 if (fixedCellHeight != -1) {
1337 cellHeight = fixedCellHeight;
1338 cellHeights = null;
1339 }
1340 else {
1341 cellHeight = -1;
1342 cellHeights = new int[list.getModel().getSize()];
1343 }
1344
1345 /* If either of JList fixedCellWidth and fixedCellHeight haven't
1346 * been set, then initialize cellWidth and cellHeights by
1347 * scanning through the entire model. Note: if the renderer is
1348 * null, we just set cellWidth and cellHeights[*] to zero,
1349 * if they're not set already.
1350 */
1351
1352 if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
1353
1354 ListModel dataModel = list.getModel();
1355 int dataModelSize = dataModel.getSize();
1356 ListCellRenderer renderer = list.getCellRenderer();
1357
1358 if (renderer != null) {
1359 for(int index = 0; index < dataModelSize; index++) {
1360 Object value = dataModel.getElementAt(index);
1361 Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
1362 rendererPane.add(c);
1363 Dimension cellSize = c.getPreferredSize();
1364 if (fixedCellWidth == -1) {
1365 cellWidth = Math.max(cellSize.width, cellWidth);
1366 }
1367 if (fixedCellHeight == -1) {
1368 cellHeights[index] = cellSize.height;
1369 }
1370 }
1371 }
1372 else {
1373 if (cellWidth == -1) {
1374 cellWidth = 0;
1375 }
1376 if (cellHeights == null) {
1821 private static final String SELECT_ALL = "selectAll";
1822 private static final String CLEAR_SELECTION = "clearSelection";
1823
1824 // add the lead item to the selection without changing lead or anchor
1825 private static final String ADD_TO_SELECTION = "addToSelection";
1826
1827 // toggle the selected state of the lead item and move the anchor to it
1828 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
1829
1830 // extend the selection to the lead item
1831 private static final String EXTEND_TO = "extendTo";
1832
1833 // move the anchor to the lead and ensure only that item is selected
1834 private static final String MOVE_SELECTION_TO = "moveSelectionTo";
1835
1836 Actions(String name) {
1837 super(name);
1838 }
1839 public void actionPerformed(ActionEvent e) {
1840 String name = getName();
1841 JList list = (JList)e.getSource();
1842 BasicListUI ui = (BasicListUI)BasicLookAndFeel.getUIOfType(
1843 list.getUI(), BasicListUI.class);
1844
1845 if (name == SELECT_PREVIOUS_COLUMN) {
1846 changeSelection(list, CHANGE_SELECTION,
1847 getNextColumnIndex(list, ui, -1), -1);
1848 }
1849 else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
1850 changeSelection(list, EXTEND_SELECTION,
1851 getNextColumnIndex(list, ui, -1), -1);
1852 }
1853 else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
1854 changeSelection(list, CHANGE_LEAD,
1855 getNextColumnIndex(list, ui, -1), -1);
1856 }
1857 else if (name == SELECT_NEXT_COLUMN) {
1858 changeSelection(list, CHANGE_SELECTION,
1859 getNextColumnIndex(list, ui, 1), 1);
1860 }
1861 else if (name == SELECT_NEXT_COLUMN_EXTEND) {
1980 public boolean isEnabled(Object c) {
1981 Object name = getName();
1982 if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD ||
1983 name == SELECT_NEXT_COLUMN_CHANGE_LEAD ||
1984 name == SELECT_PREVIOUS_ROW_CHANGE_LEAD ||
1985 name == SELECT_NEXT_ROW_CHANGE_LEAD ||
1986 name == SELECT_FIRST_ROW_CHANGE_LEAD ||
1987 name == SELECT_LAST_ROW_CHANGE_LEAD ||
1988 name == SCROLL_UP_CHANGE_LEAD ||
1989 name == SCROLL_DOWN_CHANGE_LEAD) {
1990
1991 // discontinuous selection actions are only enabled for
1992 // DefaultListSelectionModel
1993 return c != null && ((JList)c).getSelectionModel()
1994 instanceof DefaultListSelectionModel;
1995 }
1996
1997 return true;
1998 }
1999
2000 private void clearSelection(JList list) {
2001 list.clearSelection();
2002 }
2003
2004 private void selectAll(JList list) {
2005 int size = list.getModel().getSize();
2006 if (size > 0) {
2007 ListSelectionModel lsm = list.getSelectionModel();
2008 int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2009
2010 if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
2011 if (lead == -1) {
2012 int min = adjustIndex(list.getMinSelectionIndex(), list);
2013 lead = (min == -1 ? 0 : min);
2014 }
2015
2016 list.setSelectionInterval(lead, lead);
2017 list.ensureIndexIsVisible(lead);
2018 } else {
2019 list.setValueIsAdjusting(true);
2020
2021 int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2022
2023 list.setSelectionInterval(0, size - 1);
2024
2025 // this is done to restore the anchor and lead
2026 SwingUtilities2.setLeadAnchorWithoutSelection(lsm, anchor, lead);
2027
2028 list.setValueIsAdjusting(false);
2029 }
2030 }
2031 }
2032
2033 private int getNextPageIndex(JList list, int direction) {
2034 if (list.getModel().getSize() == 0) {
2035 return -1;
2036 }
2037
2038 int index = -1;
2039 Rectangle visRect = list.getVisibleRect();
2040 ListSelectionModel lsm = list.getSelectionModel();
2041 int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2042 Rectangle leadRect =
2043 (lead==-1) ? new Rectangle() : list.getCellBounds(lead, lead);
2044
2045 if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2046 list.getVisibleRowCount() <= 0) {
2047 if (!list.getComponentOrientation().isLeftToRight()) {
2048 direction = -direction;
2049 }
2050 // apply for horizontal scrolling: the step for next
2051 // page index is number of visible columns
2052 if (direction < 0) {
2053 // left
2138 // go one cell up if last visible cell doesn't fit
2139 // into adjasted visible rectangle
2140 if (cellBounds.y + cellBounds.height >
2141 visRect.y + visRect.height) {
2142 p.y = cellBounds.y - 1;
2143 index = list.locationToIndex(p);
2144 cellBounds = list.getCellBounds(index, index);
2145 }
2146 // if index isn't greater then lead
2147 // try to go to cell next after lead
2148 if (cellBounds.y <= leadRect.y) {
2149 p.y = leadRect.y + leadRect.height;
2150 index = list.locationToIndex(p);
2151 }
2152 }
2153 }
2154 }
2155 return index;
2156 }
2157
2158 private void changeSelection(JList list, int type,
2159 int index, int direction) {
2160 if (index >= 0 && index < list.getModel().getSize()) {
2161 ListSelectionModel lsm = list.getSelectionModel();
2162
2163 // CHANGE_LEAD is only valid with multiple interval selection
2164 if (type == CHANGE_LEAD &&
2165 list.getSelectionMode()
2166 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
2167
2168 type = CHANGE_SELECTION;
2169 }
2170
2171 // IMPORTANT - This needs to happen before the index is changed.
2172 // This is because JFileChooser, which uses JList, also scrolls
2173 // the selected item into view. If that happens first, then
2174 // this method becomes a no-op.
2175 adjustScrollPositionIfNecessary(list, index, direction);
2176
2177 if (type == EXTEND_SELECTION) {
2178 int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2181 }
2182
2183 list.setSelectionInterval(anchor, index);
2184 }
2185 else if (type == CHANGE_SELECTION) {
2186 list.setSelectedIndex(index);
2187 }
2188 else {
2189 // casting should be safe since the action is only enabled
2190 // for DefaultListSelectionModel
2191 ((DefaultListSelectionModel)lsm).moveLeadSelectionIndex(index);
2192 }
2193 }
2194 }
2195
2196 /**
2197 * When scroll down makes selected index the last completely visible
2198 * index. When scroll up makes selected index the first visible index.
2199 * Adjust visible rectangle respect to list's component orientation.
2200 */
2201 private void adjustScrollPositionIfNecessary(JList list, int index,
2202 int direction) {
2203 if (direction == 0) {
2204 return;
2205 }
2206 Rectangle cellBounds = list.getCellBounds(index, index);
2207 Rectangle visRect = list.getVisibleRect();
2208 if (cellBounds != null && !visRect.contains(cellBounds)) {
2209 if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2210 list.getVisibleRowCount() <= 0) {
2211 // horizontal
2212 if (list.getComponentOrientation().isLeftToRight()) {
2213 if (direction > 0) {
2214 // right for left-to-right
2215 int x =Math.max(0,
2216 cellBounds.x + cellBounds.width - visRect.width);
2217 int startIndex =
2218 list.locationToIndex(new Point(x, cellBounds.y));
2219 Rectangle startRect = list.getCellBounds(startIndex,
2220 startIndex);
2221 if (startRect.x < x && startRect.x < cellBounds.x) {
2269 startIndex);
2270 if (startRect.y < y && startRect.y < cellBounds.y) {
2271 startRect.y += startRect.height;
2272 startIndex =
2273 list.locationToIndex(startRect.getLocation());
2274 startRect =
2275 list.getCellBounds(startIndex, startIndex);
2276 }
2277 cellBounds = startRect;
2278 cellBounds.height = visRect.height;
2279 }
2280 else {
2281 // adjust height to fit into visible rectangle
2282 cellBounds.height = Math.min(cellBounds.height, visRect.height);
2283 }
2284 }
2285 list.scrollRectToVisible(cellBounds);
2286 }
2287 }
2288
2289 private int getNextColumnIndex(JList list, BasicListUI ui,
2290 int amount) {
2291 if (list.getLayoutOrientation() != JList.VERTICAL) {
2292 int index = adjustIndex(list.getLeadSelectionIndex(), list);
2293 int size = list.getModel().getSize();
2294
2295 if (index == -1) {
2296 return 0;
2297 } else if (size == 1) {
2298 // there's only one item so we should select it
2299 return 0;
2300 } else if (ui == null || ui.columnCount <= 1) {
2301 return -1;
2302 }
2303
2304 int column = ui.convertModelToColumn(index);
2305 int row = ui.convertModelToRow(index);
2306
2307 column += amount;
2308 if (column >= ui.columnCount || column < 0) {
2309 // No wrapping.
2310 return -1;
2311 }
2312 int maxRowCount = ui.getRowCount(column);
2313 if (row >= maxRowCount) {
2314 return -1;
2315 }
2316 return ui.getModelIndex(column, row);
2317 }
2318 // Won't change the selection.
2319 return -1;
2320 }
2321
2322 private int getNextIndex(JList list, BasicListUI ui, int amount) {
2323 int index = adjustIndex(list.getLeadSelectionIndex(), list);
2324 int size = list.getModel().getSize();
2325
2326 if (index == -1) {
2327 if (size > 0) {
2328 if (amount > 0) {
2329 index = 0;
2330 }
2331 else {
2332 index = size - 1;
2333 }
2334 }
2335 } else if (size == 1) {
2336 // there's only one item so we should select it
2337 index = 0;
2338 } else if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
2339 if (ui != null) {
2340 index += ui.columnCount * amount;
2341 }
2342 } else {
2354 BeforeDrag {
2355 //
2356 // KeyListener
2357 //
2358 private String prefix = "";
2359 private String typedString = "";
2360 private long lastTime = 0L;
2361
2362 /**
2363 * Invoked when a key has been typed.
2364 *
2365 * Moves the keyboard focus to the first element whose prefix matches the
2366 * sequence of alphanumeric keys pressed by the user with delay less
2367 * than value of <code>timeFactor</code> property (or 1000 milliseconds
2368 * if it is not defined). Subsequent same key presses move the keyboard
2369 * focus to the next object that starts with the same letter until another
2370 * key is pressed, then it is treated as the prefix with appropriate number
2371 * of the same letters followed by first typed another letter.
2372 */
2373 public void keyTyped(KeyEvent e) {
2374 JList src = (JList)e.getSource();
2375 ListModel model = src.getModel();
2376
2377 if (model.getSize() == 0 || e.isAltDown() ||
2378 BasicGraphicsUtils.isMenuShortcutKeyDown(e) ||
2379 isNavigationKey(e)) {
2380 // Nothing to select
2381 return;
2382 }
2383 boolean startingFromSelection = true;
2384
2385 char c = e.getKeyChar();
2386
2387 long time = e.getWhen();
2388 int startIndex = adjustIndex(src.getLeadSelectionIndex(), list);
2389 if (time - lastTime < timeFactor) {
2390 typedString += c;
2391 if((prefix.length() == 1) && (c == prefix.charAt(0))) {
2392 // Subsequent same key presses move the keyboard focus to the next
2393 // object that starts with the same letter.
2394 startIndex++;
2395 } else {
2451 private boolean isNavigationKey(KeyEvent event) {
2452 InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
2453 KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
2454
2455 if (inputMap != null && inputMap.get(key) != null) {
2456 return true;
2457 }
2458 return false;
2459 }
2460
2461 //
2462 // PropertyChangeListener
2463 //
2464 public void propertyChange(PropertyChangeEvent e) {
2465 String propertyName = e.getPropertyName();
2466
2467 /* If the JList.model property changes, remove our listener,
2468 * listDataListener from the old model and add it to the new one.
2469 */
2470 if (propertyName == "model") {
2471 ListModel oldModel = (ListModel)e.getOldValue();
2472 ListModel newModel = (ListModel)e.getNewValue();
2473 if (oldModel != null) {
2474 oldModel.removeListDataListener(listDataListener);
2475 }
2476 if (newModel != null) {
2477 newModel.addListDataListener(listDataListener);
2478 }
2479 updateLayoutStateNeeded |= modelChanged;
2480 redrawList();
2481 }
2482
2483 /* If the JList.selectionModel property changes, remove our listener,
2484 * listSelectionListener from the old selectionModel and add it to the new one.
2485 */
2486 else if (propertyName == "selectionModel") {
2487 ListSelectionModel oldModel = (ListSelectionModel)e.getOldValue();
2488 ListSelectionModel newModel = (ListSelectionModel)e.getNewValue();
2489 if (oldModel != null) {
2490 oldModel.removeListSelectionListener(listSelectionListener);
2491 }
2492 if (newModel != null) {
2811 Rectangle r = getCellBounds(list, leadIndex, leadIndex);
2812 if (r != null) {
2813 list.repaint(r.x, r.y, r.width, r.height);
2814 }
2815 }
2816 }
2817
2818 /* The focusGained() focusLost() methods run when the JList
2819 * focus changes.
2820 */
2821
2822 public void focusGained(FocusEvent e) {
2823 repaintCellFocus();
2824 }
2825
2826 public void focusLost(FocusEvent e) {
2827 repaintCellFocus();
2828 }
2829 }
2830
2831 private static int adjustIndex(int index, JList list) {
2832 return index < list.getModel().getSize() ? index : -1;
2833 }
2834
2835 private static final TransferHandler defaultTransferHandler = new ListTransferHandler();
2836
2837 @SuppressWarnings("serial") // Superclass is a JDK-implementation class
2838 static class ListTransferHandler extends TransferHandler implements UIResource {
2839
2840 /**
2841 * Create a Transferable to use as the source for a data transfer.
2842 *
2843 * @param c The component holding the data to be transfered. This
2844 * argument is provided to enable sharing of TransferHandlers by
2845 * multiple components.
2846 * @return The representation of the data to be transfered.
2847 *
2848 */
2849 protected Transferable createTransferable(JComponent c) {
2850 if (c instanceof JList) {
2851 JList list = (JList) c;
2852 Object[] values = list.getSelectedValues();
2853
2854 if (values == null || values.length == 0) {
2855 return null;
2856 }
2857
2858 StringBuilder plainStr = new StringBuilder();
2859 StringBuilder htmlStr = new StringBuilder();
2860
2861 htmlStr.append("<html>\n<body>\n<ul>\n");
2862
2863 for (int i = 0; i < values.length; i++) {
2864 Object obj = values[i];
2865 String val = ((obj == null) ? "" : obj.toString());
2866 plainStr.append(val + "\n");
2867 htmlStr.append(" <li>" + val + "\n");
2868 }
2869
2870 // remove the last newline
2871 plainStr.deleteCharAt(plainStr.length() - 1);
|
42 import java.beans.PropertyChangeEvent;
43
44 import sun.swing.SwingUtilities2;
45 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
46
47 /**
48 * An extensible implementation of {@code ListUI}.
49 * <p>
50 * {@code BasicListUI} instances cannot be shared between multiple
51 * lists.
52 *
53 * @author Hans Muller
54 * @author Philip Milne
55 * @author Shannon Hickey (drag and drop)
56 */
57 public class BasicListUI extends ListUI
58 {
59 private static final StringBuilder BASELINE_COMPONENT_KEY =
60 new StringBuilder("List.baselineComponent");
61
62 protected JList<Object> list = null;
63 protected CellRendererPane rendererPane;
64
65 // Listeners that this UI attaches to the JList
66 protected FocusListener focusListener;
67 protected MouseInputListener mouseInputListener;
68 protected ListSelectionListener listSelectionListener;
69 protected ListDataListener listDataListener;
70 protected PropertyChangeListener propertyChangeListener;
71 private Handler handler;
72
73 protected int[] cellHeights = null;
74 protected int cellHeight = -1;
75 protected int cellWidth = -1;
76 protected int updateLayoutStateNeeded = modelChanged;
77 /**
78 * Height of the list. When asked to paint, if the current size of
79 * the list differs, this will update the layout state.
80 */
81 private int listHeight;
82
179
180 map.put(TransferHandler.getCutAction().getValue(Action.NAME),
181 TransferHandler.getCutAction());
182 map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
183 TransferHandler.getCopyAction());
184 map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
185 TransferHandler.getPasteAction());
186 }
187
188 /**
189 * Paint one List cell: compute the relevant state, get the "rubber stamp"
190 * cell renderer component, and then use the CellRendererPane to paint it.
191 * Subclasses may want to override this method rather than paint().
192 *
193 * @see #paint
194 */
195 protected void paintCell(
196 Graphics g,
197 int row,
198 Rectangle rowBounds,
199 ListCellRenderer<Object> cellRenderer,
200 ListModel<Object> dataModel,
201 ListSelectionModel selModel,
202 int leadIndex)
203 {
204 Object value = dataModel.getElementAt(row);
205 boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
206 boolean isSelected = selModel.isSelectedIndex(row);
207
208 Component rendererComponent =
209 cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
210
211 int cx = rowBounds.x;
212 int cy = rowBounds.y;
213 int cw = rowBounds.width;
214 int ch = rowBounds.height;
215
216 if (isFileList) {
217 // Shrink renderer to preferred size. This is mostly used on Windows
218 // where selection is only shown around the file name, instead of
219 // across the whole list cell.
220 int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
246 private void paintImpl(Graphics g, JComponent c)
247 {
248 switch (layoutOrientation) {
249 case JList.VERTICAL_WRAP:
250 if (list.getHeight() != listHeight) {
251 updateLayoutStateNeeded |= heightChanged;
252 redrawList();
253 }
254 break;
255 case JList.HORIZONTAL_WRAP:
256 if (list.getWidth() != listWidth) {
257 updateLayoutStateNeeded |= widthChanged;
258 redrawList();
259 }
260 break;
261 default:
262 break;
263 }
264 maybeUpdateLayoutState();
265
266 ListCellRenderer<Object> renderer = list.getCellRenderer();
267 ListModel<Object> dataModel = list.getModel();
268 ListSelectionModel selModel = list.getSelectionModel();
269 int size;
270
271 if ((renderer == null) || (size = dataModel.getSize()) == 0) {
272 return;
273 }
274
275 // Determine how many columns we need to paint
276 Rectangle paintBounds = g.getClipBounds();
277
278 int startColumn, endColumn;
279 if (c.getComponentOrientation().isLeftToRight()) {
280 startColumn = convertLocationToColumn(paintBounds.x,
281 paintBounds.y);
282 endColumn = convertLocationToColumn(paintBounds.x +
283 paintBounds.width,
284 paintBounds.y);
285 } else {
286 startColumn = convertLocationToColumn(paintBounds.x +
287 paintBounds.width,
461 }
462
463 return rect;
464 }
465
466 /**
467 * Returns the baseline.
468 *
469 * @throws NullPointerException {@inheritDoc}
470 * @throws IllegalArgumentException {@inheritDoc}
471 * @see javax.swing.JComponent#getBaseline(int, int)
472 * @since 1.6
473 */
474 public int getBaseline(JComponent c, int width, int height) {
475 super.getBaseline(c, width, height);
476 int rowHeight = list.getFixedCellHeight();
477 UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
478 Component renderer = (Component)lafDefaults.get(
479 BASELINE_COMPONENT_KEY);
480 if (renderer == null) {
481 @SuppressWarnings("unchecked")
482 ListCellRenderer<Object> lcr = (ListCellRenderer)UIManager.get(
483 "List.cellRenderer");
484
485 // fix for 6711072 some LAFs like Nimbus do not provide this
486 // UIManager key and we should not through a NPE here because of it
487 if (lcr == null) {
488 lcr = new DefaultListCellRenderer();
489 }
490 renderer = lcr.getListCellRendererComponent(
491 list, "a", -1, false, false);
492 lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
493 }
494 renderer.setFont(list.getFont());
495 // JList actually has much more complex behavior here.
496 // If rowHeight != -1 the rowHeight is either the max of all cell
497 // heights (layout orientation != VERTICAL), or is variable depending
498 // upon the cell. We assume a default size.
499 // We could theoretically query the real renderer, but that would
500 // not work for an empty model and the results may vary with
501 // the content.
502 if (rowHeight == -1) {
699 list.setTransferHandler(defaultTransferHandler);
700 // default TransferHandler doesn't support drop
701 // so we don't want drop handling
702 if (list.getDropTarget() instanceof UIResource) {
703 list.setDropTarget(null);
704 }
705 }
706
707 focusListener = createFocusListener();
708 mouseInputListener = createMouseInputListener();
709 propertyChangeListener = createPropertyChangeListener();
710 listSelectionListener = createListSelectionListener();
711 listDataListener = createListDataListener();
712
713 list.addFocusListener(focusListener);
714 list.addMouseListener(mouseInputListener);
715 list.addMouseMotionListener(mouseInputListener);
716 list.addPropertyChangeListener(propertyChangeListener);
717 list.addKeyListener(getHandler());
718
719 ListModel<Object> model = list.getModel();
720 if (model != null) {
721 model.addListDataListener(listDataListener);
722 }
723
724 ListSelectionModel selectionModel = list.getSelectionModel();
725 if (selectionModel != null) {
726 selectionModel.addListSelectionListener(listSelectionListener);
727 }
728 }
729
730
731 /**
732 * Removes the listeners from the JList, its model, and its
733 * selectionModel. All of the listener fields, are reset to
734 * null here. This method is called at uninstallUI() time,
735 * it should be kept in sync with installListeners.
736 *
737 * @see #uninstallUI
738 * @see #installListeners
739 */
740 protected void uninstallListeners()
741 {
742 list.removeFocusListener(focusListener);
743 list.removeMouseListener(mouseInputListener);
744 list.removeMouseMotionListener(mouseInputListener);
745 list.removePropertyChangeListener(propertyChangeListener);
746 list.removeKeyListener(getHandler());
747
748 ListModel<Object> model = list.getModel();
749 if (model != null) {
750 model.removeListDataListener(listDataListener);
751 }
752
753 ListSelectionModel selectionModel = list.getSelectionModel();
754 if (selectionModel != null) {
755 selectionModel.removeListSelectionListener(listSelectionListener);
756 }
757
758 focusListener = null;
759 mouseInputListener = null;
760 listSelectionListener = null;
761 listDataListener = null;
762 propertyChangeListener = null;
763 handler = null;
764 }
765
766
767 /**
768 * Initializes list properties such as font, foreground, and background,
769 * and adds the CellRendererPane. The font, foreground, and background
770 * properties are only set if their current value is either null
771 * or a UIResource, other properties are set if the current
772 * value is null.
773 *
774 * @see #uninstallDefaults
775 * @see #installUI
776 * @see CellRendererPane
777 */
778 protected void installDefaults()
779 {
780 list.setLayout(null);
781
782 LookAndFeel.installBorder(list, "List.border");
783
784 LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
785
786 LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
787
788 if (list.getCellRenderer() == null) {
789 @SuppressWarnings("unchecked")
790 ListCellRenderer<Object> tmp = (ListCellRenderer)(UIManager.get("List.cellRenderer"));
791 list.setCellRenderer(tmp);
792 }
793
794 Color sbg = list.getSelectionBackground();
795 if (sbg == null || sbg instanceof UIResource) {
796 list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
797 }
798
799 Color sfg = list.getSelectionForeground();
800 if (sfg == null || sfg instanceof UIResource) {
801 list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
802 }
803
804 Long l = (Long)UIManager.get("List.timeFactor");
805 timeFactor = (l!=null) ? l.longValue() : 1000L;
806
807 updateIsFileList();
808 }
809
810 private void updateIsFileList() {
811 boolean b = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
852 if (list.getCellRenderer() instanceof UIResource) {
853 list.setCellRenderer(null);
854 }
855 if (list.getTransferHandler() instanceof UIResource) {
856 list.setTransferHandler(null);
857 }
858 }
859
860
861 /**
862 * Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
863 * <code>installListeners()</code>, and <code>installKeyboardActions()</code>
864 * in order.
865 *
866 * @see #installDefaults
867 * @see #installListeners
868 * @see #installKeyboardActions
869 */
870 public void installUI(JComponent c)
871 {
872 @SuppressWarnings("unchecked")
873 JList<Object> tmp = (JList)c;
874 list = tmp;
875
876 layoutOrientation = list.getLayoutOrientation();
877
878 rendererPane = new CellRendererPane();
879 list.add(rendererPane);
880
881 columnCount = 1;
882
883 updateLayoutStateNeeded = modelChanged;
884 isLeftToRight = list.getComponentOrientation().isLeftToRight();
885
886 installDefaults();
887 installListeners();
888 installKeyboardActions();
889 }
890
891
892 /**
893 * Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
894 * <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
913 rendererPane = null;
914 list = null;
915 }
916
917
918 /**
919 * Returns a new instance of BasicListUI. BasicListUI delegates are
920 * allocated one per JList.
921 *
922 * @return A new ListUI implementation for the Windows look and feel.
923 */
924 public static ComponentUI createUI(JComponent list) {
925 return new BasicListUI();
926 }
927
928
929 /**
930 * {@inheritDoc}
931 * @throws NullPointerException {@inheritDoc}
932 */
933 public int locationToIndex(JList<?> list, Point location) {
934 maybeUpdateLayoutState();
935 return convertLocationToModel(location.x, location.y);
936 }
937
938
939 /**
940 * {@inheritDoc}
941 */
942 public Point indexToLocation(JList<?> list, int index) {
943 maybeUpdateLayoutState();
944 Rectangle rect = getCellBounds(list, index, index);
945
946 if (rect != null) {
947 return new Point(rect.x, rect.y);
948 }
949 return null;
950 }
951
952
953 /**
954 * {@inheritDoc}
955 */
956 public Rectangle getCellBounds(JList<?> list, int index1, int index2) {
957 maybeUpdateLayoutState();
958
959 int minIndex = Math.min(index1, index2);
960 int maxIndex = Math.max(index1, index2);
961
962 if (minIndex >= list.getModel().getSize()) {
963 return null;
964 }
965
966 Rectangle minBounds = getCellBounds(list, minIndex);
967
968 if (minBounds == null) {
969 return null;
970 }
971 if (minIndex == maxIndex) {
972 return minBounds;
973 }
974 Rectangle maxBounds = getCellBounds(list, maxIndex);
975
976 if (maxBounds != null) {
980
981 if (minRow != maxRow) {
982 minBounds.x = 0;
983 minBounds.width = list.getWidth();
984 }
985 }
986 else if (minBounds.x != maxBounds.x) {
987 // Different columns
988 minBounds.y = 0;
989 minBounds.height = list.getHeight();
990 }
991 minBounds.add(maxBounds);
992 }
993 return minBounds;
994 }
995
996 /**
997 * Gets the bounds of the specified model index, returning the resulting
998 * bounds, or null if <code>index</code> is not valid.
999 */
1000 private Rectangle getCellBounds(JList<?> list, int index) {
1001 maybeUpdateLayoutState();
1002
1003 int row = convertModelToRow(index);
1004 int column = convertModelToColumn(index);
1005
1006 if (row == -1 || column == -1) {
1007 return null;
1008 }
1009
1010 Insets insets = list.getInsets();
1011 int x;
1012 int w = cellWidth;
1013 int y = insets.top;
1014 int h;
1015 switch (layoutOrientation) {
1016 case JList.VERTICAL_WRAP:
1017 case JList.HORIZONTAL_WRAP:
1018 if (isLeftToRight) {
1019 x = insets.left + column * cellWidth;
1020 } else {
1339 cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
1340
1341 if (fixedCellHeight != -1) {
1342 cellHeight = fixedCellHeight;
1343 cellHeights = null;
1344 }
1345 else {
1346 cellHeight = -1;
1347 cellHeights = new int[list.getModel().getSize()];
1348 }
1349
1350 /* If either of JList fixedCellWidth and fixedCellHeight haven't
1351 * been set, then initialize cellWidth and cellHeights by
1352 * scanning through the entire model. Note: if the renderer is
1353 * null, we just set cellWidth and cellHeights[*] to zero,
1354 * if they're not set already.
1355 */
1356
1357 if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
1358
1359 ListModel<Object> dataModel = list.getModel();
1360 int dataModelSize = dataModel.getSize();
1361 ListCellRenderer<Object> renderer = list.getCellRenderer();
1362
1363 if (renderer != null) {
1364 for(int index = 0; index < dataModelSize; index++) {
1365 Object value = dataModel.getElementAt(index);
1366 Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
1367 rendererPane.add(c);
1368 Dimension cellSize = c.getPreferredSize();
1369 if (fixedCellWidth == -1) {
1370 cellWidth = Math.max(cellSize.width, cellWidth);
1371 }
1372 if (fixedCellHeight == -1) {
1373 cellHeights[index] = cellSize.height;
1374 }
1375 }
1376 }
1377 else {
1378 if (cellWidth == -1) {
1379 cellWidth = 0;
1380 }
1381 if (cellHeights == null) {
1826 private static final String SELECT_ALL = "selectAll";
1827 private static final String CLEAR_SELECTION = "clearSelection";
1828
1829 // add the lead item to the selection without changing lead or anchor
1830 private static final String ADD_TO_SELECTION = "addToSelection";
1831
1832 // toggle the selected state of the lead item and move the anchor to it
1833 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
1834
1835 // extend the selection to the lead item
1836 private static final String EXTEND_TO = "extendTo";
1837
1838 // move the anchor to the lead and ensure only that item is selected
1839 private static final String MOVE_SELECTION_TO = "moveSelectionTo";
1840
1841 Actions(String name) {
1842 super(name);
1843 }
1844 public void actionPerformed(ActionEvent e) {
1845 String name = getName();
1846 @SuppressWarnings("unchecked")
1847 JList<Object> list = (JList)e.getSource();
1848 BasicListUI ui = (BasicListUI)BasicLookAndFeel.getUIOfType(
1849 list.getUI(), BasicListUI.class);
1850
1851 if (name == SELECT_PREVIOUS_COLUMN) {
1852 changeSelection(list, CHANGE_SELECTION,
1853 getNextColumnIndex(list, ui, -1), -1);
1854 }
1855 else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
1856 changeSelection(list, EXTEND_SELECTION,
1857 getNextColumnIndex(list, ui, -1), -1);
1858 }
1859 else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
1860 changeSelection(list, CHANGE_LEAD,
1861 getNextColumnIndex(list, ui, -1), -1);
1862 }
1863 else if (name == SELECT_NEXT_COLUMN) {
1864 changeSelection(list, CHANGE_SELECTION,
1865 getNextColumnIndex(list, ui, 1), 1);
1866 }
1867 else if (name == SELECT_NEXT_COLUMN_EXTEND) {
1986 public boolean isEnabled(Object c) {
1987 Object name = getName();
1988 if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD ||
1989 name == SELECT_NEXT_COLUMN_CHANGE_LEAD ||
1990 name == SELECT_PREVIOUS_ROW_CHANGE_LEAD ||
1991 name == SELECT_NEXT_ROW_CHANGE_LEAD ||
1992 name == SELECT_FIRST_ROW_CHANGE_LEAD ||
1993 name == SELECT_LAST_ROW_CHANGE_LEAD ||
1994 name == SCROLL_UP_CHANGE_LEAD ||
1995 name == SCROLL_DOWN_CHANGE_LEAD) {
1996
1997 // discontinuous selection actions are only enabled for
1998 // DefaultListSelectionModel
1999 return c != null && ((JList)c).getSelectionModel()
2000 instanceof DefaultListSelectionModel;
2001 }
2002
2003 return true;
2004 }
2005
2006 private void clearSelection(JList<?> list) {
2007 list.clearSelection();
2008 }
2009
2010 private void selectAll(JList<?> list) {
2011 int size = list.getModel().getSize();
2012 if (size > 0) {
2013 ListSelectionModel lsm = list.getSelectionModel();
2014 int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2015
2016 if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
2017 if (lead == -1) {
2018 int min = adjustIndex(list.getMinSelectionIndex(), list);
2019 lead = (min == -1 ? 0 : min);
2020 }
2021
2022 list.setSelectionInterval(lead, lead);
2023 list.ensureIndexIsVisible(lead);
2024 } else {
2025 list.setValueIsAdjusting(true);
2026
2027 int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2028
2029 list.setSelectionInterval(0, size - 1);
2030
2031 // this is done to restore the anchor and lead
2032 SwingUtilities2.setLeadAnchorWithoutSelection(lsm, anchor, lead);
2033
2034 list.setValueIsAdjusting(false);
2035 }
2036 }
2037 }
2038
2039 private int getNextPageIndex(JList<?> list, int direction) {
2040 if (list.getModel().getSize() == 0) {
2041 return -1;
2042 }
2043
2044 int index = -1;
2045 Rectangle visRect = list.getVisibleRect();
2046 ListSelectionModel lsm = list.getSelectionModel();
2047 int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2048 Rectangle leadRect =
2049 (lead==-1) ? new Rectangle() : list.getCellBounds(lead, lead);
2050
2051 if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2052 list.getVisibleRowCount() <= 0) {
2053 if (!list.getComponentOrientation().isLeftToRight()) {
2054 direction = -direction;
2055 }
2056 // apply for horizontal scrolling: the step for next
2057 // page index is number of visible columns
2058 if (direction < 0) {
2059 // left
2144 // go one cell up if last visible cell doesn't fit
2145 // into adjasted visible rectangle
2146 if (cellBounds.y + cellBounds.height >
2147 visRect.y + visRect.height) {
2148 p.y = cellBounds.y - 1;
2149 index = list.locationToIndex(p);
2150 cellBounds = list.getCellBounds(index, index);
2151 }
2152 // if index isn't greater then lead
2153 // try to go to cell next after lead
2154 if (cellBounds.y <= leadRect.y) {
2155 p.y = leadRect.y + leadRect.height;
2156 index = list.locationToIndex(p);
2157 }
2158 }
2159 }
2160 }
2161 return index;
2162 }
2163
2164 private void changeSelection(JList<?> list, int type,
2165 int index, int direction) {
2166 if (index >= 0 && index < list.getModel().getSize()) {
2167 ListSelectionModel lsm = list.getSelectionModel();
2168
2169 // CHANGE_LEAD is only valid with multiple interval selection
2170 if (type == CHANGE_LEAD &&
2171 list.getSelectionMode()
2172 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
2173
2174 type = CHANGE_SELECTION;
2175 }
2176
2177 // IMPORTANT - This needs to happen before the index is changed.
2178 // This is because JFileChooser, which uses JList, also scrolls
2179 // the selected item into view. If that happens first, then
2180 // this method becomes a no-op.
2181 adjustScrollPositionIfNecessary(list, index, direction);
2182
2183 if (type == EXTEND_SELECTION) {
2184 int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2187 }
2188
2189 list.setSelectionInterval(anchor, index);
2190 }
2191 else if (type == CHANGE_SELECTION) {
2192 list.setSelectedIndex(index);
2193 }
2194 else {
2195 // casting should be safe since the action is only enabled
2196 // for DefaultListSelectionModel
2197 ((DefaultListSelectionModel)lsm).moveLeadSelectionIndex(index);
2198 }
2199 }
2200 }
2201
2202 /**
2203 * When scroll down makes selected index the last completely visible
2204 * index. When scroll up makes selected index the first visible index.
2205 * Adjust visible rectangle respect to list's component orientation.
2206 */
2207 private void adjustScrollPositionIfNecessary(JList<?> list, int index,
2208 int direction) {
2209 if (direction == 0) {
2210 return;
2211 }
2212 Rectangle cellBounds = list.getCellBounds(index, index);
2213 Rectangle visRect = list.getVisibleRect();
2214 if (cellBounds != null && !visRect.contains(cellBounds)) {
2215 if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2216 list.getVisibleRowCount() <= 0) {
2217 // horizontal
2218 if (list.getComponentOrientation().isLeftToRight()) {
2219 if (direction > 0) {
2220 // right for left-to-right
2221 int x =Math.max(0,
2222 cellBounds.x + cellBounds.width - visRect.width);
2223 int startIndex =
2224 list.locationToIndex(new Point(x, cellBounds.y));
2225 Rectangle startRect = list.getCellBounds(startIndex,
2226 startIndex);
2227 if (startRect.x < x && startRect.x < cellBounds.x) {
2275 startIndex);
2276 if (startRect.y < y && startRect.y < cellBounds.y) {
2277 startRect.y += startRect.height;
2278 startIndex =
2279 list.locationToIndex(startRect.getLocation());
2280 startRect =
2281 list.getCellBounds(startIndex, startIndex);
2282 }
2283 cellBounds = startRect;
2284 cellBounds.height = visRect.height;
2285 }
2286 else {
2287 // adjust height to fit into visible rectangle
2288 cellBounds.height = Math.min(cellBounds.height, visRect.height);
2289 }
2290 }
2291 list.scrollRectToVisible(cellBounds);
2292 }
2293 }
2294
2295 private int getNextColumnIndex(JList<?> list, BasicListUI ui,
2296 int amount) {
2297 if (list.getLayoutOrientation() != JList.VERTICAL) {
2298 int index = adjustIndex(list.getLeadSelectionIndex(), list);
2299 int size = list.getModel().getSize();
2300
2301 if (index == -1) {
2302 return 0;
2303 } else if (size == 1) {
2304 // there's only one item so we should select it
2305 return 0;
2306 } else if (ui == null || ui.columnCount <= 1) {
2307 return -1;
2308 }
2309
2310 int column = ui.convertModelToColumn(index);
2311 int row = ui.convertModelToRow(index);
2312
2313 column += amount;
2314 if (column >= ui.columnCount || column < 0) {
2315 // No wrapping.
2316 return -1;
2317 }
2318 int maxRowCount = ui.getRowCount(column);
2319 if (row >= maxRowCount) {
2320 return -1;
2321 }
2322 return ui.getModelIndex(column, row);
2323 }
2324 // Won't change the selection.
2325 return -1;
2326 }
2327
2328 private int getNextIndex(JList<?> list, BasicListUI ui, int amount) {
2329 int index = adjustIndex(list.getLeadSelectionIndex(), list);
2330 int size = list.getModel().getSize();
2331
2332 if (index == -1) {
2333 if (size > 0) {
2334 if (amount > 0) {
2335 index = 0;
2336 }
2337 else {
2338 index = size - 1;
2339 }
2340 }
2341 } else if (size == 1) {
2342 // there's only one item so we should select it
2343 index = 0;
2344 } else if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
2345 if (ui != null) {
2346 index += ui.columnCount * amount;
2347 }
2348 } else {
2360 BeforeDrag {
2361 //
2362 // KeyListener
2363 //
2364 private String prefix = "";
2365 private String typedString = "";
2366 private long lastTime = 0L;
2367
2368 /**
2369 * Invoked when a key has been typed.
2370 *
2371 * Moves the keyboard focus to the first element whose prefix matches the
2372 * sequence of alphanumeric keys pressed by the user with delay less
2373 * than value of <code>timeFactor</code> property (or 1000 milliseconds
2374 * if it is not defined). Subsequent same key presses move the keyboard
2375 * focus to the next object that starts with the same letter until another
2376 * key is pressed, then it is treated as the prefix with appropriate number
2377 * of the same letters followed by first typed another letter.
2378 */
2379 public void keyTyped(KeyEvent e) {
2380 JList<?> src = (JList)e.getSource();
2381 ListModel<?> model = src.getModel();
2382
2383 if (model.getSize() == 0 || e.isAltDown() ||
2384 BasicGraphicsUtils.isMenuShortcutKeyDown(e) ||
2385 isNavigationKey(e)) {
2386 // Nothing to select
2387 return;
2388 }
2389 boolean startingFromSelection = true;
2390
2391 char c = e.getKeyChar();
2392
2393 long time = e.getWhen();
2394 int startIndex = adjustIndex(src.getLeadSelectionIndex(), list);
2395 if (time - lastTime < timeFactor) {
2396 typedString += c;
2397 if((prefix.length() == 1) && (c == prefix.charAt(0))) {
2398 // Subsequent same key presses move the keyboard focus to the next
2399 // object that starts with the same letter.
2400 startIndex++;
2401 } else {
2457 private boolean isNavigationKey(KeyEvent event) {
2458 InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
2459 KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
2460
2461 if (inputMap != null && inputMap.get(key) != null) {
2462 return true;
2463 }
2464 return false;
2465 }
2466
2467 //
2468 // PropertyChangeListener
2469 //
2470 public void propertyChange(PropertyChangeEvent e) {
2471 String propertyName = e.getPropertyName();
2472
2473 /* If the JList.model property changes, remove our listener,
2474 * listDataListener from the old model and add it to the new one.
2475 */
2476 if (propertyName == "model") {
2477 @SuppressWarnings("unchecked")
2478 ListModel<?> oldModel = (ListModel)e.getOldValue();
2479 @SuppressWarnings("unchecked")
2480 ListModel<?> newModel = (ListModel)e.getNewValue();
2481 if (oldModel != null) {
2482 oldModel.removeListDataListener(listDataListener);
2483 }
2484 if (newModel != null) {
2485 newModel.addListDataListener(listDataListener);
2486 }
2487 updateLayoutStateNeeded |= modelChanged;
2488 redrawList();
2489 }
2490
2491 /* If the JList.selectionModel property changes, remove our listener,
2492 * listSelectionListener from the old selectionModel and add it to the new one.
2493 */
2494 else if (propertyName == "selectionModel") {
2495 ListSelectionModel oldModel = (ListSelectionModel)e.getOldValue();
2496 ListSelectionModel newModel = (ListSelectionModel)e.getNewValue();
2497 if (oldModel != null) {
2498 oldModel.removeListSelectionListener(listSelectionListener);
2499 }
2500 if (newModel != null) {
2819 Rectangle r = getCellBounds(list, leadIndex, leadIndex);
2820 if (r != null) {
2821 list.repaint(r.x, r.y, r.width, r.height);
2822 }
2823 }
2824 }
2825
2826 /* The focusGained() focusLost() methods run when the JList
2827 * focus changes.
2828 */
2829
2830 public void focusGained(FocusEvent e) {
2831 repaintCellFocus();
2832 }
2833
2834 public void focusLost(FocusEvent e) {
2835 repaintCellFocus();
2836 }
2837 }
2838
2839 private static int adjustIndex(int index, JList<?> list) {
2840 return index < list.getModel().getSize() ? index : -1;
2841 }
2842
2843 private static final TransferHandler defaultTransferHandler = new ListTransferHandler();
2844
2845 @SuppressWarnings("serial") // Superclass is a JDK-implementation class
2846 static class ListTransferHandler extends TransferHandler implements UIResource {
2847
2848 /**
2849 * Create a Transferable to use as the source for a data transfer.
2850 *
2851 * @param c The component holding the data to be transfered. This
2852 * argument is provided to enable sharing of TransferHandlers by
2853 * multiple components.
2854 * @return The representation of the data to be transfered.
2855 *
2856 */
2857 protected Transferable createTransferable(JComponent c) {
2858 if (c instanceof JList) {
2859 JList<?> list = (JList) c;
2860 Object[] values = list.getSelectedValues();
2861
2862 if (values == null || values.length == 0) {
2863 return null;
2864 }
2865
2866 StringBuilder plainStr = new StringBuilder();
2867 StringBuilder htmlStr = new StringBuilder();
2868
2869 htmlStr.append("<html>\n<body>\n<ul>\n");
2870
2871 for (int i = 0; i < values.length; i++) {
2872 Object obj = values[i];
2873 String val = ((obj == null) ? "" : obj.toString());
2874 plainStr.append(val + "\n");
2875 htmlStr.append(" <li>" + val + "\n");
2876 }
2877
2878 // remove the last newline
2879 plainStr.deleteCharAt(plainStr.length() - 1);
|