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