1. /*
  2. * @(#)BasicListUI.java 1.104 04/03/30
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import sun.swing.DefaultLookup;
  9. import sun.swing.UIAction;
  10. import javax.swing.*;
  11. import javax.swing.event.*;
  12. import javax.swing.plaf.*;
  13. import javax.swing.text.Position;
  14. import java.awt.*;
  15. import java.awt.event.*;
  16. import java.awt.datatransfer.Transferable;
  17. import java.awt.dnd.*;
  18. import java.util.ArrayList;
  19. import java.util.TooManyListenersException;
  20. import java.beans.PropertyChangeListener;
  21. import java.beans.PropertyChangeEvent;
  22. import com.sun.java.swing.SwingUtilities2;
  23. /**
  24. * A Windows L&F implementation of ListUI.
  25. * <p>
  26. *
  27. * @version 1.104 03/30/04
  28. * @author Hans Muller
  29. * @author Philip Milne
  30. */
  31. public class BasicListUI extends ListUI
  32. {
  33. protected JList list = null;
  34. protected CellRendererPane rendererPane;
  35. // Listeners that this UI attaches to the JList
  36. protected FocusListener focusListener;
  37. protected MouseInputListener mouseInputListener;
  38. protected ListSelectionListener listSelectionListener;
  39. protected ListDataListener listDataListener;
  40. protected PropertyChangeListener propertyChangeListener;
  41. private Handler handler;
  42. protected int[] cellHeights = null;
  43. protected int cellHeight = -1;
  44. protected int cellWidth = -1;
  45. protected int updateLayoutStateNeeded = modelChanged;
  46. /**
  47. * Height of the list. When asked to paint, if the current size of
  48. * the list differs, this will update the layout state.
  49. */
  50. private int listHeight;
  51. /**
  52. * Width of the list. When asked to paint, if the current size of
  53. * the list differs, this will update the layout state.
  54. */
  55. private int listWidth;
  56. /**
  57. * The layout orientation of the list.
  58. */
  59. private int layoutOrientation;
  60. // Following ivars are used if the list is laying out horizontally
  61. /**
  62. * Number of columns to create.
  63. */
  64. private int columnCount;
  65. /**
  66. * Preferred height to make the list, this is only used if the
  67. * the list is layed out horizontally.
  68. */
  69. private int preferredHeight;
  70. /**
  71. * Number of rows per column. This is only used if the row height is
  72. * fixed.
  73. */
  74. private int rowsPerColumn;
  75. /**
  76. * The time factor to treate the series of typed alphanumeric key
  77. * as prefix for first letter navigation.
  78. */
  79. private long timeFactor = 1000L;
  80. /**
  81. * Local cache of JList's client property "List.isFileList"
  82. */
  83. private boolean isFileList = false;
  84. /**
  85. * Local cache of JList's component orientation property
  86. */
  87. private boolean isLeftToRight = true;
  88. /* The bits below define JList property changes that affect layout.
  89. * When one of these properties changes we set a bit in
  90. * updateLayoutStateNeeded. The change is dealt with lazily, see
  91. * maybeUpdateLayoutState. Changes to the JLists model, e.g. the
  92. * models length changed, are handled similarly, see DataListener.
  93. */
  94. protected final static int modelChanged = 1 << 0;
  95. protected final static int selectionModelChanged = 1 << 1;
  96. protected final static int fontChanged = 1 << 2;
  97. protected final static int fixedCellWidthChanged = 1 << 3;
  98. protected final static int fixedCellHeightChanged = 1 << 4;
  99. protected final static int prototypeCellValueChanged = 1 << 5;
  100. protected final static int cellRendererChanged = 1 << 6;
  101. private final static int layoutOrientationChanged = 1 << 7;
  102. private final static int heightChanged = 1 << 8;
  103. private final static int widthChanged = 1 << 9;
  104. private final static int componentOrientationChanged = 1 << 10;
  105. static void loadActionMap(LazyActionMap map) {
  106. map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN));
  107. map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_EXTEND));
  108. map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD));
  109. map.put(new Actions(Actions.SELECT_NEXT_COLUMN));
  110. map.put(new Actions(Actions.SELECT_NEXT_COLUMN_EXTEND));
  111. map.put(new Actions(Actions.SELECT_NEXT_COLUMN_CHANGE_LEAD));
  112. map.put(new Actions(Actions.SELECT_PREVIOUS_ROW));
  113. map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_EXTEND));
  114. map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_CHANGE_LEAD));
  115. map.put(new Actions(Actions.SELECT_NEXT_ROW));
  116. map.put(new Actions(Actions.SELECT_NEXT_ROW_EXTEND));
  117. map.put(new Actions(Actions.SELECT_NEXT_ROW_CHANGE_LEAD));
  118. map.put(new Actions(Actions.SELECT_FIRST_ROW));
  119. map.put(new Actions(Actions.SELECT_FIRST_ROW_EXTEND));
  120. map.put(new Actions(Actions.SELECT_FIRST_ROW_CHANGE_LEAD));
  121. map.put(new Actions(Actions.SELECT_LAST_ROW));
  122. map.put(new Actions(Actions.SELECT_LAST_ROW_EXTEND));
  123. map.put(new Actions(Actions.SELECT_LAST_ROW_CHANGE_LEAD));
  124. map.put(new Actions(Actions.SCROLL_UP));
  125. map.put(new Actions(Actions.SCROLL_UP_EXTEND));
  126. map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
  127. map.put(new Actions(Actions.SCROLL_DOWN));
  128. map.put(new Actions(Actions.SCROLL_DOWN_EXTEND));
  129. map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
  130. map.put(new Actions(Actions.SELECT_ALL));
  131. map.put(new Actions(Actions.CLEAR_SELECTION));
  132. map.put(new Actions(Actions.ADD_TO_SELECTION));
  133. map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
  134. map.put(new Actions(Actions.EXTEND_TO));
  135. map.put(new Actions(Actions.MOVE_SELECTION_TO));
  136. map.put(TransferHandler.getCutAction().getValue(Action.NAME),
  137. TransferHandler.getCutAction());
  138. map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
  139. TransferHandler.getCopyAction());
  140. map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
  141. TransferHandler.getPasteAction());
  142. }
  143. /**
  144. * Paint one List cell: compute the relevant state, get the "rubber stamp"
  145. * cell renderer component, and then use the CellRendererPane to paint it.
  146. * Subclasses may want to override this method rather than paint().
  147. *
  148. * @see #paint
  149. */
  150. protected void paintCell(
  151. Graphics g,
  152. int row,
  153. Rectangle rowBounds,
  154. ListCellRenderer cellRenderer,
  155. ListModel dataModel,
  156. ListSelectionModel selModel,
  157. int leadIndex)
  158. {
  159. Object value = dataModel.getElementAt(row);
  160. boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
  161. boolean isSelected = selModel.isSelectedIndex(row);
  162. Component rendererComponent =
  163. cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
  164. int cx = rowBounds.x;
  165. int cy = rowBounds.y;
  166. int cw = rowBounds.width;
  167. int ch = rowBounds.height;
  168. if (isFileList) {
  169. // Shrink renderer to preferred size. This is mostly used on Windows
  170. // where selection is only shown around the file name, instead of
  171. // across the whole list cell.
  172. int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
  173. if (!isLeftToRight) {
  174. cx += (cw - w);
  175. }
  176. cw = w;
  177. }
  178. rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
  179. }
  180. /**
  181. * Paint the rows that intersect the Graphics objects clipRect. This
  182. * method calls paintCell as necessary. Subclasses
  183. * may want to override these methods.
  184. *
  185. * @see #paintCell
  186. */
  187. public void paint(Graphics g, JComponent c)
  188. {
  189. switch (layoutOrientation) {
  190. case JList.VERTICAL_WRAP:
  191. if (list.getHeight() != listHeight) {
  192. updateLayoutStateNeeded |= heightChanged;
  193. redrawList();
  194. }
  195. break;
  196. case JList.HORIZONTAL_WRAP:
  197. if (list.getWidth() != listWidth) {
  198. updateLayoutStateNeeded |= widthChanged;
  199. redrawList();
  200. }
  201. break;
  202. default:
  203. break;
  204. }
  205. maybeUpdateLayoutState();
  206. ListCellRenderer renderer = list.getCellRenderer();
  207. ListModel dataModel = list.getModel();
  208. ListSelectionModel selModel = list.getSelectionModel();
  209. int size;
  210. if ((renderer == null) || (size = dataModel.getSize()) == 0) {
  211. return;
  212. }
  213. // Determine how many columns we need to paint
  214. Rectangle paintBounds = g.getClipBounds();
  215. int startColumn, endColumn;
  216. if (c.getComponentOrientation().isLeftToRight()) {
  217. startColumn = convertLocationToColumn(paintBounds.x,
  218. paintBounds.y);
  219. endColumn = convertLocationToColumn(paintBounds.x +
  220. paintBounds.width,
  221. paintBounds.y);
  222. } else {
  223. startColumn = convertLocationToColumn(paintBounds.x +
  224. paintBounds.width,
  225. paintBounds.y);
  226. endColumn = convertLocationToColumn(paintBounds.x,
  227. paintBounds.y);
  228. }
  229. int maxY = paintBounds.y + paintBounds.height;
  230. int leadIndex = list.getLeadSelectionIndex();
  231. int rowIncrement = (layoutOrientation == JList.HORIZONTAL_WRAP) ?
  232. columnCount : 1;
  233. for (int colCounter = startColumn; colCounter <= endColumn;
  234. colCounter++) {
  235. // And then how many rows in this columnn
  236. int row = convertLocationToRowInColumn(paintBounds.y, colCounter);
  237. int rowCount = getRowCount(colCounter);
  238. int index = getModelIndex(colCounter, row);
  239. Rectangle rowBounds = getCellBounds(list, index, index);
  240. if (rowBounds == null) {
  241. // Not valid, bail!
  242. return;
  243. }
  244. while (row < rowCount && rowBounds.y < maxY &&
  245. index < size) {
  246. rowBounds.height = getHeight(colCounter, row);
  247. g.setClip(rowBounds.x, rowBounds.y, rowBounds.width,
  248. rowBounds.height);
  249. g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width,
  250. paintBounds.height);
  251. paintCell(g, index, rowBounds, renderer, dataModel, selModel,
  252. leadIndex);
  253. rowBounds.y += rowBounds.height;
  254. index += rowIncrement;
  255. row++;
  256. }
  257. }
  258. }
  259. /**
  260. * The preferredSize of the list depends upon the layout orientation.
  261. * <table summary="Describes the preferred size for each layout orientation">
  262. * <tr><th>Layout Orientation</th><th>Preferred Size</th></tr>
  263. * <tr>
  264. * <td>JList.VERTICAL
  265. * <td>The preferredSize of the list is total height of the rows
  266. * and the maximum width of the cells. If JList.fixedCellHeight
  267. * is specified then the total height of the rows is just
  268. * (cellVerticalMargins + fixedCellHeight) * model.getSize() where
  269. * rowVerticalMargins is the space we allocate for drawing
  270. * the yellow focus outline. Similarly if fixedCellWidth is
  271. * specified then we just use that.
  272. * </td>
  273. * <tr>
  274. * <td>JList.VERTICAL_WRAP
  275. * <td>If the visible row count is greater than zero, the preferredHeight
  276. * is the maximum cell height * visibleRowCount. If the visible row
  277. * count is <= 0, the preferred height is either the current height
  278. * of the list, or the maximum cell height, whichever is
  279. * bigger. The preferred width is than the maximum cell width *
  280. * number of columns needed. Where the number of columns needs is
  281. * list.height / max cell height. Max cell height is either the fixed
  282. * cell height, or is determined by iterating through all the cells
  283. * to find the maximum height from the ListCellRenderer.
  284. * <tr>
  285. * <td>JList.HORIZONTAL_WRAP
  286. * <td>If the visible row count is greater than zero, the preferredHeight
  287. * is the maximum cell height * adjustedRowCount. Where
  288. * visibleRowCount is used to determine the number of columns.
  289. * Because this lays out horizontally the number of rows is
  290. * then determined from the column count. For example, lets say
  291. * you have a model with 10 items and the visible row count is 8.
  292. * The number of columns needed to display this is 2, but you no
  293. * longer need 8 rows to display this, you only need 5, thus
  294. * the adjustedRowCount is 5.
  295. * <p>If the visible row
  296. * count is <= 0, the preferred height is dictated by the
  297. * number of columns, which will be as many as can fit in the width
  298. * of the <code>JList</code> (width / max cell width), with at
  299. * least one column. The preferred height then becomes the
  300. * model size / number of columns * maximum cell height.
  301. * Max cell height is either the fixed
  302. * cell height, or is determined by iterating through all the cells
  303. * to find the maximum height from the ListCellRenderer.
  304. * </table>
  305. * The above specifies the raw preferred width and height. The resulting
  306. * preferred width is the above width + insets.left + insets.right and
  307. * the resulting preferred height is the above height + insets.top +
  308. * insets.bottom. Where the <code>Insets</code> are determined from
  309. * <code>list.getInsets()</code>.
  310. *
  311. * @param c The JList component.
  312. * @return The total size of the list.
  313. */
  314. public Dimension getPreferredSize(JComponent c) {
  315. maybeUpdateLayoutState();
  316. int lastRow = list.getModel().getSize() - 1;
  317. if (lastRow < 0) {
  318. return new Dimension(0, 0);
  319. }
  320. Insets insets = list.getInsets();
  321. int width = cellWidth * columnCount + insets.left + insets.right;
  322. int height;
  323. if (layoutOrientation != JList.VERTICAL) {
  324. height = preferredHeight;
  325. }
  326. else {
  327. Rectangle bounds = getCellBounds(list, lastRow);
  328. if (bounds != null) {
  329. height = bounds.y + bounds.height + insets.bottom;
  330. }
  331. else {
  332. height = 0;
  333. }
  334. }
  335. return new Dimension(width, height);
  336. }
  337. /**
  338. * Selected the previous row and force it to be visible.
  339. *
  340. * @see JList#ensureIndexIsVisible
  341. */
  342. protected void selectPreviousIndex() {
  343. int s = list.getSelectedIndex();
  344. if(s > 0) {
  345. s -= 1;
  346. list.setSelectedIndex(s);
  347. list.ensureIndexIsVisible(s);
  348. }
  349. }
  350. /**
  351. * Selected the previous row and force it to be visible.
  352. *
  353. * @see JList#ensureIndexIsVisible
  354. */
  355. protected void selectNextIndex()
  356. {
  357. int s = list.getSelectedIndex();
  358. if((s + 1) < list.getModel().getSize()) {
  359. s += 1;
  360. list.setSelectedIndex(s);
  361. list.ensureIndexIsVisible(s);
  362. }
  363. }
  364. /**
  365. * Registers the keyboard bindings on the <code>JList</code> that the
  366. * <code>BasicListUI</code> is associated with. This method is called at
  367. * installUI() time.
  368. *
  369. * @see #installUI
  370. */
  371. protected void installKeyboardActions() {
  372. InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
  373. SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
  374. inputMap);
  375. LazyActionMap.installLazyActionMap(list, BasicListUI.class,
  376. "List.actionMap");
  377. }
  378. InputMap getInputMap(int condition) {
  379. if (condition == JComponent.WHEN_FOCUSED) {
  380. InputMap keyMap = (InputMap)DefaultLookup.get(
  381. list, this, "List.focusInputMap");
  382. InputMap rtlKeyMap;
  383. if (isLeftToRight ||
  384. ((rtlKeyMap = (InputMap)DefaultLookup.get(list, this,
  385. "List.focusInputMap.RightToLeft")) == null)) {
  386. return keyMap;
  387. } else {
  388. rtlKeyMap.setParent(keyMap);
  389. return rtlKeyMap;
  390. }
  391. }
  392. return null;
  393. }
  394. /**
  395. * Unregisters keyboard actions installed from
  396. * <code>installKeyboardActions</code>.
  397. * This method is called at uninstallUI() time - subclassess should
  398. * ensure that all of the keyboard actions registered at installUI
  399. * time are removed here.
  400. *
  401. * @see #installUI
  402. */
  403. protected void uninstallKeyboardActions() {
  404. SwingUtilities.replaceUIActionMap(list, null);
  405. SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null);
  406. }
  407. /**
  408. * Create and install the listeners for the JList, its model, and its
  409. * selectionModel. This method is called at installUI() time.
  410. *
  411. * @see #installUI
  412. * @see #uninstallListeners
  413. */
  414. protected void installListeners()
  415. {
  416. TransferHandler th = list.getTransferHandler();
  417. if (th == null || th instanceof UIResource) {
  418. list.setTransferHandler(defaultTransferHandler);
  419. }
  420. DropTarget dropTarget = list.getDropTarget();
  421. if (dropTarget instanceof UIResource) {
  422. try {
  423. dropTarget.addDropTargetListener(new ListDropTargetListener());
  424. } catch (TooManyListenersException tmle) {
  425. // should not happen... swing drop target is multicast
  426. }
  427. }
  428. focusListener = createFocusListener();
  429. mouseInputListener = createMouseInputListener();
  430. propertyChangeListener = createPropertyChangeListener();
  431. listSelectionListener = createListSelectionListener();
  432. listDataListener = createListDataListener();
  433. list.addFocusListener(focusListener);
  434. list.addMouseListener(defaultDragRecognizer);
  435. list.addMouseMotionListener(defaultDragRecognizer);
  436. list.addMouseListener(mouseInputListener);
  437. list.addMouseMotionListener(mouseInputListener);
  438. list.addPropertyChangeListener(propertyChangeListener);
  439. list.addKeyListener(getHandler());
  440. ListModel model = list.getModel();
  441. if (model != null) {
  442. model.addListDataListener(listDataListener);
  443. }
  444. ListSelectionModel selectionModel = list.getSelectionModel();
  445. if (selectionModel != null) {
  446. selectionModel.addListSelectionListener(listSelectionListener);
  447. }
  448. }
  449. /**
  450. * Remove the listeners for the JList, its model, and its
  451. * selectionModel. All of the listener fields, are reset to
  452. * null here. This method is called at uninstallUI() time,
  453. * it should be kept in sync with installListeners.
  454. *
  455. * @see #uninstallUI
  456. * @see #installListeners
  457. */
  458. protected void uninstallListeners()
  459. {
  460. list.removeFocusListener(focusListener);
  461. list.removeMouseListener(defaultDragRecognizer);
  462. list.removeMouseMotionListener(defaultDragRecognizer);
  463. list.removeMouseListener(mouseInputListener);
  464. list.removeMouseMotionListener(mouseInputListener);
  465. list.removePropertyChangeListener(propertyChangeListener);
  466. list.removeKeyListener(getHandler());
  467. ListModel model = list.getModel();
  468. if (model != null) {
  469. model.removeListDataListener(listDataListener);
  470. }
  471. ListSelectionModel selectionModel = list.getSelectionModel();
  472. if (selectionModel != null) {
  473. selectionModel.removeListSelectionListener(listSelectionListener);
  474. }
  475. focusListener = null;
  476. mouseInputListener = null;
  477. listSelectionListener = null;
  478. listDataListener = null;
  479. propertyChangeListener = null;
  480. handler = null;
  481. }
  482. /**
  483. * Initialize JList properties, e.g. font, foreground, and background,
  484. * and add the CellRendererPane. The font, foreground, and background
  485. * properties are only set if their current value is either null
  486. * or a UIResource, other properties are set if the current
  487. * value is null.
  488. *
  489. * @see #uninstallDefaults
  490. * @see #installUI
  491. * @see CellRendererPane
  492. */
  493. protected void installDefaults()
  494. {
  495. list.setLayout(null);
  496. LookAndFeel.installBorder(list, "List.border");
  497. LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
  498. LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
  499. if (list.getCellRenderer() == null) {
  500. list.setCellRenderer((ListCellRenderer)(UIManager.get("List.cellRenderer")));
  501. }
  502. Color sbg = list.getSelectionBackground();
  503. if (sbg == null || sbg instanceof UIResource) {
  504. list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
  505. }
  506. Color sfg = list.getSelectionForeground();
  507. if (sfg == null || sfg instanceof UIResource) {
  508. list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
  509. }
  510. Long l = (Long)UIManager.get("List.timeFactor");
  511. timeFactor = (l!=null) ? l.longValue() : 1000L;
  512. isFileList = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
  513. isLeftToRight = list.getComponentOrientation().isLeftToRight();
  514. }
  515. /**
  516. * Set the JList properties that haven't been explicitly overridden to
  517. * null. A property is considered overridden if its current value
  518. * is not a UIResource.
  519. *
  520. * @see #installDefaults
  521. * @see #uninstallUI
  522. * @see CellRendererPane
  523. */
  524. protected void uninstallDefaults()
  525. {
  526. LookAndFeel.uninstallBorder(list);
  527. if (list.getFont() instanceof UIResource) {
  528. list.setFont(null);
  529. }
  530. if (list.getForeground() instanceof UIResource) {
  531. list.setForeground(null);
  532. }
  533. if (list.getBackground() instanceof UIResource) {
  534. list.setBackground(null);
  535. }
  536. if (list.getSelectionBackground() instanceof UIResource) {
  537. list.setSelectionBackground(null);
  538. }
  539. if (list.getSelectionForeground() instanceof UIResource) {
  540. list.setSelectionForeground(null);
  541. }
  542. if (list.getCellRenderer() instanceof UIResource) {
  543. list.setCellRenderer(null);
  544. }
  545. if (list.getTransferHandler() instanceof UIResource) {
  546. list.setTransferHandler(null);
  547. }
  548. }
  549. /**
  550. * Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
  551. * <code>installListeners()</code>, and <code>installKeyboardActions()</code>
  552. * in order.
  553. *
  554. * @see #installDefaults
  555. * @see #installListeners
  556. * @see #installKeyboardActions
  557. */
  558. public void installUI(JComponent c)
  559. {
  560. list = (JList)c;
  561. layoutOrientation = list.getLayoutOrientation();
  562. rendererPane = new CellRendererPane();
  563. list.add(rendererPane);
  564. columnCount = 1;
  565. installDefaults();
  566. installListeners();
  567. installKeyboardActions();
  568. }
  569. /**
  570. * Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
  571. * <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
  572. * in order. Sets this.list to null.
  573. *
  574. * @see #uninstallListeners
  575. * @see #uninstallKeyboardActions
  576. * @see #uninstallDefaults
  577. */
  578. public void uninstallUI(JComponent c)
  579. {
  580. uninstallListeners();
  581. uninstallDefaults();
  582. uninstallKeyboardActions();
  583. cellWidth = cellHeight = -1;
  584. cellHeights = null;
  585. listWidth = listHeight = -1;
  586. list.remove(rendererPane);
  587. rendererPane = null;
  588. list = null;
  589. }
  590. /**
  591. * Returns a new instance of BasicListUI. BasicListUI delegates are
  592. * allocated one per JList.
  593. *
  594. * @return A new ListUI implementation for the Windows look and feel.
  595. */
  596. public static ComponentUI createUI(JComponent list) {
  597. return new BasicListUI();
  598. }
  599. /**
  600. * Convert a point in <code>JList</code> coordinates to the closest index
  601. * of the cell at that location. To determine if the cell actually
  602. * contains the specified location use a combination of this method and
  603. * <code>getCellBounds</code>. Returns -1 if the model is empty.
  604. *
  605. * @return The index of the cell at location, or -1.
  606. * @see ListUI#locationToIndex
  607. */
  608. public int locationToIndex(JList list, Point location) {
  609. maybeUpdateLayoutState();
  610. return convertLocationToModel(location.x, location.y);
  611. }
  612. /**
  613. * @return The origin of the index'th cell, null if index is invalid.
  614. * @see ListUI#indexToLocation
  615. */
  616. public Point indexToLocation(JList list, int index) {
  617. maybeUpdateLayoutState();
  618. Rectangle rect = getCellBounds(list, index, index);
  619. if (rect != null) {
  620. return new Point(rect.x, rect.y);
  621. }
  622. return null;
  623. }
  624. /**
  625. * @return The bounds of the index'th cell.
  626. * @see ListUI#getCellBounds
  627. */
  628. public Rectangle getCellBounds(JList list, int index1, int index2) {
  629. maybeUpdateLayoutState();
  630. int minIndex = Math.min(index1, index2);
  631. int maxIndex = Math.max(index1, index2);
  632. if (minIndex >= list.getModel().getSize()) {
  633. return null;
  634. }
  635. Rectangle minBounds = getCellBounds(list, minIndex);
  636. if (minBounds == null) {
  637. return null;
  638. }
  639. if (minIndex == maxIndex) {
  640. return minBounds;
  641. }
  642. Rectangle maxBounds = getCellBounds(list, maxIndex);
  643. if (maxBounds != null) {
  644. if (layoutOrientation == JList.HORIZONTAL_WRAP) {
  645. int minRow = convertModelToRow(minIndex);
  646. int maxRow = convertModelToRow(maxIndex);
  647. if (minRow != maxRow) {
  648. minBounds.x = 0;
  649. minBounds.width = list.getWidth();
  650. }
  651. }
  652. else if (minBounds.x != maxBounds.x) {
  653. // Different columns
  654. minBounds.y = 0;
  655. minBounds.height = list.getHeight();
  656. }
  657. minBounds.add(maxBounds);
  658. }
  659. return minBounds;
  660. }
  661. /**
  662. * Gets the bounds of the specified model index, returning the resulting
  663. * bounds, or null if <code>index</code> is not valid.
  664. */
  665. private Rectangle getCellBounds(JList list, int index) {
  666. maybeUpdateLayoutState();
  667. int row = convertModelToRow(index);
  668. int column = convertModelToColumn(index);
  669. if (row == -1 || column == -1) {
  670. return null;
  671. }
  672. Insets insets = list.getInsets();
  673. int x;
  674. int w = cellWidth;
  675. int y = insets.top;
  676. int h;
  677. switch (layoutOrientation) {
  678. case JList.VERTICAL_WRAP:
  679. case JList.HORIZONTAL_WRAP:
  680. if (isLeftToRight) {
  681. x = insets.left + column * cellWidth;
  682. } else {
  683. x = list.getWidth() - insets.right - (column+1) * cellWidth;
  684. }
  685. y += cellHeight * row;
  686. h = cellHeight;
  687. break;
  688. default:
  689. x = insets.left;
  690. if (cellHeights == null) {
  691. y += (cellHeight * row);
  692. }
  693. else if (row >= cellHeights.length) {
  694. y = 0;
  695. }
  696. else {
  697. for(int i = 0; i < row; i++) {
  698. y += cellHeights[i];
  699. }
  700. }
  701. w = list.getWidth() - (insets.left + insets.right);
  702. h = getRowHeight(index);
  703. break;
  704. }
  705. return new Rectangle(x, y, w, h);
  706. }
  707. /**
  708. * Returns the height of the specified row based on the current layout.
  709. *
  710. * @return The specified row height or -1 if row isn't valid.
  711. * @see #convertYToRow
  712. * @see #convertRowToY
  713. * @see #updateLayoutState
  714. */
  715. protected int getRowHeight(int row)
  716. {
  717. return getHeight(0, row);
  718. }
  719. /**
  720. * Convert the JList relative coordinate to the row that contains it,
  721. * based on the current layout. If y0 doesn't fall within any row,
  722. * return -1.
  723. *
  724. * @return The row that contains y0, or -1.
  725. * @see #getRowHeight
  726. * @see #updateLayoutState
  727. */
  728. protected int convertYToRow(int y0)
  729. {
  730. return convertLocationToRow(0, y0, false);
  731. }
  732. /**
  733. * Return the JList relative Y coordinate of the origin of the specified
  734. * row or -1 if row isn't valid.
  735. *
  736. * @return The Y coordinate of the origin of row, or -1.
  737. * @see #getRowHeight
  738. * @see #updateLayoutState
  739. */
  740. protected int convertRowToY(int row)
  741. {
  742. if (row >= getRowCount(0) || row < 0) {
  743. return -1;
  744. }
  745. Rectangle bounds = getCellBounds(list, row, row);
  746. return bounds.y;
  747. }
  748. /**
  749. * Returns the height of the cell at the passed in location.
  750. */
  751. private int getHeight(int column, int row) {
  752. if (column < 0 || column > columnCount || row < 0) {
  753. return -1;
  754. }
  755. if (layoutOrientation != JList.VERTICAL) {
  756. return cellHeight;
  757. }
  758. if (row >= list.getModel().getSize()) {
  759. return -1;
  760. }
  761. return (cellHeights == null) ? cellHeight :
  762. ((row < cellHeights.length) ? cellHeights[row] : -1);
  763. }
  764. /**
  765. * Returns the row at location x/y.
  766. *
  767. * @param closest If true and the location doesn't exactly match a
  768. * particular location, this will return the closest row.
  769. */
  770. private int convertLocationToRow(int x, int y0, boolean closest) {
  771. int size = list.getModel().getSize();
  772. if (size <= 0) {
  773. return -1;
  774. }
  775. Insets insets = list.getInsets();
  776. if (cellHeights == null) {
  777. int row = (cellHeight == 0) ? 0 :
  778. ((y0 - insets.top) / cellHeight);
  779. if (closest) {
  780. if (row < 0) {
  781. row = 0;
  782. }
  783. else if (row >= size) {
  784. row = size - 1;
  785. }
  786. }
  787. return row;
  788. }
  789. else if (size > cellHeights.length) {
  790. return -1;
  791. }
  792. else {
  793. int y = insets.top;
  794. int row = 0;
  795. if (closest && y0 < y) {
  796. return 0;
  797. }
  798. int i;
  799. for (i = 0; i < size; i++) {
  800. if ((y0 >= y) && (y0 < y + cellHeights[i])) {
  801. return row;
  802. }
  803. y += cellHeights[i];
  804. row += 1;
  805. }
  806. return i - 1;
  807. }
  808. }
  809. /**
  810. * Returns the closest row that starts at the specified y-location
  811. * in the passed in column.
  812. */
  813. private int convertLocationToRowInColumn(int y, int column) {
  814. int x = 0;
  815. if (layoutOrientation != JList.VERTICAL) {
  816. if (isLeftToRight) {
  817. x = column * cellWidth;
  818. } else {
  819. x = list.getWidth() - (column+1)*cellWidth - list.getInsets().right;
  820. }
  821. }
  822. return convertLocationToRow(x, y, true);
  823. }
  824. /**
  825. * Returns the closest location to the model index of the passed in
  826. * location.
  827. */
  828. private int convertLocationToModel(int x, int y) {
  829. int row = convertLocationToRow(x, y, true);
  830. int column = convertLocationToColumn(x, y);
  831. if (row >= 0 && column >= 0) {
  832. return getModelIndex(column, row);
  833. }
  834. return -1;
  835. }
  836. /**
  837. * Returns the number of rows in the given column.
  838. */
  839. private int getRowCount(int column) {
  840. if (column < 0 || column >= columnCount) {
  841. return -1;
  842. }
  843. if (layoutOrientation == JList.VERTICAL ||
  844. (column == 0 && columnCount == 1)) {
  845. return list.getModel().getSize();
  846. }
  847. if (column >= columnCount) {
  848. return -1;
  849. }
  850. if (layoutOrientation == JList.VERTICAL_WRAP) {
  851. if (column < (columnCount - 1)) {
  852. return rowsPerColumn;
  853. }
  854. return list.getModel().getSize() - (columnCount - 1) *
  855. rowsPerColumn;
  856. }
  857. // JList.HORIZONTAL_WRAP
  858. int diff = columnCount - (columnCount * rowsPerColumn -
  859. list.getModel().getSize());
  860. if (column >= diff) {
  861. return Math.max(0, rowsPerColumn - 1);
  862. }
  863. return rowsPerColumn;
  864. }
  865. /**
  866. * Returns the model index for the specified display location.
  867. * If <code>column</code>x<code>row</code> is beyond the length of the
  868. * model, this will return the model size - 1.
  869. */
  870. private int getModelIndex(int column, int row) {
  871. switch (layoutOrientation) {
  872. case JList.VERTICAL_WRAP:
  873. return Math.min(list.getModel().getSize() - 1, rowsPerColumn *
  874. column + Math.min(row, rowsPerColumn-1));
  875. case JList.HORIZONTAL_WRAP:
  876. return Math.min(list.getModel().getSize() - 1, row * columnCount +
  877. column);
  878. default:
  879. return row;
  880. }
  881. }
  882. /**
  883. * Returns the closest column to the passed in location.
  884. */
  885. private int convertLocationToColumn(int x, int y) {
  886. if (cellWidth > 0) {
  887. if (layoutOrientation == JList.VERTICAL) {
  888. return 0;
  889. }
  890. Insets insets = list.getInsets();
  891. int col;
  892. if (isLeftToRight) {
  893. col = (x - insets.left) / cellWidth;
  894. } else {
  895. col = (list.getWidth() - x - insets.right - 1) / cellWidth;
  896. }
  897. if (col < 0) {
  898. return 0;
  899. }
  900. else if (col >= columnCount) {
  901. return columnCount - 1;
  902. }
  903. return col;
  904. }
  905. return 0;
  906. }
  907. /**
  908. * Returns the row that the model index <code>index</code> will be
  909. * displayed in..
  910. */
  911. private int convertModelToRow(int index) {
  912. int size = list.getModel().getSize();
  913. if ((index < 0) || (index >= size)) {
  914. return -1;
  915. }
  916. if (layoutOrientation != JList.VERTICAL && columnCount > 1 &&
  917. rowsPerColumn > 0) {
  918. if (layoutOrientation == JList.VERTICAL_WRAP) {
  919. return index % rowsPerColumn;
  920. }
  921. return index / columnCount;
  922. }
  923. return index;
  924. }
  925. /**
  926. * Returns the column that the model index <code>index</code> will be
  927. * displayed in.
  928. */
  929. private int convertModelToColumn(int index) {
  930. int size = list.getModel().getSize();
  931. if ((index < 0) || (index >= size)) {
  932. return -1;
  933. }
  934. if (layoutOrientation != JList.VERTICAL && rowsPerColumn > 0 &&
  935. columnCount > 1) {
  936. if (layoutOrientation == JList.VERTICAL_WRAP) {
  937. return index / rowsPerColumn;
  938. }
  939. return index % columnCount;
  940. }
  941. return 0;
  942. }
  943. /**
  944. * If updateLayoutStateNeeded is non zero, call updateLayoutState() and reset
  945. * updateLayoutStateNeeded. This method should be called by methods
  946. * before doing any computation based on the geometry of the list.
  947. * For example it's the first call in paint() and getPreferredSize().
  948. *
  949. * @see #updateLayoutState
  950. */
  951. protected void maybeUpdateLayoutState()
  952. {
  953. if (updateLayoutStateNeeded != 0) {
  954. updateLayoutState();
  955. updateLayoutStateNeeded = 0;
  956. }
  957. }
  958. /**
  959. * Recompute the value of cellHeight or cellHeights based
  960. * and cellWidth, based on the current font and the current
  961. * values of fixedCellWidth, fixedCellHeight, and prototypeCellValue.
  962. *
  963. * @see #maybeUpdateLayoutState
  964. */
  965. protected void updateLayoutState()
  966. {
  967. /* If both JList fixedCellWidth and fixedCellHeight have been
  968. * set, then initialize cellWidth and cellHeight, and set
  969. * cellHeights to null.
  970. */
  971. int fixedCellHeight = list.getFixedCellHeight();
  972. int fixedCellWidth = list.getFixedCellWidth();
  973. cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
  974. if (fixedCellHeight != -1) {
  975. cellHeight = fixedCellHeight;
  976. cellHeights = null;
  977. }
  978. else {
  979. cellHeight = -1;
  980. cellHeights = new int[list.getModel().getSize()];
  981. }
  982. /* If either of JList fixedCellWidth and fixedCellHeight haven't
  983. * been set, then initialize cellWidth and cellHeights by
  984. * scanning through the entire model. Note: if the renderer is
  985. * null, we just set cellWidth and cellHeights[*] to zero,
  986. * if they're not set already.
  987. */
  988. if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
  989. ListModel dataModel = list.getModel();
  990. int dataModelSize = dataModel.getSize();
  991. ListCellRenderer renderer = list.getCellRenderer();
  992. if (renderer != null) {
  993. for(int index = 0; index < dataModelSize; index++) {
  994. Object value = dataModel.getElementAt(index);
  995. Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
  996. rendererPane.add(c);
  997. Dimension cellSize = c.getPreferredSize();
  998. if (fixedCellWidth == -1) {
  999. cellWidth = Math.max(cellSize.width, cellWidth);
  1000. }
  1001. if (fixedCellHeight == -1) {
  1002. cellHeights[index] = cellSize.height;
  1003. }
  1004. }
  1005. }
  1006. else {
  1007. if (cellWidth == -1) {
  1008. cellWidth = 0;
  1009. }
  1010. if (cellHeights == null) {
  1011. cellHeights = new int[dataModelSize];
  1012. }
  1013. for(int index = 0; index < dataModelSize; index++) {
  1014. cellHeights[index] = 0;
  1015. }
  1016. }
  1017. }
  1018. columnCount = 1;
  1019. if (layoutOrientation != JList.VERTICAL) {
  1020. updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
  1021. }
  1022. }
  1023. /**
  1024. * Invoked when the list is layed out horizontally to determine how
  1025. * many columns to create.
  1026. * <p>
  1027. * This updates the <code>rowsPerColumn, </code><code>columnCount</code>,
  1028. * <code>preferredHeight</code> and potentially <code>cellHeight</code>
  1029. * instance variables.
  1030. */
  1031. private void updateHorizontalLayoutState(int fixedCellWidth,
  1032. int fixedCellHeight) {
  1033. int visRows = list.getVisibleRowCount();
  1034. int dataModelSize = list.getModel().getSize();
  1035. Insets insets = list.getInsets();
  1036. listHeight = list.getHeight();
  1037. listWidth = list.getWidth();
  1038. if (dataModelSize == 0) {
  1039. rowsPerColumn = columnCount = 0;
  1040. preferredHeight = insets.top + insets.bottom;
  1041. return;
  1042. }
  1043. int height;
  1044. if (fixedCellHeight != -1) {
  1045. height = fixedCellHeight;
  1046. }
  1047. else {
  1048. // Determine the max of the renderer heights.
  1049. int maxHeight = 0;
  1050. if (cellHeights.length > 0) {
  1051. maxHeight = cellHeights[cellHeights.length - 1];
  1052. for (int counter = cellHeights.length - 2;
  1053. counter >= 0; counter--) {
  1054. maxHeight = Math.max(maxHeight, cellHeights[counter]);
  1055. }
  1056. }
  1057. height = cellHeight = maxHeight;
  1058. cellHeights = null;
  1059. }
  1060. // The number of rows is either determined by the visible row
  1061. // count, or by the height of the list.
  1062. rowsPerColumn = dataModelSize;
  1063. if (visRows > 0) {
  1064. rowsPerColumn = visRows;
  1065. columnCount = Math.max(1, dataModelSize / rowsPerColumn);
  1066. if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
  1067. dataModelSize % rowsPerColumn != 0) {
  1068. columnCount++;
  1069. }
  1070. if (layoutOrientation == JList.HORIZONTAL_WRAP) {
  1071. // Because HORIZONTAL_WRAP flows differently, the
  1072. // rowsPerColumn needs to be adjusted.
  1073. rowsPerColumn = (dataModelSize / columnCount);
  1074. if (dataModelSize % columnCount > 0) {
  1075. rowsPerColumn++;
  1076. }
  1077. }
  1078. }
  1079. else if (layoutOrientation == JList.VERTICAL_WRAP && height != 0) {
  1080. rowsPerColumn = Math.max(1, (listHeight - insets.top -
  1081. insets.bottom) / height);
  1082. columnCount = Math.max(1, dataModelSize / rowsPerColumn);
  1083. if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
  1084. dataModelSize % rowsPerColumn != 0) {
  1085. columnCount++;
  1086. }
  1087. }
  1088. else if (layoutOrientation == JList.HORIZONTAL_WRAP && cellWidth > 0 &&
  1089. listWidth > 0) {
  1090. columnCount = Math.max(1, (listWidth - insets.left -
  1091. insets.right) / cellWidth);
  1092. rowsPerColumn = dataModelSize / columnCount;
  1093. if (dataModelSize % columnCount > 0) {
  1094. rowsPerColumn++;
  1095. }
  1096. }
  1097. preferredHeight = rowsPerColumn * cellHeight + insets.top +
  1098. insets.bottom;
  1099. }
  1100. private Handler getHandler() {
  1101. if (handler == null) {
  1102. handler = new Handler();
  1103. }
  1104. return handler;
  1105. }
  1106. /**
  1107. * Mouse input, and focus handling for JList. An instance of this
  1108. * class is added to the appropriate java.awt.Component lists
  1109. * at installUI() time. Note keyboard input is handled with JComponent
  1110. * KeyboardActions, see installKeyboardActions().
  1111. * <p>
  1112. * <strong>Warning:</strong>
  1113. * Serialized objects of this class will not be compatible with
  1114. * future Swing releases. The current serialization support is
  1115. * appropriate for short term storage or RMI between applications running
  1116. * the same version of Swing. As of 1.4, support for long term storage
  1117. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  1118. * has been added to the <code>java.beans</code> package.
  1119. * Please see {@link java.beans.XMLEncoder}.
  1120. *
  1121. * @see #createMouseInputListener
  1122. * @see #installKeyboardActions
  1123. * @see #installUI
  1124. */
  1125. public class MouseInputHandler implements MouseInputListener
  1126. {
  1127. public void mouseClicked(MouseEvent e) {
  1128. getHandler().mouseClicked(e);
  1129. }
  1130. public void mouseEntered(MouseEvent e) {
  1131. getHandler().mouseEntered(e);
  1132. }
  1133. public void mouseExited(MouseEvent e) {
  1134. getHandler().mouseExited(e);
  1135. }
  1136. public void mousePressed(MouseEvent e) {
  1137. getHandler().mousePressed(e);
  1138. }
  1139. public void mouseDragged(MouseEvent e) {
  1140. getHandler().mouseDragged(e);
  1141. }
  1142. public void mouseMoved(MouseEvent e) {
  1143. getHandler().mouseMoved(e);
  1144. }
  1145. public void mouseReleased(MouseEvent e) {
  1146. getHandler().mouseReleased(e);
  1147. }
  1148. }
  1149. /**
  1150. * Creates a delegate that implements MouseInputListener.
  1151. * The delegate is added to the corresponding java.awt.Component listener
  1152. * lists at installUI() time. Subclasses can override this method to return
  1153. * a custom MouseInputListener, e.g.
  1154. * <pre>
  1155. * class MyListUI extends BasicListUI {
  1156. * protected MouseInputListener <b>createMouseInputListener</b>() {
  1157. * return new MyMouseInputHandler();
  1158. * }
  1159. * public class MyMouseInputHandler extends MouseInputHandler {
  1160. * public void mouseMoved(MouseEvent e) {
  1161. * // do some extra work when the mouse moves
  1162. * super.mouseMoved(e);
  1163. * }
  1164. * }
  1165. * }
  1166. * </pre>
  1167. *
  1168. * @see MouseInputHandler
  1169. * @see #installUI
  1170. */
  1171. protected MouseInputListener createMouseInputListener() {
  1172. return getHandler();
  1173. }
  1174. /**
  1175. * This inner class is marked "public" due to a compiler bug.
  1176. * This class should be treated as a "protected" inner class.
  1177. * Instantiate it only within subclasses of BasicTableUI.
  1178. */
  1179. public class FocusHandler implements FocusListener
  1180. {
  1181. protected void repaintCellFocus()
  1182. {
  1183. getHandler().repaintCellFocus();
  1184. }
  1185. /* The focusGained() focusLost() methods run when the JList
  1186. * focus changes.
  1187. */
  1188. public void focusGained(FocusEvent e) {
  1189. getHandler().focusGained(e);
  1190. }
  1191. public void focusLost(FocusEvent e) {
  1192. getHandler().focusLost(e);
  1193. }
  1194. }
  1195. protected FocusListener createFocusListener() {
  1196. return getHandler();
  1197. }
  1198. /**
  1199. * The ListSelectionListener that's added to the JLists selection
  1200. * model at installUI time, and whenever the JList.selectionModel property
  1201. * changes. When the selection changes we repaint the affected rows.
  1202. * <p>
  1203. * <strong>Warning:</strong>
  1204. * Serialized objects of this class will not be compatible with
  1205. * future Swing releases. The current serialization support is
  1206. * appropriate for short term storage or RMI between applications running
  1207. * the same version of Swing. As of 1.4, support for long term storage
  1208. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  1209. * has been added to the <code>java.beans</code> package.
  1210. * Please see {@link java.beans.XMLEncoder}.
  1211. *
  1212. * @see #createListSelectionListener
  1213. * @see #getCellBounds
  1214. * @see #installUI
  1215. */
  1216. public class ListSelectionHandler implements ListSelectionListener
  1217. {
  1218. public void valueChanged(ListSelectionEvent e)
  1219. {
  1220. getHandler().valueChanged(e);
  1221. }
  1222. }
  1223. /**
  1224. * Creates an instance of ListSelectionHandler that's added to
  1225. * the JLists by selectionModel as needed. Subclasses can override
  1226. * this method to return a custom ListSelectionListener, e.g.
  1227. * <pre>
  1228. * class MyListUI extends BasicListUI {
  1229. * protected ListSelectionListener <b>createListSelectionListener</b>() {
  1230. * return new MySelectionListener();
  1231. * }
  1232. * public class MySelectionListener extends ListSelectionHandler {
  1233. * public void valueChanged(ListSelectionEvent e) {
  1234. * // do some extra work when the selection changes
  1235. * super.valueChange(e);
  1236. * }
  1237. * }
  1238. * }
  1239. * </pre>
  1240. *
  1241. * @see ListSelectionHandler
  1242. * @see #installUI
  1243. */
  1244. protected ListSelectionListener createListSelectionListener() {
  1245. return getHandler();
  1246. }
  1247. private void redrawList() {
  1248. list.revalidate();
  1249. list.repaint();
  1250. }
  1251. /**
  1252. * The ListDataListener that's added to the JLists model at
  1253. * installUI time, and whenever the JList.model property changes.
  1254. * <p>
  1255. * <strong>Warning:</strong>
  1256. * Serialized objects of this class will not be compatible with
  1257. * future Swing releases. The current serialization support is
  1258. * appropriate for short term storage or RMI between applications running
  1259. * the same version of Swing. As of 1.4, support for long term storage
  1260. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  1261. * has been added to the <code>java.beans</code> package.
  1262. * Please see {@link java.beans.XMLEncoder}.
  1263. *
  1264. * @see JList#getModel
  1265. * @see #maybeUpdateLayoutState
  1266. * @see #createListDataListener
  1267. * @see #installUI
  1268. */
  1269. public class ListDataHandler implements ListDataListener
  1270. {
  1271. public void intervalAdded(ListDataEvent e) {
  1272. getHandler().intervalAdded(e);
  1273. }
  1274. public void intervalRemoved(ListDataEvent e)
  1275. {
  1276. getHandler().intervalRemoved(e);
  1277. }
  1278. public void contentsChanged(ListDataEvent e) {
  1279. getHandler().contentsChanged(e);
  1280. }
  1281. }
  1282. /**
  1283. * Creates an instance of ListDataListener that's added to
  1284. * the JLists by model as needed. Subclasses can override
  1285. * this method to return a custom ListDataListener, e.g.
  1286. * <pre>
  1287. * class MyListUI extends BasicListUI {
  1288. * protected ListDataListener <b>createListDataListener</b>() {
  1289. * return new MyListDataListener();
  1290. * }
  1291. * public class MyListDataListener extends ListDataHandler {
  1292. * public void contentsChanged(ListDataEvent e) {
  1293. * // do some extra work when the models contents change
  1294. * super.contentsChange(e);
  1295. * }
  1296. * }
  1297. * }
  1298. * </pre>
  1299. *
  1300. * @see ListDataListener
  1301. * @see JList#getModel
  1302. * @see #installUI
  1303. */
  1304. protected ListDataListener createListDataListener() {
  1305. return getHandler();
  1306. }
  1307. /**
  1308. * The PropertyChangeListener that's added to the JList at
  1309. * installUI time. When the value of a JList property that
  1310. * affects layout changes, we set a bit in updateLayoutStateNeeded.
  1311. * If the JLists model changes we additionally remove our listeners
  1312. * from the old model. Likewise for the JList selectionModel.
  1313. * <p>
  1314. * <strong>Warning:</strong>
  1315. * Serialized objects of this class will not be compatible with
  1316. * future Swing releases. The current serialization support is
  1317. * appropriate for short term storage or RMI between applications running
  1318. * the same version of Swing. As of 1.4, support for long term storage
  1319. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  1320. * has been added to the <code>java.beans</code> package.
  1321. * Please see {@link java.beans.XMLEncoder}.
  1322. *
  1323. * @see #maybeUpdateLayoutState
  1324. * @see #createPropertyChangeListener
  1325. * @see #installUI
  1326. */
  1327. public class PropertyChangeHandler implements PropertyChangeListener
  1328. {
  1329. public void propertyChange(PropertyChangeEvent e)
  1330. {
  1331. getHandler().propertyChange(e);
  1332. }
  1333. }
  1334. /**
  1335. * Creates an instance of PropertyChangeHandler that's added to
  1336. * the JList by installUI(). Subclasses can override this method
  1337. * to return a custom PropertyChangeListener, e.g.
  1338. * <pre>
  1339. * class MyListUI extends BasicListUI {
  1340. * protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
  1341. * return new MyPropertyChangeListener();
  1342. * }
  1343. * public class MyPropertyChangeListener extends PropertyChangeHandler {
  1344. * public void propertyChange(PropertyChangeEvent e) {
  1345. * if (e.getPropertyName().equals("model")) {
  1346. * // do some extra work when the model changes
  1347. * }
  1348. * super.propertyChange(e);
  1349. * }
  1350. * }
  1351. * }
  1352. * </pre>
  1353. *
  1354. * @see PropertyChangeListener
  1355. * @see #installUI
  1356. */
  1357. protected PropertyChangeListener createPropertyChangeListener() {
  1358. return getHandler();
  1359. }
  1360. /** Used by IncrementLeadSelectionAction. Indicates the action should
  1361. * change the lead, and not select it. */
  1362. private static final int CHANGE_LEAD = 0;
  1363. /** Used by IncrementLeadSelectionAction. Indicates the action should
  1364. * change the selection and lead. */
  1365. private static final int CHANGE_SELECTION = 1;
  1366. /** Used by IncrementLeadSelectionAction. Indicates the action should
  1367. * extend the selection from the anchor to the next index. */
  1368. private static final int EXTEND_SELECTION = 2;
  1369. private static class Actions extends UIAction {
  1370. private static final String SELECT_PREVIOUS_COLUMN =
  1371. "selectPreviousColumn";
  1372. private static final String SELECT_PREVIOUS_COLUMN_EXTEND =
  1373. "selectPreviousColumnExtendSelection";
  1374. private static final String SELECT_PREVIOUS_COLUMN_CHANGE_LEAD =
  1375. "selectPreviousColumnChangeLead";
  1376. private static final String SELECT_NEXT_COLUMN = "selectNextColumn";
  1377. private static final String SELECT_NEXT_COLUMN_EXTEND =
  1378. "selectNextColumnExtendSelection";
  1379. private static final String SELECT_NEXT_COLUMN_CHANGE_LEAD =
  1380. "selectNextColumnChangeLead";
  1381. private static final String SELECT_PREVIOUS_ROW = "selectPreviousRow";
  1382. private static final String SELECT_PREVIOUS_ROW_EXTEND =
  1383. "selectPreviousRowExtendSelection";
  1384. private static final String SELECT_PREVIOUS_ROW_CHANGE_LEAD =
  1385. "selectPreviousRowChangeLead";
  1386. private static final String SELECT_NEXT_ROW = "selectNextRow";
  1387. private static final String SELECT_NEXT_ROW_EXTEND =
  1388. "selectNextRowExtendSelection";
  1389. private static final String SELECT_NEXT_ROW_CHANGE_LEAD =
  1390. "selectNextRowChangeLead";
  1391. private static final String SELECT_FIRST_ROW = "selectFirstRow";
  1392. private static final String SELECT_FIRST_ROW_EXTEND =
  1393. "selectFirstRowExtendSelection";
  1394. private static final String SELECT_FIRST_ROW_CHANGE_LEAD =
  1395. "selectFirstRowChangeLead";
  1396. private static final String SELECT_LAST_ROW = "selectLastRow";
  1397. private static final String SELECT_LAST_ROW_EXTEND =
  1398. "selectLastRowExtendSelection";
  1399. private static final String SELECT_LAST_ROW_CHANGE_LEAD =
  1400. "selectLastRowChangeLead";
  1401. private static final String SCROLL_UP = "scrollUp";
  1402. private static final String SCROLL_UP_EXTEND =
  1403. "scrollUpExtendSelection";
  1404. private static final String SCROLL_UP_CHANGE_LEAD =
  1405. "scrollUpChangeLead";
  1406. private static final String SCROLL_DOWN = "scrollDown";
  1407. private static final String SCROLL_DOWN_EXTEND =
  1408. "scrollDownExtendSelection";
  1409. private static final String SCROLL_DOWN_CHANGE_LEAD =
  1410. "scrollDownChangeLead";
  1411. private static final String SELECT_ALL = "selectAll";
  1412. private static final String CLEAR_SELECTION = "clearSelection";
  1413. // add the lead item to the selection without changing lead or anchor
  1414. private static final String ADD_TO_SELECTION = "addToSelection";
  1415. // toggle the selected state of the lead item and move the anchor to it
  1416. private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
  1417. // extend the selection to the lead item
  1418. private static final String EXTEND_TO = "extendTo";
  1419. // move the anchor to the lead and ensure only that item is selected
  1420. private static final String MOVE_SELECTION_TO = "moveSelectionTo";
  1421. Actions(String name) {
  1422. super(name);
  1423. }
  1424. public void actionPerformed(ActionEvent e) {
  1425. String name = getName();
  1426. JList list = (JList)e.getSource();
  1427. BasicListUI ui = (BasicListUI)BasicLookAndFeel.getUIOfType(
  1428. list.getUI(), BasicListUI.class);
  1429. if (name == SELECT_PREVIOUS_COLUMN) {
  1430. changeSelection(list, CHANGE_SELECTION,
  1431. getNextColumnIndex(list, ui, -1), true);
  1432. }
  1433. else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
  1434. changeSelection(list, EXTEND_SELECTION,
  1435. getNextColumnIndex(list, ui, -1), true);
  1436. }
  1437. else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
  1438. changeSelection(list, CHANGE_LEAD,
  1439. getNextColumnIndex(list, ui, -1), true);
  1440. }
  1441. else if (name == SELECT_NEXT_COLUMN) {
  1442. changeSelection(list, CHANGE_SELECTION,
  1443. getNextColumnIndex(list, ui, 1), true);
  1444. }
  1445. else if (name == SELECT_NEXT_COLUMN_EXTEND) {
  1446. changeSelection(list, EXTEND_SELECTION,
  1447. getNextColumnIndex(list, ui, 1), true);
  1448. }
  1449. else if (name == SELECT_NEXT_COLUMN_CHANGE_LEAD) {
  1450. changeSelection(list, CHANGE_LEAD,
  1451. getNextColumnIndex(list, ui, 1), true);
  1452. }
  1453. else if (name == SELECT_PREVIOUS_ROW) {
  1454. changeSelection(list, CHANGE_SELECTION,
  1455. getNextIndex(list, ui, -1), true);
  1456. }
  1457. else if (name == SELECT_PREVIOUS_ROW_EXTEND) {
  1458. changeSelection(list, EXTEND_SELECTION,
  1459. getNextIndex(list, ui, -1), true);
  1460. }
  1461. else if (name == SELECT_PREVIOUS_ROW_CHANGE_LEAD) {
  1462. changeSelection(list, CHANGE_LEAD,
  1463. getNextIndex(list, ui, -1), true);
  1464. }
  1465. else if (name == SELECT_NEXT_ROW) {
  1466. changeSelection(list, CHANGE_SELECTION,
  1467. getNextIndex(list, ui, 1), true);
  1468. }
  1469. else if (name == SELECT_NEXT_ROW_EXTEND) {
  1470. changeSelection(list, EXTEND_SELECTION,
  1471. getNextIndex(list, ui, 1), true);
  1472. }
  1473. else if (name == SELECT_NEXT_ROW_CHANGE_LEAD) {
  1474. changeSelection(list, CHANGE_LEAD,
  1475. getNextIndex(list, ui, 1), true);
  1476. }
  1477. else if (name == SELECT_FIRST_ROW) {
  1478. changeSelection(list, CHANGE_SELECTION, 0, true);
  1479. }
  1480. else if (name == SELECT_FIRST_ROW_EXTEND) {
  1481. changeSelection(list, EXTEND_SELECTION, 0, true);
  1482. }
  1483. else if (name == SELECT_FIRST_ROW_CHANGE_LEAD) {
  1484. changeSelection(list, CHANGE_LEAD, 0, true);
  1485. }
  1486. else if (name == SELECT_LAST_ROW) {
  1487. changeSelection(list, CHANGE_SELECTION,
  1488. list.getModel().getSize() - 1, true);
  1489. }
  1490. else if (name == SELECT_LAST_ROW_EXTEND) {
  1491. changeSelection(list, EXTEND_SELECTION,
  1492. list.getModel().getSize() - 1, true);
  1493. }
  1494. else if (name == SELECT_LAST_ROW_CHANGE_LEAD) {
  1495. changeSelection(list, CHANGE_LEAD,
  1496. list.getModel().getSize() - 1, true);
  1497. }
  1498. else if (name == SCROLL_UP) {
  1499. scroll(list, CHANGE_SELECTION, true);
  1500. }
  1501. else if (name == SCROLL_UP_EXTEND) {
  1502. scroll(list, EXTEND_SELECTION, true);
  1503. }
  1504. else if (name == SCROLL_UP_CHANGE_LEAD) {
  1505. scroll(list, CHANGE_LEAD, true);
  1506. }
  1507. else if (name == SCROLL_DOWN) {
  1508. scroll(list, CHANGE_SELECTION, false);
  1509. }
  1510. else if (name == SCROLL_DOWN_EXTEND) {
  1511. scroll(list, EXTEND_SELECTION, false);
  1512. }
  1513. else if (name == SCROLL_DOWN_CHANGE_LEAD) {
  1514. scroll(list, CHANGE_LEAD, false);
  1515. }
  1516. else if (name == SELECT_ALL) {
  1517. selectAll(list);
  1518. }
  1519. else if (name == CLEAR_SELECTION) {
  1520. clearSelection(list);
  1521. }
  1522. else if (name == ADD_TO_SELECTION) {
  1523. int index = list.getSelectionModel().getLeadSelectionIndex();
  1524. if (!list.isSelectedIndex(index)) {
  1525. int oldAnchor = list.getSelectionModel().getAnchorSelectionIndex();
  1526. list.setValueIsAdjusting(true);
  1527. list.addSelectionInterval(index, index);
  1528. list.getSelectionModel().setAnchorSelectionIndex(oldAnchor);
  1529. list.setValueIsAdjusting(false);
  1530. }
  1531. }
  1532. else if (name == TOGGLE_AND_ANCHOR) {
  1533. int index = list.getSelectionModel().getLeadSelectionIndex();
  1534. if (list.isSelectedIndex(index)) {
  1535. list.removeSelectionInterval(index, index);
  1536. } else {
  1537. list.addSelectionInterval(index, index);
  1538. }
  1539. }
  1540. else if (name == EXTEND_TO) {
  1541. changeSelection(list, EXTEND_SELECTION,
  1542. list.getSelectionModel().getLeadSelectionIndex(),
  1543. false);
  1544. }
  1545. else if (name == MOVE_SELECTION_TO) {
  1546. changeSelection(list, CHANGE_SELECTION,
  1547. list.getSelectionModel().getLeadSelectionIndex(),
  1548. false);
  1549. }
  1550. }
  1551. public boolean isEnabled(Object c) {
  1552. Object name = getName();
  1553. if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD ||
  1554. name == SELECT_NEXT_COLUMN_CHANGE_LEAD ||
  1555. name == SELECT_PREVIOUS_ROW_CHANGE_LEAD ||
  1556. name == SELECT_NEXT_ROW_CHANGE_LEAD ||
  1557. name == SELECT_FIRST_ROW_CHANGE_LEAD ||
  1558. name == SELECT_LAST_ROW_CHANGE_LEAD ||
  1559. name == SCROLL_UP_CHANGE_LEAD ||
  1560. name == SCROLL_DOWN_CHANGE_LEAD) {
  1561. // discontinuous selection actions are only enabled for
  1562. // DefaultListSelectionModel
  1563. return c != null && ((JList)c).getSelectionModel()
  1564. instanceof DefaultListSelectionModel;
  1565. }
  1566. return true;
  1567. }
  1568. private void clearSelection(JList list) {
  1569. list.clearSelection();
  1570. }
  1571. private void selectAll(JList list) {
  1572. int size = list.getModel().getSize();
  1573. if (size > 0) {
  1574. ListSelectionModel lsm = list.getSelectionModel();
  1575. if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
  1576. int leadIndex = list.getLeadSelectionIndex();
  1577. if (leadIndex != -1) {
  1578. list.setSelectionInterval(leadIndex, leadIndex);
  1579. } else if (list.getMinSelectionIndex() == -1) {
  1580. list.setSelectionInterval(0, 0);
  1581. list.ensureIndexIsVisible(0);
  1582. }
  1583. } else {
  1584. list.setValueIsAdjusting(true);
  1585. int anchor = lsm.getAnchorSelectionIndex();
  1586. int lead = lsm.getLeadSelectionIndex();
  1587. list.setSelectionInterval(0, size - 1);
  1588. // this is called simply to restore the anchor and lead
  1589. list.addSelectionInterval(anchor, lead);
  1590. list.setValueIsAdjusting(false);
  1591. }
  1592. }
  1593. }
  1594. private void scroll(JList list, int type,
  1595. boolean up) {
  1596. int index = getNextPageIndex(list, up);
  1597. if (index != -1) {
  1598. changeSelection(list, type, index, false);
  1599. // down
  1600. Rectangle visRect = list.getVisibleRect();
  1601. Rectangle cellBounds = list.getCellBounds(index, index);
  1602. if (!up) {
  1603. cellBounds.y = Math.max(0, cellBounds.y +
  1604. cellBounds.height -visRect.height);
  1605. cellBounds.height = visRect.height;
  1606. }
  1607. else {
  1608. cellBounds.height = visRect.height;
  1609. }
  1610. list.scrollRectToVisible(cellBounds);
  1611. }
  1612. }
  1613. private int getNextPageIndex(JList list, boolean up) {
  1614. if (up) {
  1615. int index = list.getFirstVisibleIndex();
  1616. ListSelectionModel lsm = list.getSelectionModel();
  1617. if (lsm.getLeadSelectionIndex() == index) {
  1618. Rectangle visRect = list.getVisibleRect();
  1619. visRect.y = Math.max(0, visRect.y - visRect.height);
  1620. index = list.locationToIndex(visRect.getLocation());
  1621. }
  1622. return index;
  1623. }
  1624. // down
  1625. int index = list.getLastVisibleIndex();
  1626. ListSelectionModel lsm = list.getSelectionModel();
  1627. if (index == -1) {
  1628. // Will happen if size < viewport size.
  1629. index = list.getModel().getSize() - 1;
  1630. }
  1631. if (lsm.getLeadSelectionIndex() == index) {
  1632. Rectangle visRect = list.getVisibleRect();
  1633. visRect.y += visRect.height + visRect.height - 1;
  1634. index = list.locationToIndex(visRect.getLocation());
  1635. if (index == -1) {
  1636. index = list.getModel().getSize() - 1;
  1637. }
  1638. }
  1639. return index;
  1640. }
  1641. private void changeSelection(JList list, int type,
  1642. int index, boolean scroll) {
  1643. if (index >= 0 && index < list.getModel().getSize()) {
  1644. ListSelectionModel lsm = list.getSelectionModel();
  1645. // CHANGE_LEAD is only valid with multiple interval selection
  1646. if (type == CHANGE_LEAD &&
  1647. list.getSelectionMode()
  1648. != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
  1649. type = CHANGE_SELECTION;
  1650. }
  1651. if (type == EXTEND_SELECTION) {
  1652. int anchor = lsm.getAnchorSelectionIndex();
  1653. if (anchor == -1) {
  1654. anchor = index;
  1655. }
  1656. list.setSelectionInterval(anchor, index);
  1657. }
  1658. else if (type == CHANGE_SELECTION) {
  1659. list.setSelectedIndex(index);
  1660. }
  1661. else {
  1662. // casting should be safe since the action is only enabled
  1663. // for DefaultListSelectionModel
  1664. ((DefaultListSelectionModel)lsm).moveLeadSelectionIndex(index);
  1665. }
  1666. if (scroll) {
  1667. list.ensureIndexIsVisible(index);
  1668. }
  1669. }
  1670. }
  1671. private int getNextColumnIndex(JList list, BasicListUI ui,
  1672. int amount) {
  1673. if (list.getLayoutOrientation() != JList.VERTICAL && ui != null &&
  1674. ui.columnCount > 1) {
  1675. int index = list.getLeadSelectionIndex();
  1676. if (index == -1) {
  1677. return 0;
  1678. }
  1679. int size = list.getModel().getSize();
  1680. int column = ui.convertModelToColumn(index);
  1681. int row = ui.convertModelToRow(index);
  1682. column += amount;
  1683. if (column >= ui.columnCount || column < 0) {
  1684. // No wrapping.
  1685. return -1;
  1686. }
  1687. int maxRowCount = ui.getRowCount(column);
  1688. if (row >= maxRowCount) {
  1689. row = maxRowCount - 1;
  1690. }
  1691. return ui.getModelIndex(column, row);
  1692. }
  1693. // Won't change the selection.
  1694. return -1;
  1695. }
  1696. private int getNextIndex(JList list, BasicListUI ui, int amount) {
  1697. int index = list.getLeadSelectionIndex();
  1698. int size = list.getModel().getSize();
  1699. if (index == -1) {
  1700. if (size > 0) {
  1701. if (amount > 0) {
  1702. index = 0;
  1703. }
  1704. else {
  1705. index = size - 1;
  1706. }
  1707. }
  1708. }
  1709. else {
  1710. if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
  1711. if (ui != null) {
  1712. index += ui.columnCount * amount;
  1713. }
  1714. }
  1715. else {
  1716. index += amount;
  1717. }
  1718. }
  1719. return index;
  1720. }
  1721. }
  1722. private class Handler implements FocusListener, KeyListener,
  1723. ListDataListener, ListSelectionListener,
  1724. MouseInputListener, PropertyChangeListener {
  1725. //
  1726. // KeyListener
  1727. //
  1728. private String prefix = "";
  1729. private String typedString = "";
  1730. private long lastTime = 0L;
  1731. /**
  1732. * Invoked when a key has been typed.
  1733. *
  1734. * Moves the keyboard focus to the first element whose prefix matches the
  1735. * sequence of alphanumeric keys pressed by the user with delay less
  1736. * than value of <code>timeFactor</code> property (or 1000 milliseconds
  1737. * if it is not defined). Subsequent same key presses move the keyboard
  1738. * focus to the next object that starts with the same letter until another
  1739. * key is pressed, then it is treated as the prefix with appropriate number
  1740. * of the same letters followed by first typed anothe letter.
  1741. */
  1742. public void keyTyped(KeyEvent e) {
  1743. JList src = (JList)e.getSource();
  1744. ListModel model = src.getModel();
  1745. if (model.getSize() == 0 || e.isAltDown() || e.isControlDown() || e.isMetaDown() ||
  1746. isNavigationKey(e)) {
  1747. // Nothing to select
  1748. return;
  1749. }
  1750. boolean startingFromSelection = true;
  1751. char c = e.getKeyChar();
  1752. long time = e.getWhen();
  1753. int startIndex = src.getLeadSelectionIndex();
  1754. if (time - lastTime < timeFactor) {
  1755. typedString += c;
  1756. if((prefix.length() == 1) && (c == prefix.charAt(0))) {
  1757. // Subsequent same key presses move the keyboard focus to the next
  1758. // object that starts with the same letter.
  1759. startIndex++;
  1760. } else {
  1761. prefix = typedString;
  1762. }
  1763. } else {
  1764. startIndex++;
  1765. typedString = "" + c;
  1766. prefix = typedString;
  1767. }
  1768. lastTime = time;
  1769. if (startIndex < 0 || startIndex >= model.getSize()) {
  1770. startingFromSelection = false;
  1771. startIndex = 0;
  1772. }
  1773. int index = src.getNextMatch(prefix, startIndex,
  1774. Position.Bias.Forward);
  1775. if (index >= 0) {
  1776. src.setSelectedIndex(index);
  1777. src.ensureIndexIsVisible(index);
  1778. } else if (startingFromSelection) { // wrap
  1779. index = src.getNextMatch(prefix, 0,
  1780. Position.Bias.Forward);
  1781. if (index >= 0) {
  1782. src.setSelectedIndex(index);
  1783. src.ensureIndexIsVisible(index);
  1784. }
  1785. }
  1786. }
  1787. /**
  1788. * Invoked when a key has been pressed.
  1789. *
  1790. * Checks to see if the key event is a navigation key to prevent
  1791. * dispatching these keys for the first letter navigation.
  1792. */
  1793. public void keyPressed(KeyEvent e) {
  1794. if ( isNavigationKey(e) ) {
  1795. prefix = "";
  1796. typedString = "";
  1797. lastTime = 0L;
  1798. }
  1799. }
  1800. /**
  1801. * Invoked when a key has been released.
  1802. * See the class description for {@link KeyEvent} for a definition of
  1803. * a key released event.
  1804. */
  1805. public void keyReleased(KeyEvent e) {
  1806. }
  1807. /**
  1808. * Returns whether or not the supplied key event maps to a key that is used for
  1809. * navigation. This is used for optimizing key input by only passing non-
  1810. * navigation keys to the first letter navigation mechanism.
  1811. */
  1812. private boolean isNavigationKey(KeyEvent event) {
  1813. InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  1814. KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
  1815. if (inputMap != null && inputMap.get(key) != null) {
  1816. return true;
  1817. }
  1818. return false;
  1819. }
  1820. //
  1821. // PropertyChangeListener
  1822. //
  1823. public void propertyChange(PropertyChangeEvent e) {
  1824. String propertyName = e.getPropertyName();
  1825. /* If the JList.model property changes, remove our listener,
  1826. * listDataListener from the old model and add it to the new one.
  1827. */
  1828. if (propertyName == "model") {
  1829. ListModel oldModel = (ListModel)e.getOldValue();
  1830. ListModel newModel = (ListModel)e.getNewValue();
  1831. if (oldModel != null) {
  1832. oldModel.removeListDataListener(listDataListener);
  1833. }
  1834. if (newModel != null) {
  1835. newModel.addListDataListener(listDataListener);
  1836. }
  1837. updateLayoutStateNeeded |= modelChanged;
  1838. redrawList();
  1839. }
  1840. /* If the JList.selectionModel property changes, remove our listener,
  1841. * listSelectionListener from the old selectionModel and add it to the new one.
  1842. */
  1843. else if (propertyName == "selectionModel") {
  1844. ListSelectionModel oldModel = (ListSelectionModel)e.getOldValue();
  1845. ListSelectionModel newModel = (ListSelectionModel)e.getNewValue();
  1846. if (oldModel != null) {
  1847. oldModel.removeListSelectionListener(listSelectionListener);
  1848. }
  1849. if (newModel != null) {
  1850. newModel.addListSelectionListener(listSelectionListener);
  1851. }
  1852. updateLayoutStateNeeded |= modelChanged;
  1853. redrawList();
  1854. }
  1855. else if (propertyName == "cellRenderer") {
  1856. updateLayoutStateNeeded |= cellRendererChanged;
  1857. redrawList();
  1858. }
  1859. else if (propertyName == "font") {
  1860. updateLayoutStateNeeded |= fontChanged;
  1861. redrawList();
  1862. }
  1863. else if (propertyName == "prototypeCellValue") {
  1864. updateLayoutStateNeeded |= prototypeCellValueChanged;
  1865. redrawList();
  1866. }
  1867. else if (propertyName == "fixedCellHeight") {
  1868. updateLayoutStateNeeded |= fixedCellHeightChanged;
  1869. redrawList();
  1870. }
  1871. else if (propertyName == "fixedCellWidth") {
  1872. updateLayoutStateNeeded |= fixedCellWidthChanged;
  1873. redrawList();
  1874. }
  1875. else if (propertyName == "cellRenderer") {
  1876. updateLayoutStateNeeded |= cellRendererChanged;
  1877. redrawList();
  1878. }
  1879. else if (propertyName == "selectionForeground") {
  1880. list.repaint();
  1881. }
  1882. else if (propertyName == "selectionBackground") {
  1883. list.repaint();
  1884. }
  1885. else if ("layoutOrientation" == propertyName) {
  1886. updateLayoutStateNeeded |= layoutOrientationChanged;
  1887. layoutOrientation = list.getLayoutOrientation();
  1888. redrawList();
  1889. }
  1890. else if ("visibleRowCount" == propertyName) {
  1891. if (layoutOrientation != JList.VERTICAL) {
  1892. updateLayoutStateNeeded |= layoutOrientationChanged;
  1893. redrawList();
  1894. }
  1895. }
  1896. else if ("componentOrientation" == propertyName) {
  1897. isLeftToRight = list.getComponentOrientation().isLeftToRight();
  1898. updateLayoutStateNeeded |= componentOrientationChanged;
  1899. redrawList();
  1900. InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
  1901. SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
  1902. inputMap);
  1903. } else if ("List.isFileList" == propertyName) {
  1904. isFileList = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
  1905. redrawList();
  1906. } else if ("transferHandler" == propertyName) {
  1907. DropTarget dropTarget = list.getDropTarget();
  1908. if (dropTarget instanceof UIResource) {
  1909. try {
  1910. dropTarget.addDropTargetListener(new ListDropTargetListener());
  1911. } catch (TooManyListenersException tmle) {
  1912. // should not happen... swing drop target is multicast
  1913. }
  1914. }
  1915. }
  1916. }
  1917. //
  1918. // ListDataListener
  1919. //
  1920. public void intervalAdded(ListDataEvent e) {
  1921. updateLayoutStateNeeded = modelChanged;
  1922. int minIndex = Math.min(e.getIndex0(), e.getIndex1());
  1923. int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
  1924. /* Sync the SelectionModel with the DataModel.
  1925. */
  1926. ListSelectionModel sm = list.getSelectionModel();
  1927. if (sm != null) {
  1928. sm.insertIndexInterval(minIndex, maxIndex - minIndex+1, true);
  1929. }
  1930. /* Repaint the entire list, from the origin of
  1931. * the first added cell, to the bottom of the
  1932. * component.
  1933. */
  1934. redrawList();
  1935. }
  1936. public void intervalRemoved(ListDataEvent e)
  1937. {
  1938. updateLayoutStateNeeded = modelChanged;
  1939. /* Sync the SelectionModel with the DataModel.
  1940. */
  1941. ListSelectionModel sm = list.getSelectionModel();
  1942. if (sm != null) {
  1943. sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
  1944. }
  1945. /* Repaint the entire list, from the origin of
  1946. * the first removed cell, to the bottom of the
  1947. * component.
  1948. */
  1949. redrawList();
  1950. }
  1951. public void contentsChanged(ListDataEvent e) {
  1952. updateLayoutStateNeeded = modelChanged;
  1953. redrawList();
  1954. }
  1955. //
  1956. // ListSelectionListener
  1957. //
  1958. public void valueChanged(ListSelectionEvent e) {
  1959. maybeUpdateLayoutState();
  1960. Rectangle bounds = getCellBounds(list, e.getFirstIndex(),
  1961. e.getLastIndex());
  1962. if (bounds != null) {
  1963. list.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
  1964. }
  1965. }
  1966. //
  1967. // MouseListener
  1968. //
  1969. private boolean selectedOnPress;
  1970. public void mouseClicked(MouseEvent e) {
  1971. }
  1972. public void mouseEntered(MouseEvent e) {
  1973. }
  1974. public void mouseExited(MouseEvent e) {
  1975. }
  1976. public void mousePressed(MouseEvent e) {
  1977. if (e.isConsumed()) {
  1978. selectedOnPress = false;
  1979. return;
  1980. }
  1981. selectedOnPress = true;
  1982. adjustFocusAndSelection(e);
  1983. }
  1984. private void adjustFocusAndSelection(MouseEvent e) {
  1985. if (!SwingUtilities.isLeftMouseButton(e)) {
  1986. return;
  1987. }
  1988. if (!list.isEnabled()) {
  1989. return;
  1990. }
  1991. /* Request focus before updating the list selection. This implies
  1992. * that the current focus owner will see a focusLost() event
  1993. * before the lists selection is updated IF requestFocus() is
  1994. * synchronous (it is on Windows). See bug 4122345
  1995. */
  1996. if (!list.hasFocus() && list.isRequestFocusEnabled()) {
  1997. list.requestFocus();
  1998. }
  1999. int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
  2000. if (row < 0) {
  2001. // If shift is down in multi-select, we should do nothing.
  2002. // For single select or non-shift-click, clear the selection
  2003. if (isFileList &&
  2004. e.getID() == MouseEvent.MOUSE_PRESSED &&
  2005. (!e.isShiftDown() ||
  2006. list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
  2007. list.clearSelection();
  2008. }
  2009. }
  2010. else {
  2011. boolean adjusting = (e.getID() == MouseEvent.MOUSE_PRESSED) ? true : false;
  2012. list.setValueIsAdjusting(adjusting);
  2013. int anchorIndex = list.getAnchorSelectionIndex();
  2014. if (e.isControlDown()) {
  2015. if (list.isSelectedIndex(row)) {
  2016. list.removeSelectionInterval(row, row);
  2017. }
  2018. else {
  2019. list.addSelectionInterval(row, row);
  2020. }
  2021. }
  2022. else if (e.isShiftDown() && (anchorIndex != -1)) {
  2023. list.setSelectionInterval(anchorIndex, row);
  2024. }
  2025. else {
  2026. list.setSelectionInterval(row, row);
  2027. }
  2028. }
  2029. }
  2030. public void mouseDragged(MouseEvent e) {
  2031. if (e.isConsumed()) {
  2032. return;
  2033. }
  2034. if (!SwingUtilities.isLeftMouseButton(e)) {
  2035. return;
  2036. }
  2037. if (!list.isEnabled()) {
  2038. return;
  2039. }
  2040. if (e.isShiftDown() || e.isControlDown()) {
  2041. return;
  2042. }
  2043. int row = locationToIndex(list, e.getPoint());
  2044. if (row != -1) {
  2045. // 4835633. Dragging onto a File should not select it.
  2046. if (isFileList) {
  2047. return;
  2048. }
  2049. Rectangle cellBounds = getCellBounds(list, row, row);
  2050. if (cellBounds != null) {
  2051. list.scrollRectToVisible(cellBounds);
  2052. list.setSelectionInterval(row, row);
  2053. }
  2054. }
  2055. }
  2056. public void mouseMoved(MouseEvent e) {
  2057. }
  2058. public void mouseReleased(MouseEvent e) {
  2059. if (selectedOnPress) {
  2060. if (!SwingUtilities.isLeftMouseButton(e)) {
  2061. return;
  2062. }
  2063. list.setValueIsAdjusting(false);
  2064. } else {
  2065. adjustFocusAndSelection(e);
  2066. }
  2067. }
  2068. //
  2069. // FocusListener
  2070. //
  2071. protected void repaintCellFocus()
  2072. {
  2073. int leadIndex = list.getLeadSelectionIndex();
  2074. if (leadIndex != -1) {
  2075. Rectangle r = getCellBounds(list, leadIndex, leadIndex);
  2076. if (r != null) {
  2077. list.repaint(r.x, r.y, r.width, r.height);
  2078. }
  2079. }
  2080. }
  2081. /* The focusGained() focusLost() methods run when the JList
  2082. * focus changes.
  2083. */
  2084. public void focusGained(FocusEvent e) {
  2085. repaintCellFocus();
  2086. }
  2087. public void focusLost(FocusEvent e) {
  2088. repaintCellFocus();
  2089. }
  2090. }
  2091. private static final ListDragGestureRecognizer defaultDragRecognizer = new ListDragGestureRecognizer();
  2092. /**
  2093. * Drag gesture recognizer for JList components
  2094. */
  2095. static class ListDragGestureRecognizer extends BasicDragGestureRecognizer {
  2096. /**
  2097. * Determines if the following are true:
  2098. * <ul>
  2099. * <li>the press event is located over a selection
  2100. * <li>the dragEnabled property is true
  2101. * <li>A TranferHandler is installed
  2102. * </ul>
  2103. * <p>
  2104. * This is implemented to perform the superclass behavior
  2105. * followed by a check if the dragEnabled
  2106. * property is set and if the location picked is selected.
  2107. */
  2108. protected boolean isDragPossible(MouseEvent e) {
  2109. if (super.isDragPossible(e)) {
  2110. JList list = (JList) this.getComponent(e);
  2111. if (list.getDragEnabled()) {
  2112. ListUI ui = list.getUI();
  2113. int row = SwingUtilities2.loc2IndexFileList(list,
  2114. e.getPoint());
  2115. if ((row != -1) && list.isSelectedIndex(row)) {
  2116. return true;
  2117. }
  2118. }
  2119. }
  2120. return false;
  2121. }
  2122. }
  2123. /**
  2124. * A DropTargetListener to extend the default Swing handling of drop operations
  2125. * by moving the list selection to the nearest location to the mouse pointer.
  2126. * Also adds autoscroll.
  2127. */
  2128. class ListDropTargetListener extends BasicDropTargetListener {
  2129. /**
  2130. * called to save the state of a component in case it needs to
  2131. * be restored because a drop is not performed.
  2132. */
  2133. protected void saveComponentState(JComponent comp) {
  2134. JList list = (JList) comp;
  2135. selectedIndices = list.getSelectedIndices();
  2136. }
  2137. /**
  2138. * called to restore the state of a component
  2139. * because a drop was not performed.
  2140. */
  2141. protected void restoreComponentState(JComponent comp) {
  2142. JList list = (JList) comp;
  2143. list.setSelectedIndices(selectedIndices);
  2144. }
  2145. /**
  2146. * called to set the insertion location to match the current
  2147. * mouse pointer coordinates.
  2148. */
  2149. protected void updateInsertionLocation(JComponent comp, Point p) {
  2150. JList list = (JList) comp;
  2151. int index = locationToIndex(list, p);
  2152. if (index != -1) {
  2153. list.setSelectionInterval(index, index);
  2154. }
  2155. }
  2156. private int[] selectedIndices;
  2157. }
  2158. private static final TransferHandler defaultTransferHandler = new ListTransferHandler();
  2159. static class ListTransferHandler extends TransferHandler implements UIResource {
  2160. /**
  2161. * Create a Transferable to use as the source for a data transfer.
  2162. *
  2163. * @param c The component holding the data to be transfered. This
  2164. * argument is provided to enable sharing of TransferHandlers by
  2165. * multiple components.
  2166. * @return The representation of the data to be transfered.
  2167. *
  2168. */
  2169. protected Transferable createTransferable(JComponent c) {
  2170. if (c instanceof JList) {
  2171. JList list = (JList) c;
  2172. Object[] values = list.getSelectedValues();
  2173. if (values == null || values.length == 0) {
  2174. return null;
  2175. }
  2176. StringBuffer plainBuf = new StringBuffer();
  2177. StringBuffer htmlBuf = new StringBuffer();
  2178. htmlBuf.append("<html>\n<body>\n<ul>\n");
  2179. for (int i = 0; i < values.length; i++) {
  2180. Object obj = values[i];
  2181. String val = ((obj == null) ? "" : obj.toString());
  2182. plainBuf.append(val + "\n");
  2183. htmlBuf.append(" <li>" + val + "\n");
  2184. }
  2185. // remove the last newline
  2186. plainBuf.deleteCharAt(plainBuf.length() - 1);
  2187. htmlBuf.append("</ul>\n</body>\n</html>");
  2188. return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
  2189. }
  2190. return null;
  2191. }
  2192. public int getSourceActions(JComponent c) {
  2193. return COPY;
  2194. }
  2195. }
  2196. }