1. /*
  2. * @(#)BasicListUI.java 1.54 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.plaf.basic;
  11. import javax.swing.*;
  12. import javax.swing.event.*;
  13. import javax.swing.plaf.*;
  14. import java.awt.*;
  15. import java.awt.event.*;
  16. import java.beans.PropertyChangeListener;
  17. import java.beans.PropertyChangeEvent;
  18. /**
  19. * A Windows L&F implementation of ListUI.
  20. * <p>
  21. *
  22. * @version 1.54 02/02/00
  23. * @author Hans Muller
  24. * @author Philip Milne
  25. */
  26. public class BasicListUI extends ListUI
  27. {
  28. protected JList list = null;
  29. protected CellRendererPane rendererPane;
  30. // Listeners that this UI attaches to the JList
  31. protected FocusListener focusListener;
  32. protected MouseInputListener mouseInputListener;
  33. protected ListSelectionListener listSelectionListener;
  34. protected ListDataListener listDataListener;
  35. protected PropertyChangeListener propertyChangeListener;
  36. // PENDING(hmuller) need a doc pointer to #getRowHeight, #maybeUpdateLayoutState
  37. protected int[] cellHeights = null;
  38. protected int cellHeight = -1;
  39. protected int cellWidth = -1;
  40. protected int updateLayoutStateNeeded = modelChanged;
  41. /* The bits below define JList property changes that affect layout.
  42. * When one of these properties changes we set a bit in
  43. * updateLayoutStateNeeded. The change is dealt with lazily, see
  44. * maybeUpdateLayoutState. Changes to the JLists model, e.g. the
  45. * models length changed, are handled similarly, see DataListener.
  46. */
  47. protected final static int modelChanged = 1 << 0;
  48. protected final static int selectionModelChanged = 1 << 1;
  49. protected final static int fontChanged = 1 << 2;
  50. protected final static int fixedCellWidthChanged = 1 << 3;
  51. protected final static int fixedCellHeightChanged = 1 << 4;
  52. protected final static int prototypeCellValueChanged = 1 << 5;
  53. protected final static int cellRendererChanged = 1 << 6;
  54. /**
  55. * Paint one List cell: compute the relevant state, get the "rubber stamp"
  56. * cell renderer component, and then use the CellRendererPane to paint it.
  57. * Subclasses may want to override this method rather than paint().
  58. *
  59. * @see #paint
  60. */
  61. protected void paintCell(
  62. Graphics g,
  63. int row,
  64. Rectangle rowBounds,
  65. ListCellRenderer cellRenderer,
  66. ListModel dataModel,
  67. ListSelectionModel selModel,
  68. int leadIndex)
  69. {
  70. Object value = dataModel.getElementAt(row);
  71. boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
  72. boolean isSelected = selModel.isSelectedIndex(row);
  73. Component rendererComponent =
  74. cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
  75. int cx = rowBounds.x;
  76. int cy = rowBounds.y;
  77. int cw = rowBounds.width;
  78. int ch = rowBounds.height;
  79. rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
  80. }
  81. /**
  82. * Paint the rows that intersect the Graphics objects clipRect. This
  83. * method calls paintCell as necessary. Subclasses
  84. * may want to override these methods.
  85. *
  86. * @see #paintCell
  87. */
  88. public void paint(Graphics g, JComponent c)
  89. {
  90. maybeUpdateLayoutState();
  91. ListCellRenderer renderer = list.getCellRenderer();
  92. ListModel dataModel = list.getModel();
  93. ListSelectionModel selModel = list.getSelectionModel();
  94. if ((renderer == null) || (dataModel.getSize() == 0)) {
  95. return;
  96. }
  97. /* Compute the area we're going to paint in terms of the affected
  98. * rows (firstPaintRow, lastPaintRow), and the clip bounds.
  99. */
  100. Rectangle paintBounds = g.getClipBounds();
  101. int firstPaintRow = convertYToRow(paintBounds.y);
  102. int lastPaintRow = convertYToRow((paintBounds.y + paintBounds.height) - 1);
  103. if (firstPaintRow == -1) {
  104. firstPaintRow = 0;
  105. }
  106. if (lastPaintRow == -1) {
  107. lastPaintRow = dataModel.getSize() - 1;
  108. }
  109. Rectangle rowBounds = getCellBounds(list, firstPaintRow, firstPaintRow);
  110. if (rowBounds == null) {
  111. return;
  112. }
  113. int leadIndex = list.getLeadSelectionIndex();
  114. for(int row = firstPaintRow; row <= lastPaintRow; row++) {
  115. rowBounds.height = getRowHeight(row);
  116. /* Set the clip rect to be the intersection of rowBounds
  117. * and paintBounds and then paint the cell.
  118. */
  119. g.setClip(rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height);
  120. g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width, paintBounds.height);
  121. paintCell(g, row, rowBounds, renderer, dataModel, selModel, leadIndex);
  122. rowBounds.y += rowBounds.height;
  123. }
  124. }
  125. /**
  126. * The preferredSize of a list is total height of the rows
  127. * and the maximum width of the cells. If JList.fixedCellHeight
  128. * is specified then the total height of the rows is just
  129. * (cellVerticalMargins + fixedCellHeight) * model.getSize() where
  130. * rowVerticalMargins is the space we allocate for drawing
  131. * the yellow focus outline. Similarly if JListfixedCellWidth is
  132. * specified then we just use that plus the horizontal margins.
  133. *
  134. * @param c The JList component.
  135. * @return The total size of the list.
  136. */
  137. public Dimension getPreferredSize(JComponent c) {
  138. maybeUpdateLayoutState();
  139. int lastRow = list.getModel().getSize() - 1;
  140. if (lastRow < 0) {
  141. return new Dimension(0, 0);
  142. }
  143. Insets insets = list.getInsets();
  144. int width = cellWidth + insets.left + insets.right;
  145. int height = convertRowToY(lastRow) + getRowHeight(lastRow) + insets.bottom;
  146. return new Dimension(width, height);
  147. }
  148. /**
  149. * @returns The preferred size.
  150. * @see #getPreferredSize
  151. */
  152. public Dimension getMinimumSize(JComponent c) {
  153. return getPreferredSize(c);
  154. }
  155. /**
  156. * @returns The preferred size.
  157. * @see #getPreferredSize
  158. */
  159. public Dimension getMaximumSize(JComponent c) {
  160. return getPreferredSize(c);
  161. }
  162. /**
  163. * Selected the previous row and force it to be visible.
  164. * Called by the KeyEvent.VK_UP keyboard action.
  165. *
  166. * @see #installKeyboardActions
  167. * @see JList#ensureIndexIsVisible
  168. */
  169. protected void selectPreviousIndex() {
  170. int s = list.getSelectedIndex();
  171. if(s > 0) {
  172. s -= 1;
  173. list.setSelectedIndex(s);
  174. list.ensureIndexIsVisible(s);
  175. }
  176. }
  177. /**
  178. * Selected the previous row and force it to be visible.
  179. * Called by the KeyEvent.VK_DOWN keyboard action.
  180. *
  181. * @see #installKeyboardActions
  182. * @see JList#ensureIndexIsVisible
  183. */
  184. protected void selectNextIndex()
  185. {
  186. int s = list.getSelectedIndex();
  187. if((s + 1) < list.getModel().getSize()) {
  188. s += 1;
  189. list.setSelectedIndex(s);
  190. list.ensureIndexIsVisible(s);
  191. }
  192. }
  193. /**
  194. * Register keyboard actions for the up and down arrow keys. The
  195. * actions just call out to protected methods, subclasses that
  196. * want to override or extend keyboard behavior should consider
  197. * just overriding those methods. This method is called at
  198. * installUI() time.
  199. *
  200. * @see #selectPreviousIndex
  201. * @see #selectNextIndex
  202. * @see #installUI
  203. */
  204. protected void installKeyboardActions() {
  205. InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
  206. SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
  207. inputMap);
  208. ActionMap map = getActionMap();
  209. if (map != null) {
  210. SwingUtilities.replaceUIActionMap(list, map);
  211. }
  212. }
  213. InputMap getInputMap(int condition) {
  214. if (condition == JComponent.WHEN_FOCUSED) {
  215. return (InputMap)UIManager.get("List.focusInputMap");
  216. }
  217. return null;
  218. }
  219. ActionMap getActionMap() {
  220. ActionMap map = (ActionMap)UIManager.get("List.actionMap");
  221. if (map == null) {
  222. map = createActionMap();
  223. if (map != null) {
  224. UIManager.put("List.actionMap", map);
  225. }
  226. }
  227. return map;
  228. }
  229. ActionMap createActionMap() {
  230. ActionMap map = new ActionMapUIResource();
  231. map.put("selectPreviousRow",
  232. new IncrementLeadSelectionAction("selectPreviousRow",
  233. CHANGE_SELECTION, -1));
  234. map.put("selectPreviousRowExtendSelection",
  235. new IncrementLeadSelectionAction
  236. ("selectPreviousRowExtendSelection",EXTEND_SELECTION, -1));
  237. map.put("selectNextRow",
  238. new IncrementLeadSelectionAction("selectNextRow",
  239. CHANGE_SELECTION, 1));
  240. map.put("selectNextRowExtendSelection",
  241. new IncrementLeadSelectionAction
  242. ("selectNextRowExtendSelection", EXTEND_SELECTION, 1));
  243. map.put("selectFirstRow",
  244. new HomeAction("selectFirstRow", CHANGE_SELECTION));
  245. map.put("selectFirstRowExtendSelection",
  246. new HomeAction("selectFirstRowExtendSelection",
  247. EXTEND_SELECTION));
  248. map.put("selectLastRow",
  249. new EndAction("selctLastRow", CHANGE_SELECTION));
  250. map.put("selectLastRowExtendSelection",
  251. new EndAction("selectLastRowExtendSelection",
  252. EXTEND_SELECTION));
  253. map.put("scrollUp",
  254. new PageUpAction("scrollUp", CHANGE_SELECTION));
  255. map.put("scrollUpExtendSelection",
  256. new PageUpAction("scrollUpExtendSelection",
  257. EXTEND_SELECTION));
  258. map.put("scrollDown",
  259. new PageDownAction("scrollDown", CHANGE_SELECTION));
  260. map.put("scrollDownExtendSelection",
  261. new PageDownAction("scrollDownExtendSelection",
  262. EXTEND_SELECTION));
  263. map.put("selectAll", new SelectAllAction("selectAll"));
  264. map.put("clearSelection", new
  265. ClearSelectionAction("clearSelection"));
  266. return map;
  267. }
  268. /**
  269. * Unregister keyboard actions for the up and down arrow keys.
  270. * This method is called at uninstallUI() time - subclassess should
  271. * ensure that all of the keyboard actions registered at installUI
  272. * time are removed here.
  273. *
  274. * @see #selectPreviousIndex
  275. * @see #selectNextIndex
  276. * @see #installUI
  277. */
  278. protected void uninstallKeyboardActions() {
  279. SwingUtilities.replaceUIActionMap(list, null);
  280. SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null);
  281. }
  282. /**
  283. * Create and install the listeners for the JList, its model, and its
  284. * selectionModel. This method is called at installUI() time.
  285. *
  286. * @see #installUI
  287. * @see #uninstallListeners
  288. */
  289. protected void installListeners()
  290. {
  291. focusListener = createFocusListener();
  292. mouseInputListener = createMouseInputListener();
  293. propertyChangeListener = createPropertyChangeListener();
  294. listSelectionListener = createListSelectionListener();
  295. listDataListener = createListDataListener();
  296. list.addFocusListener(focusListener);
  297. list.addMouseListener(mouseInputListener);
  298. list.addMouseMotionListener(mouseInputListener);
  299. list.addPropertyChangeListener(propertyChangeListener);
  300. ListModel model = list.getModel();
  301. if (model != null) {
  302. model.addListDataListener(listDataListener);
  303. }
  304. ListSelectionModel selectionModel = list.getSelectionModel();
  305. if (selectionModel != null) {
  306. selectionModel.addListSelectionListener(listSelectionListener);
  307. }
  308. }
  309. /**
  310. * Remove the listeners for the JList, its model, and its
  311. * selectionModel. All of the listener fields, are reset to
  312. * null here. This method is called at uninstallUI() time,
  313. * it should be kept in sync with installListeners.
  314. *
  315. * @see #uninstallUI
  316. * @see #installListeners
  317. */
  318. protected void uninstallListeners()
  319. {
  320. list.removeFocusListener(focusListener);
  321. list.removeMouseListener(mouseInputListener);
  322. list.removeMouseMotionListener(mouseInputListener);
  323. list.removePropertyChangeListener(propertyChangeListener);
  324. ListModel model = list.getModel();
  325. if (model != null) {
  326. model.removeListDataListener(listDataListener);
  327. }
  328. ListSelectionModel selectionModel = list.getSelectionModel();
  329. if (selectionModel != null) {
  330. selectionModel.removeListSelectionListener(listSelectionListener);
  331. }
  332. focusListener = null;
  333. mouseInputListener = null;
  334. listSelectionListener = null;
  335. listDataListener = null;
  336. propertyChangeListener = null;
  337. }
  338. /**
  339. * Initialize JList properties, e.g. font, foreground, and background,
  340. * and add the CellRendererPane. The font, foreground, and background
  341. * properties are only set if their current value is either null
  342. * or a UIResource, other properties are set if the current
  343. * value is null.
  344. *
  345. * @see #uninstallDefaults
  346. * @see #installUI
  347. * @see CellRendererPane
  348. */
  349. protected void installDefaults()
  350. {
  351. list.setLayout(null);
  352. LookAndFeel.installBorder(list, "List.border");
  353. LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
  354. if (list.getCellRenderer() == null) {
  355. list.setCellRenderer((ListCellRenderer)(UIManager.get("List.cellRenderer")));
  356. }
  357. Color sbg = list.getSelectionBackground();
  358. if (sbg == null || sbg instanceof UIResource) {
  359. list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
  360. }
  361. Color sfg = list.getSelectionForeground();
  362. if (sfg == null || sfg instanceof UIResource) {
  363. list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
  364. }
  365. }
  366. /**
  367. * Set the JList properties that haven't been explicitly overriden to
  368. * null. A property is considered overridden if its current value
  369. * is not a UIResource.
  370. *
  371. * @see #installDefaults
  372. * @see #uninstallUI
  373. * @see CellRendererPane
  374. */
  375. protected void uninstallDefaults()
  376. {
  377. if (list.getCellRenderer() instanceof UIResource) {
  378. list.setCellRenderer(null);
  379. }
  380. }
  381. /**
  382. * Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
  383. * <code>installListeners()</code>, and <code>installKeyboardActions()</code>
  384. * in order.
  385. *
  386. * @see #installDefaults
  387. * @see #installListeners
  388. * @see #installKeyboardActions
  389. */
  390. public void installUI(JComponent c)
  391. {
  392. list = (JList)c;
  393. rendererPane = new CellRendererPane();
  394. list.add(rendererPane);
  395. installDefaults();
  396. installListeners();
  397. installKeyboardActions();
  398. }
  399. /**
  400. * Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
  401. * <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
  402. * in order. Sets this.list to null.
  403. *
  404. * @see #uninstallListeners
  405. * @see #uninstallKeyboardActions
  406. * @see #uninstallDefaults
  407. */
  408. public void uninstallUI(JComponent c)
  409. {
  410. uninstallDefaults();
  411. uninstallListeners();
  412. uninstallKeyboardActions();
  413. cellWidth = cellHeight = -1;
  414. cellHeights = null;
  415. list.remove(rendererPane);
  416. rendererPane = null;
  417. list = null;
  418. }
  419. /**
  420. * Returns a new instance of BasicListUI. BasicListUI delegates are
  421. * allocated one per JList.
  422. *
  423. * @return A new ListUI implementation for the Windows look and feel.
  424. */
  425. public static ComponentUI createUI(JComponent list) {
  426. return new BasicListUI();
  427. }
  428. /**
  429. * @return The index of the cell at location, or -1.
  430. * @see ListUI#locationToIndex
  431. */
  432. public int locationToIndex(JList list, Point location) {
  433. maybeUpdateLayoutState();
  434. return convertYToRow(location.y);
  435. }
  436. /**
  437. * @return The origin of the index'th cell, null if index is invalid.
  438. * @see ListUI#indexToLocation
  439. */
  440. public Point indexToLocation(JList list, int index) {
  441. maybeUpdateLayoutState();
  442. int y = convertRowToY(index);
  443. return (y == -1) ? null : new Point(0, y);
  444. }
  445. /**
  446. * @return The bounds of the index'th cell.
  447. * @see ListUI#getCellBounds
  448. */
  449. public Rectangle getCellBounds(JList list, int index1, int index2) {
  450. maybeUpdateLayoutState();
  451. int minIndex = Math.min(index1, index2);
  452. int maxIndex = Math.max(index1, index2);
  453. int minY = convertRowToY(minIndex);
  454. int maxY = convertRowToY(maxIndex);
  455. if ((minY == -1) || (maxY == -1)) {
  456. return null;
  457. }
  458. Insets insets = list.getInsets();
  459. int x = insets.left;
  460. int y = minY;
  461. int w = list.getWidth() - (insets.left + insets.right);
  462. int h = (maxY + getRowHeight(maxIndex)) - minY;
  463. return new Rectangle(x, y, w, h);
  464. }
  465. // PENDING(hmuller) explain the cell geometry abstraction in
  466. // the getRowHeight javadoc
  467. /**
  468. * Returns the height of the specified row based on the current layout.
  469. *
  470. * @return The specified row height or -1 if row isn't valid.
  471. * @see #convertYToRow
  472. * @see #convertRowToY
  473. * @see #updateLayoutState
  474. */
  475. protected int getRowHeight(int row)
  476. {
  477. if ((row < 0) || (row >= list.getModel().getSize())) {
  478. return -1;
  479. }
  480. return (cellHeights == null) ? cellHeight : ((row < cellHeights.length) ? cellHeights[row] : -1);
  481. }
  482. /**
  483. * Convert the JList relative coordinate to the row that contains it,
  484. * based on the current layout. If y0 doesn't fall within any row,
  485. * return -1.
  486. *
  487. * @return The row that contains y0, or -1.
  488. * @see #getRowHeight
  489. * @see #updateLayoutState
  490. */
  491. protected int convertYToRow(int y0)
  492. {
  493. int nrows = list.getModel().getSize();
  494. Insets insets = list.getInsets();
  495. if (nrows <= 0) {
  496. return -1;
  497. }
  498. else if (cellHeights == null) {
  499. int row = (cellHeight == 0) ? 0 : ((y0 - insets.top) / cellHeight);
  500. return ((row < 0) || (row >= nrows)) ? -1 : row;
  501. }
  502. else if (nrows > cellHeights.length) {
  503. return -1;
  504. }
  505. else {
  506. int y = insets.top;
  507. int row = 0;
  508. for(int i = 0; i < nrows; i++) {
  509. if ((y0 >= y) && (y0 < y + cellHeights[i])) {
  510. return row;
  511. }
  512. y += cellHeights[i];
  513. row += 1;
  514. }
  515. return -1;
  516. }
  517. }
  518. /**
  519. * Return the JList relative Y coordinate of the origin of the specified
  520. * row or -1 if row isn't valid.
  521. *
  522. * @return The Y coordinate of the origin of row, or -1.
  523. * @see #getRowHeight
  524. * @see #updateLayoutState
  525. */
  526. protected int convertRowToY(int row)
  527. {
  528. int nrows = list.getModel().getSize();
  529. Insets insets = list.getInsets();
  530. if ((row < 0) || (row >= nrows)) {
  531. return -1;
  532. }
  533. if (cellHeights == null) {
  534. return insets.top + (cellHeight * row);
  535. }
  536. else if (row >= cellHeights.length) {
  537. return -1;
  538. }
  539. else {
  540. int y = insets.top;
  541. for(int i = 0; i < row; i++) {
  542. y += cellHeights[i];
  543. }
  544. return y;
  545. }
  546. }
  547. /**
  548. * If updateLayoutStateNeeded is non zero, call updateLayoutState() and reset
  549. * updateLayoutStateNeeded. This method should be called by methods
  550. * before doing any computation based on the geometry of the list.
  551. * For example it's the first call in paint() and getPreferredSize().
  552. *
  553. * @see #updateLayoutState
  554. */
  555. protected void maybeUpdateLayoutState()
  556. {
  557. if (updateLayoutStateNeeded != 0) {
  558. updateLayoutState();
  559. updateLayoutStateNeeded = 0;
  560. }
  561. }
  562. /**
  563. * Recompute the value of cellHeight or cellHeights based
  564. * and cellWidth, based on the current font and the current
  565. * values of fixedCellWidth, fixedCellHeight, and prototypeCellValue.
  566. *
  567. * @see #maybeUpdateLayoutState
  568. */
  569. protected void updateLayoutState()
  570. {
  571. /* If both JList fixedCellWidth and fixedCellHeight have been
  572. * set, then initialize cellWidth and cellHeight, and set
  573. * cellHeights to null.
  574. */
  575. int fixedCellHeight = list.getFixedCellHeight();
  576. int fixedCellWidth = list.getFixedCellWidth();
  577. cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
  578. if (fixedCellHeight != -1) {
  579. cellHeight = fixedCellHeight;
  580. cellHeights = null;
  581. }
  582. else {
  583. cellHeight = -1;
  584. cellHeights = new int[list.getModel().getSize()];
  585. }
  586. /* If either of JList fixedCellWidth and fixedCellHeight haven't
  587. * been set, then initialize cellWidth and cellHeights by
  588. * scanning through the entire model. Note: if the renderer is
  589. * null, we just set cellWidth and cellHeights[*] to zero,
  590. * if they're not set already.
  591. */
  592. if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
  593. ListModel dataModel = list.getModel();
  594. int dataModelSize = dataModel.getSize();
  595. ListCellRenderer renderer = list.getCellRenderer();
  596. if (renderer != null) {
  597. for(int index = 0; index < dataModelSize; index++) {
  598. Object value = dataModel.getElementAt(index);
  599. Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
  600. rendererPane.add(c);
  601. Dimension cellSize = c.getPreferredSize();
  602. if (fixedCellWidth == -1) {
  603. cellWidth = Math.max(cellSize.width, cellWidth);
  604. }
  605. if (fixedCellHeight == -1) {
  606. cellHeights[index] = cellSize.height;
  607. }
  608. }
  609. }
  610. else {
  611. if (cellWidth == -1) {
  612. cellWidth = 0;
  613. }
  614. for(int index = 0; index < dataModelSize; index++) {
  615. cellHeights[index] = 0;
  616. }
  617. }
  618. }
  619. list.invalidate();
  620. }
  621. /**
  622. * Mouse input, and focus handling for JList. An instance of this
  623. * class is added to the appropriate java.awt.Component lists
  624. * at installUI() time. Note keyboard input is handled with JComponent
  625. * KeyboardActions, see installKeyboardActions().
  626. * <p>
  627. * <strong>Warning:</strong>
  628. * Serialized objects of this class will not be compatible with
  629. * future Swing releases. The current serialization support is appropriate
  630. * for short term storage or RMI between applications running the same
  631. * version of Swing. A future release of Swing will provide support for
  632. * long term persistence.
  633. *
  634. * @see #createMouseInputListener
  635. * @see #installKeyboardActions
  636. * @see #installUI
  637. */
  638. public class MouseInputHandler implements MouseInputListener
  639. {
  640. public void mouseClicked(MouseEvent e) {}
  641. public void mouseEntered(MouseEvent e) {}
  642. public void mouseExited(MouseEvent e) {}
  643. public void mousePressed(MouseEvent e)
  644. {
  645. if (!SwingUtilities.isLeftMouseButton(e)) {
  646. return;
  647. }
  648. if (!list.isEnabled()) {
  649. return;
  650. }
  651. /* Request focus before updating the list selection. This implies
  652. * that the current focus owner will see a focusLost() event
  653. * before the lists selection is updated IF requestFocus() is
  654. * synchronous (it is on Windows). See bug 4122345
  655. */
  656. if (!list.hasFocus()) {
  657. list.requestFocus();
  658. }
  659. int row = convertYToRow(e.getY());
  660. if (row != -1) {
  661. list.setValueIsAdjusting(true);
  662. int anchorIndex = list.getAnchorSelectionIndex();
  663. if (e.isControlDown()) {
  664. if (list.isSelectedIndex(row)) {
  665. list.removeSelectionInterval(row, row);
  666. }
  667. else {
  668. list.addSelectionInterval(row, row);
  669. }
  670. }
  671. else if (e.isShiftDown() && (anchorIndex != -1)) {
  672. list.setSelectionInterval(anchorIndex, row);
  673. }
  674. else {
  675. list.setSelectionInterval(row, row);
  676. }
  677. }
  678. }
  679. public void mouseDragged(MouseEvent e) {
  680. if (!SwingUtilities.isLeftMouseButton(e)) {
  681. return;
  682. }
  683. if (!list.isEnabled()) {
  684. return;
  685. }
  686. if (e.isShiftDown() || e.isControlDown()) {
  687. return;
  688. }
  689. int row = convertYToRow(e.getY());
  690. if (row != -1) {
  691. Rectangle cellBounds = getCellBounds(list, row, row);
  692. if (cellBounds != null) {
  693. list.scrollRectToVisible(cellBounds);
  694. list.setSelectionInterval(row, row);
  695. }
  696. }
  697. }
  698. public void mouseMoved(MouseEvent e) {
  699. }
  700. public void mouseReleased(MouseEvent e) {
  701. if (!SwingUtilities.isLeftMouseButton(e)) {
  702. return;
  703. }
  704. list.setValueIsAdjusting(false);
  705. }
  706. }
  707. /**
  708. * Creates a delegate that implements MouseInputListener.
  709. * The delegate is added to the corresponding java.awt.Component listener
  710. * lists at installUI() time. Subclasses can override this method to return
  711. * a custom MouseInputListener, e.g.
  712. * <pre>
  713. * class MyListUI extends BasicListUI {
  714. * protected MouseInputListener <b>createMouseInputListener</b>() {
  715. * return new MyMouseInputHandler();
  716. * }
  717. * public class MyMouseInputHandler extends MouseInputHandler {
  718. * public void mouseMoved(MouseEvent e) {
  719. * // do some extra work when the mouse moves
  720. * super.mouseMoved(e);
  721. * }
  722. * }
  723. * }
  724. * </pre>
  725. *
  726. * @see MouseInputHandler
  727. * @see #installUI
  728. */
  729. protected MouseInputListener createMouseInputListener() {
  730. return new MouseInputHandler();
  731. }
  732. /**
  733. * This inner class is marked "public" due to a compiler bug.
  734. * This class should be treated as a "protected" inner class.
  735. * Instantiate it only within subclasses of BasicTableUI.
  736. */
  737. public class FocusHandler implements FocusListener
  738. {
  739. protected void repaintCellFocus()
  740. {
  741. int leadIndex = list.getLeadSelectionIndex();
  742. if (leadIndex != -1) {
  743. Rectangle r = getCellBounds(list, leadIndex, leadIndex);
  744. if (r != null) {
  745. list.repaint(r.x, r.y, r.width, r.height);
  746. }
  747. }
  748. }
  749. /* The focusGained() focusLost() methods run when the JList
  750. * focus changes.
  751. */
  752. public void focusGained(FocusEvent e) {
  753. // hasFocus = true;
  754. repaintCellFocus();
  755. }
  756. public void focusLost(FocusEvent e) {
  757. // hasFocus = false;
  758. repaintCellFocus();
  759. }
  760. }
  761. protected FocusListener createFocusListener() {
  762. return new FocusHandler();
  763. }
  764. /**
  765. * The ListSelectionListener that's added to the JLists selection
  766. * model at installUI time, and whenever the JList.selectionModel property
  767. * changes. When the selection changes we repaint the affected rows.
  768. * <p>
  769. * <strong>Warning:</strong>
  770. * Serialized objects of this class will not be compatible with
  771. * future Swing releases. The current serialization support is appropriate
  772. * for short term storage or RMI between applications running the same
  773. * version of Swing. A future release of Swing will provide support for
  774. * long term persistence.
  775. *
  776. * @see #createListSelectionListener
  777. * @see #getCellBounds
  778. * @see #installUI
  779. */
  780. public class ListSelectionHandler implements ListSelectionListener
  781. {
  782. public void valueChanged(ListSelectionEvent e)
  783. {
  784. maybeUpdateLayoutState();
  785. int minY = convertRowToY(e.getFirstIndex());
  786. int maxY = convertRowToY(e.getLastIndex());
  787. if ((minY == -1) || (maxY == -1)) {
  788. list.repaint(0, 0, list.getWidth(), list.getHeight());
  789. }
  790. else {
  791. maxY += getRowHeight(e.getLastIndex());
  792. list.repaint(0, minY, list.getWidth(), maxY - minY);
  793. }
  794. }
  795. }
  796. /**
  797. * Creates an instance of ListSelectionHandler that's added to
  798. * the JLists by selectionModel as needed. Subclasses can override
  799. * this method to return a custom ListSelectionListener, e.g.
  800. * <pre>
  801. * class MyListUI extends BasicListUI {
  802. * protected ListSelectionListener <b>createListSelectionListener</b>() {
  803. * return new MySelectionListener();
  804. * }
  805. * public class MySelectionListener extends ListSelectionHandler {
  806. * public void valueChanged(ListSelectionEvent e) {
  807. * // do some extra work when the selection changes
  808. * super.valueChange(e);
  809. * }
  810. * }
  811. * }
  812. * </pre>
  813. *
  814. * @see ListSelectionHandler
  815. * @see #installUI
  816. */
  817. protected ListSelectionListener createListSelectionListener() {
  818. return new ListSelectionHandler();
  819. }
  820. private void redrawList() {
  821. list.revalidate();
  822. list.repaint();
  823. }
  824. /**
  825. * The ListDataListener that's added to the JLists model at
  826. * installUI time, and whenever the JList.model property changes.
  827. * <p>
  828. * <strong>Warning:</strong>
  829. * Serialized objects of this class will not be compatible with
  830. * future Swing releases. The current serialization support is appropriate
  831. * for short term storage or RMI between applications running the same
  832. * version of Swing. A future release of Swing will provide support for
  833. * long term persistence.
  834. *
  835. * @see JList#getModel
  836. * @see #maybeUpdateLayoutState
  837. * @see #createListDataListener
  838. * @see #installUI
  839. */
  840. public class ListDataHandler implements ListDataListener
  841. {
  842. public void intervalAdded(ListDataEvent e) {
  843. updateLayoutStateNeeded = modelChanged;
  844. int minIndex = Math.min(e.getIndex0(), e.getIndex1());
  845. int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
  846. /* Sync the SelectionModel with the DataModel.
  847. */
  848. ListSelectionModel sm = list.getSelectionModel();
  849. if (sm != null) {
  850. sm.insertIndexInterval(minIndex, maxIndex - minIndex, true);
  851. }
  852. /* Repaint the entire list, from the origin of
  853. * the first added cell, to the bottom of the
  854. * component.
  855. */
  856. int y = Math.max(0, convertRowToY(minIndex));
  857. int h = list.getHeight() - y;
  858. list.revalidate();
  859. list.repaint(0, y, list.getWidth(), h);
  860. }
  861. public void intervalRemoved(ListDataEvent e)
  862. {
  863. updateLayoutStateNeeded = modelChanged;
  864. /* Sync the SelectionModel with the DataModel.
  865. */
  866. ListSelectionModel sm = list.getSelectionModel();
  867. if (sm != null) {
  868. sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
  869. }
  870. /* Repaint the entire list, from the origin of
  871. * the first removed cell, to the bottom of the
  872. * component.
  873. */
  874. int minIndex = Math.min(e.getIndex0(), e.getIndex1());
  875. int y = Math.max(0, convertRowToY(minIndex));
  876. int h = list.getHeight() - y;
  877. list.revalidate();
  878. list.repaint(0, y, list.getWidth(), h);
  879. }
  880. public void contentsChanged(ListDataEvent e) {
  881. updateLayoutStateNeeded = modelChanged;
  882. redrawList();
  883. }
  884. }
  885. /**
  886. * Creates an instance of ListDataListener that's added to
  887. * the JLists by model as needed. Subclasses can override
  888. * this method to return a custom ListDataListener, e.g.
  889. * <pre>
  890. * class MyListUI extends BasicListUI {
  891. * protected ListDataListener <b>createListDataListener</b>() {
  892. * return new MyListDataListener();
  893. * }
  894. * public class MyListDataListener extends ListDataHandler {
  895. * public void contentsChanged(ListDataEvent e) {
  896. * // do some extra work when the models contents change
  897. * super.contentsChange(e);
  898. * }
  899. * }
  900. * }
  901. * </pre>
  902. *
  903. * @see ListDataListener
  904. * @see JList#getModel
  905. * @see #installUI
  906. */
  907. protected ListDataListener createListDataListener() {
  908. return new ListDataHandler();
  909. }
  910. /**
  911. * The PropertyChangeListener that's added to the JList at
  912. * installUI time. When the value of a JList property that
  913. * affects layout changes, we set a bit in updateLayoutStateNeeded.
  914. * If the JLists model changes we additionally remove our listeners
  915. * from the old model. Likewise for the JList selectionModel.
  916. * <p>
  917. * <strong>Warning:</strong>
  918. * Serialized objects of this class will not be compatible with
  919. * future Swing releases. The current serialization support is appropriate
  920. * for short term storage or RMI between applications running the same
  921. * version of Swing. A future release of Swing will provide support for
  922. * long term persistence.
  923. *
  924. * @see #maybeUpdateLayoutState
  925. * @see #createPropertyChangeListener
  926. * @see #installUI
  927. */
  928. public class PropertyChangeHandler implements PropertyChangeListener
  929. {
  930. public void propertyChange(PropertyChangeEvent e)
  931. {
  932. String propertyName = e.getPropertyName();
  933. /* If the JList.model property changes, remove our listener,
  934. * listDataListener from the old model and add it to the new one.
  935. */
  936. if (propertyName.equals("model")) {
  937. ListModel oldModel = (ListModel)e.getOldValue();
  938. ListModel newModel = (ListModel)e.getNewValue();
  939. if (oldModel != null) {
  940. oldModel.removeListDataListener(listDataListener);
  941. }
  942. if (newModel != null) {
  943. newModel.addListDataListener(listDataListener);
  944. }
  945. updateLayoutStateNeeded |= modelChanged;
  946. redrawList();
  947. }
  948. /* If the JList.selectionModel property changes, remove our listener,
  949. * listSelectionListener from the old selectionModel and add it to the new one.
  950. */
  951. else if (propertyName.equals("selectionModel")) {
  952. ListSelectionModel oldModel = (ListSelectionModel)e.getOldValue();
  953. ListSelectionModel newModel = (ListSelectionModel)e.getNewValue();
  954. if (oldModel != null) {
  955. oldModel.removeListSelectionListener(listSelectionListener);
  956. }
  957. if (newModel != null) {
  958. newModel.addListSelectionListener(listSelectionListener);
  959. }
  960. updateLayoutStateNeeded |= modelChanged;
  961. redrawList();
  962. }
  963. else if (propertyName.equals("cellRenderer")) {
  964. updateLayoutStateNeeded |= cellRendererChanged;
  965. redrawList();
  966. }
  967. else if (propertyName.equals("font")) {
  968. updateLayoutStateNeeded |= fontChanged;
  969. redrawList();
  970. }
  971. else if (propertyName.equals("prototypeCellValue")) {
  972. updateLayoutStateNeeded |= prototypeCellValueChanged;
  973. redrawList();
  974. }
  975. else if (propertyName.equals("fixedCellHeight")) {
  976. updateLayoutStateNeeded |= fixedCellHeightChanged;
  977. redrawList();
  978. }
  979. else if (propertyName.equals("fixedCellWidth")) {
  980. updateLayoutStateNeeded |= fixedCellWidthChanged;
  981. redrawList();
  982. }
  983. else if (propertyName.equals("cellRenderer")) {
  984. updateLayoutStateNeeded |= cellRendererChanged;
  985. redrawList();
  986. }
  987. else if (propertyName.equals("selectionForeground")) {
  988. list.repaint();
  989. }
  990. else if (propertyName.equals("selectionBackground")) {
  991. list.repaint();
  992. }
  993. }
  994. }
  995. /**
  996. * Creates an instance of PropertyChangeHandler that's added to
  997. * the JList by installUI(). Subclasses can override this method
  998. * to return a custom PropertyChangeListener, e.g.
  999. * <pre>
  1000. * class MyListUI extends BasicListUI {
  1001. * protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
  1002. * return new MyPropertyChangeListener();
  1003. * }
  1004. * public class MyPropertyChangeListener extends PropertyChangeHandler {
  1005. * public void propertyChange(PropertyChangeEvent e) {
  1006. * if (e.getPropertyName().equals("model")) {
  1007. * // do some extra work when the model changes
  1008. * }
  1009. * super.propertyChange(e);
  1010. * }
  1011. * }
  1012. * }
  1013. * </pre>
  1014. *
  1015. * @see PropertyChangeListener
  1016. * @see #installUI
  1017. */
  1018. protected PropertyChangeListener createPropertyChangeListener() {
  1019. return new PropertyChangeHandler();
  1020. }
  1021. // Keyboard navigation actions.
  1022. // NOTE: DefaultListSelectionModel.setAnchorSelectionIndex and
  1023. // DefaultListSelectionModel.setLeadSelectionIndex both force the
  1024. // new index to be selected. Because of this not all the bindings
  1025. // could be appropriately implemented. Specifically those that
  1026. // change the lead/anchor without selecting are not enabled.
  1027. // Once this has been fixed the following actions will appropriately
  1028. // work with selectionType == CHANGE_LEAD.
  1029. /** Used by IncrementLeadSelectionAction. Indicates the action should
  1030. * change the lead, and not select it. */
  1031. private static final int CHANGE_LEAD = 0;
  1032. /** Used by IncrementLeadSelectionAction. Indicates the action should
  1033. * change the selection and lead. */
  1034. private static final int CHANGE_SELECTION = 1;
  1035. /** Used by IncrementLeadSelectionAction. Indicates the action should
  1036. * extend the selection from the anchor to the next index. */
  1037. private static final int EXTEND_SELECTION = 2;
  1038. /**
  1039. * Action to increment the selection in the list up/down a row at
  1040. * a type. This also has the option to extend the selection, or
  1041. * only move the lead.
  1042. */
  1043. private static class IncrementLeadSelectionAction extends AbstractAction {
  1044. /** Amount to offset, subclasses will define what this means. */
  1045. protected int amount;
  1046. /** One of CHANGE_LEAD, CHANGE_SELECTION or EXTEND_SELECTION. */
  1047. protected int selectionType;
  1048. protected IncrementLeadSelectionAction(String name, int type) {
  1049. this(name, type, -1);
  1050. }
  1051. protected IncrementLeadSelectionAction(String name, int type,
  1052. int amount) {
  1053. super(name);
  1054. this.amount = amount;
  1055. this.selectionType = type;
  1056. }
  1057. /**
  1058. * Returns the next index to select. This is based on the lead
  1059. * selected index and the <code>amount</code> ivar.
  1060. */
  1061. protected int getNextIndex(JList list) {
  1062. int index = list.getLeadSelectionIndex();
  1063. int size = list.getModel().getSize();
  1064. if (index == -1) {
  1065. if (size > 0) {
  1066. if (amount > 0) {
  1067. index = 0;
  1068. }
  1069. else {
  1070. index = size - 1;
  1071. }
  1072. }
  1073. }
  1074. else {
  1075. index += amount;
  1076. }
  1077. return index;
  1078. }
  1079. /**
  1080. * Ensures the particular index is visible. This simply forwards
  1081. * the method to list.
  1082. */
  1083. protected void ensureIndexIsVisible(JList list, int index) {
  1084. list.ensureIndexIsVisible(index);
  1085. }
  1086. /**
  1087. * Invokes <code>getNextIndex</code> to determine the next index
  1088. * to select. If the index is valid (not -1 and < size of the model),
  1089. * this will either: move the selection to the new index if
  1090. * the selectionType == CHANGE_SELECTION, move the lead to the
  1091. * new index if selectionType == CHANGE_LEAD, otherwise the
  1092. * selection is extend from the anchor to the new index and the
  1093. * lead is set to the new index.
  1094. */
  1095. public void actionPerformed(ActionEvent e) {
  1096. JList list = (JList)e.getSource();
  1097. int index = getNextIndex(list);
  1098. if (index >= 0 && index < list.getModel().getSize()) {
  1099. ListSelectionModel lsm = list.getSelectionModel();
  1100. if (selectionType == EXTEND_SELECTION) {
  1101. int anchor = lsm.getAnchorSelectionIndex();
  1102. if (anchor == -1) {
  1103. anchor = index;
  1104. }
  1105. list.setSelectionInterval(anchor, index);
  1106. lsm.setAnchorSelectionIndex(anchor);
  1107. lsm.setLeadSelectionIndex(index);
  1108. }
  1109. else if (selectionType == CHANGE_SELECTION) {
  1110. list.setSelectedIndex(index);
  1111. }
  1112. else {
  1113. lsm.setLeadSelectionIndex(index);
  1114. }
  1115. ensureIndexIsVisible(list, index);
  1116. }
  1117. }
  1118. }
  1119. /**
  1120. * Action to move the selection to the first item in the list.
  1121. */
  1122. private static class HomeAction extends IncrementLeadSelectionAction {
  1123. protected HomeAction(String name, int type) {
  1124. super(name, type);
  1125. }
  1126. protected int getNextIndex(JList list) {
  1127. return 0;
  1128. }
  1129. }
  1130. /**
  1131. * Action to move the selection to the last item in the list.
  1132. */
  1133. private static class EndAction extends IncrementLeadSelectionAction {
  1134. protected EndAction(String name, int type) {
  1135. super(name, type);
  1136. }
  1137. protected int getNextIndex(JList list) {
  1138. return list.getModel().getSize() - 1;
  1139. }
  1140. }
  1141. /**
  1142. * Action to move up one page.
  1143. */
  1144. private static class PageUpAction extends IncrementLeadSelectionAction {
  1145. protected PageUpAction(String name, int type) {
  1146. super(name, type);
  1147. }
  1148. protected int getNextIndex(JList list) {
  1149. int index = list.getFirstVisibleIndex();
  1150. ListSelectionModel lsm = list.getSelectionModel();
  1151. if (lsm.getLeadSelectionIndex() == index) {
  1152. Rectangle visRect = list.getVisibleRect();
  1153. visRect.y = Math.max(0, visRect.y - visRect.height);
  1154. index = list.locationToIndex(visRect.getLocation());
  1155. }
  1156. return index;
  1157. }
  1158. protected void ensureIndexIsVisible(JList list, int index) {
  1159. Rectangle visRect = list.getVisibleRect();
  1160. Rectangle cellBounds = list.getCellBounds(index, index);
  1161. cellBounds.height = visRect.height;
  1162. list.scrollRectToVisible(cellBounds);
  1163. }
  1164. }
  1165. /**
  1166. * Action to move down one page.
  1167. */
  1168. private static class PageDownAction extends IncrementLeadSelectionAction {
  1169. protected PageDownAction(String name, int type) {
  1170. super(name, type);
  1171. }
  1172. protected int getNextIndex(JList list) {
  1173. int index = list.getLastVisibleIndex();
  1174. ListSelectionModel lsm = list.getSelectionModel();
  1175. if (index == -1) {
  1176. // Will happen if size < viewport size.
  1177. index = list.getModel().getSize() - 1;
  1178. }
  1179. if (lsm.getLeadSelectionIndex() == index) {
  1180. Rectangle visRect = list.getVisibleRect();
  1181. visRect.y += visRect.height + visRect.height - 1;
  1182. index = list.locationToIndex(visRect.getLocation());
  1183. if (index == -1) {
  1184. index = list.getModel().getSize() - 1;
  1185. }
  1186. }
  1187. return index;
  1188. }
  1189. protected void ensureIndexIsVisible(JList list, int index) {
  1190. Rectangle visRect = list.getVisibleRect();
  1191. Rectangle cellBounds = list.getCellBounds(index, index);
  1192. cellBounds.y = Math.max(0, cellBounds.y + cellBounds.height -
  1193. visRect.height);
  1194. cellBounds.height = visRect.height;
  1195. list.scrollRectToVisible(cellBounds);
  1196. }
  1197. }
  1198. /**
  1199. * Action to select all the items in the list.
  1200. */
  1201. private static class SelectAllAction extends AbstractAction {
  1202. private SelectAllAction(String name) {
  1203. super(name);
  1204. }
  1205. public void actionPerformed(ActionEvent e) {
  1206. JList list = (JList)e.getSource();
  1207. // Select all should not alter the lead and anchor.
  1208. // ListSelectionModel encforces the selection to the anchor/lead,
  1209. // so it is commented out.
  1210. // ListSelectionModel lsm = list.getSelectionModel();
  1211. // int anchor = lsm.getAnchorSelectionIndex();
  1212. // int lead = lsm.getLeadSelectionIndex();
  1213. list.setSelectionInterval(0, list.getModel().getSize() - 1);
  1214. // lsm.setAnchorSelectionIndex(anchor);
  1215. // lsm.setLeadSelectionIndex(lead);
  1216. }
  1217. }
  1218. /**
  1219. * Action to clear the selection in the list.
  1220. */
  1221. private static class ClearSelectionAction extends AbstractAction {
  1222. private ClearSelectionAction(String name) {
  1223. super(name);
  1224. }
  1225. public void actionPerformed(ActionEvent e) {
  1226. JList list = (JList)e.getSource();
  1227. // Unselect all should not alter the lead and anchor.
  1228. // ListSelectionModel encforces the selection to the anchor/lead,
  1229. // so it is commented out.
  1230. // ListSelectionModel lsm = list.getSelectionModel();
  1231. // int anchor = lsm.getAnchorSelectionIndex();
  1232. // int lead = lsm.getLeadSelectionIndex();
  1233. list.clearSelection();
  1234. // lsm.setAnchorSelectionIndex(anchor);
  1235. // lsm.setLeadSelectionIndex(lead);
  1236. }
  1237. }
  1238. }