1. /*
  2. * @(#)BasicTableUI.java 1.140 04/06/14
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import java.awt.*;
  9. import java.awt.datatransfer.*;
  10. import java.awt.dnd.*;
  11. import java.awt.event.*;
  12. import java.util.Enumeration;
  13. import java.util.EventObject;
  14. import java.util.Hashtable;
  15. import java.util.TooManyListenersException;
  16. import javax.swing.*;
  17. import javax.swing.event.*;
  18. import javax.swing.plaf.*;
  19. import javax.swing.text.*;
  20. import javax.swing.table.*;
  21. import java.beans.PropertyChangeEvent;
  22. import java.beans.PropertyChangeListener;
  23. import sun.swing.DefaultLookup;
  24. import sun.swing.UIAction;
  25. /**
  26. * BasicTableUI implementation
  27. *
  28. * @version 1.140 06/14/04
  29. * @author Philip Milne
  30. */
  31. public class BasicTableUI extends TableUI
  32. {
  33. //
  34. // Instance Variables
  35. //
  36. // The JTable that is delegating the painting to this UI.
  37. protected JTable table;
  38. protected CellRendererPane rendererPane;
  39. // Listeners that are attached to the JTable
  40. protected KeyListener keyListener;
  41. protected FocusListener focusListener;
  42. protected MouseInputListener mouseInputListener;
  43. private Handler handler;
  44. //
  45. // Helper class for keyboard actions
  46. //
  47. private static class Actions extends UIAction {
  48. private static final String CANCEL_EDITING = "cancel";
  49. private static final String SELECT_ALL = "selectAll";
  50. private static final String CLEAR_SELECTION = "clearSelection";
  51. private static final String START_EDITING = "startEditing";
  52. private static final String NEXT_ROW = "selectNextRow";
  53. private static final String NEXT_ROW_CELL = "selectNextRowCell";
  54. private static final String NEXT_ROW_EXTEND_SELECTION =
  55. "selectNextRowExtendSelection";
  56. private static final String NEXT_ROW_CHANGE_LEAD =
  57. "selectNextRowChangeLead";
  58. private static final String PREVIOUS_ROW = "selectPreviousRow";
  59. private static final String PREVIOUS_ROW_CELL = "selectPreviousRowCell";
  60. private static final String PREVIOUS_ROW_EXTEND_SELECTION =
  61. "selectPreviousRowExtendSelection";
  62. private static final String PREVIOUS_ROW_CHANGE_LEAD =
  63. "selectPreviousRowChangeLead";
  64. private static final String NEXT_COLUMN = "selectNextColumn";
  65. private static final String NEXT_COLUMN_CELL = "selectNextColumnCell";
  66. private static final String NEXT_COLUMN_EXTEND_SELECTION =
  67. "selectNextColumnExtendSelection";
  68. private static final String NEXT_COLUMN_CHANGE_LEAD =
  69. "selectNextColumnChangeLead";
  70. private static final String PREVIOUS_COLUMN = "selectPreviousColumn";
  71. private static final String PREVIOUS_COLUMN_CELL =
  72. "selectPreviousColumnCell";
  73. private static final String PREVIOUS_COLUMN_EXTEND_SELECTION =
  74. "selectPreviousColumnExtendSelection";
  75. private static final String PREVIOUS_COLUMN_CHANGE_LEAD =
  76. "selectPreviousColumnChangeLead";
  77. private static final String SCROLL_LEFT_CHANGE_SELECTION =
  78. "scrollLeftChangeSelection";
  79. private static final String SCROLL_LEFT_EXTEND_SELECTION =
  80. "scrollLeftExtendSelection";
  81. private static final String SCROLL_RIGHT_CHANGE_SELECTION =
  82. "scrollRightChangeSelection";
  83. private static final String SCROLL_RIGHT_EXTEND_SELECTION =
  84. "scrollRightExtendSelection";
  85. private static final String SCROLL_UP_CHANGE_SELECTION =
  86. "scrollUpChangeSelection";
  87. private static final String SCROLL_UP_EXTEND_SELECTION =
  88. "scrollUpExtendSelection";
  89. private static final String SCROLL_DOWN_CHANGE_SELECTION =
  90. "scrollDownChangeSelection";
  91. private static final String SCROLL_DOWN_EXTEND_SELECTION =
  92. "scrollDownExtendSelection";
  93. private static final String FIRST_COLUMN =
  94. "selectFirstColumn";
  95. private static final String FIRST_COLUMN_EXTEND_SELECTION =
  96. "selectFirstColumnExtendSelection";
  97. private static final String LAST_COLUMN =
  98. "selectLastColumn";
  99. private static final String LAST_COLUMN_EXTEND_SELECTION =
  100. "selectLastColumnExtendSelection";
  101. private static final String FIRST_ROW =
  102. "selectFirstRow";
  103. private static final String FIRST_ROW_EXTEND_SELECTION =
  104. "selectFirstRowExtendSelection";
  105. private static final String LAST_ROW =
  106. "selectLastRow";
  107. private static final String LAST_ROW_EXTEND_SELECTION =
  108. "selectLastRowExtendSelection";
  109. // add the lead item to the selection without changing lead or anchor
  110. private static final String ADD_TO_SELECTION = "addToSelection";
  111. // toggle the selected state of the lead item and move the anchor to it
  112. private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
  113. // extend the selection to the lead item
  114. private static final String EXTEND_TO = "extendTo";
  115. // move the anchor to the lead and ensure only that item is selected
  116. private static final String MOVE_SELECTION_TO = "moveSelectionTo";
  117. protected int dx;
  118. protected int dy;
  119. protected boolean extend;
  120. protected boolean inSelection;
  121. protected boolean forwards;
  122. protected boolean vertically;
  123. protected boolean toLimit;
  124. protected int leadRow;
  125. protected int leadColumn;
  126. Actions(String name) {
  127. super(name);
  128. }
  129. Actions(String name, int dx, int dy, boolean extend,
  130. boolean inSelection) {
  131. super(name);
  132. // Actions spcifying true for "inSelection" are
  133. // fairly sensitive to bad parameter values. They require
  134. // that one of dx and dy be 0 and the other be -1 or 1.
  135. // Bogus parameter values could cause an infinite loop.
  136. // To prevent any problems we massage the params here
  137. // and complain if we get something we can't deal with.
  138. if (inSelection) {
  139. this.inSelection = true;
  140. // look at the sign of dx and dy only
  141. dx = sign(dx);
  142. dy = sign(dy);
  143. // make sure one is zero, but not both
  144. assert (dx == 0 || dy == 0) && !(dx == 0 && dy == 0);
  145. }
  146. this.dx = dx;
  147. this.dy = dy;
  148. this.extend = extend;
  149. }
  150. Actions(String name, boolean extend, boolean forwards,
  151. boolean vertically, boolean toLimit) {
  152. this(name, 0, 0, extend, false);
  153. this.forwards = forwards;
  154. this.vertically = vertically;
  155. this.toLimit = toLimit;
  156. }
  157. private static int clipToRange(int i, int a, int b) {
  158. return Math.min(Math.max(i, a), b-1);
  159. }
  160. private void moveWithinTableRange(JTable table, int dx, int dy) {
  161. leadRow = clipToRange(leadRow+dy, 0, table.getRowCount());
  162. leadColumn = clipToRange(leadColumn+dx, 0, table.getColumnCount());
  163. }
  164. private static int sign(int num) {
  165. return (num < 0) ? -1 : ((num == 0) ? 0 : 1);
  166. }
  167. /**
  168. * Called to move within the selected range of the given JTable.
  169. * This method uses the table's notion of selection, which is
  170. * important to allow the user to navigate between items visually
  171. * selected on screen. This notion may or may not be the same as
  172. * what could be determined by directly querying the selection models.
  173. * It depends on certain table properties (such as whether or not
  174. * row or column selection is allowed). When performing modifications,
  175. * it is recommended that caution be taken in order to preserve
  176. * the intent of this method, especially when deciding whether to
  177. * query the selection models or interact with JTable directly.
  178. */
  179. private boolean moveWithinSelectedRange(JTable table, int dx, int dy,
  180. ListSelectionModel rsm, ListSelectionModel csm) {
  181. // Note: The Actions constructor ensures that only one of
  182. // dx and dy is 0, and the other is either -1 or 1
  183. // find out how many items the table is showing as selected
  184. // and the range of items to navigate through
  185. int totalCount;
  186. int minX, maxX, minY, maxY;
  187. boolean rs = table.getRowSelectionAllowed();
  188. boolean cs = table.getColumnSelectionAllowed();
  189. // both column and row selection
  190. if (rs && cs) {
  191. totalCount = table.getSelectedRowCount() * table.getSelectedColumnCount();
  192. minX = csm.getMinSelectionIndex();
  193. maxX = csm.getMaxSelectionIndex();
  194. minY = rsm.getMinSelectionIndex();
  195. maxY = rsm.getMaxSelectionIndex();
  196. // row selection only
  197. } else if (rs) {
  198. totalCount = table.getSelectedRowCount();
  199. minX = 0;
  200. maxX = table.getColumnCount() - 1;
  201. minY = rsm.getMinSelectionIndex();
  202. maxY = rsm.getMaxSelectionIndex();
  203. // column selection only
  204. } else if (cs) {
  205. totalCount = table.getSelectedColumnCount();
  206. minX = csm.getMinSelectionIndex();
  207. maxX = csm.getMaxSelectionIndex();
  208. minY = 0;
  209. maxY = table.getRowCount() - 1;
  210. // no selection allowed
  211. } else {
  212. totalCount = 0;
  213. // A bogus assignment to stop javac from complaining
  214. // about unitialized values. In this case, these
  215. // won't even be used.
  216. minX = maxX = minY = maxY = 0;
  217. }
  218. // For some cases, there is no point in trying to stay within the
  219. // selected area. Instead, move outside the selection, wrapping at
  220. // the table boundaries. The cases are:
  221. boolean stayInSelection;
  222. // - nothing selected
  223. if (totalCount == 0 ||
  224. // - one item selected, and the lead is already selected
  225. (totalCount == 1 && table.isCellSelected(leadRow, leadColumn))) {
  226. stayInSelection = false;
  227. maxX = table.getColumnCount() - 1;
  228. maxY = table.getRowCount() - 1;
  229. // the mins are calculated like this in case the max is -1
  230. minX = Math.min(0, maxX);
  231. minY = Math.min(0, maxY);
  232. } else {
  233. stayInSelection = true;
  234. }
  235. // In cases where the lead is not within the search range,
  236. // we need to bring it within one cell for the the search
  237. // to work properly. Check these here.
  238. leadRow = Math.min(Math.max(leadRow, minY - 1), maxY + 1);
  239. leadColumn = Math.min(Math.max(leadColumn, minX - 1), maxX + 1);
  240. // find the next position, possibly looping until it is selected
  241. do {
  242. calcNextPos(dx, minX, maxX, dy, minY, maxY);
  243. } while (stayInSelection && !table.isCellSelected(leadRow, leadColumn));
  244. return stayInSelection;
  245. }
  246. /**
  247. * Find the next lead row and column based on the given
  248. * dx/dy and max/min values.
  249. */
  250. private void calcNextPos(int dx, int minX, int maxX,
  251. int dy, int minY, int maxY) {
  252. if (dx != 0) {
  253. leadColumn += dx;
  254. if (leadColumn > maxX) {
  255. leadColumn = minX;
  256. leadRow++;
  257. if (leadRow > maxY) {
  258. leadRow = minY;
  259. }
  260. } else if (leadColumn < minX) {
  261. leadColumn = maxX;
  262. leadRow--;
  263. if (leadRow < minY) {
  264. leadRow = maxY;
  265. }
  266. }
  267. } else {
  268. leadRow += dy;
  269. if (leadRow > maxY) {
  270. leadRow = minY;
  271. leadColumn++;
  272. if (leadColumn > maxX) {
  273. leadColumn = minX;
  274. }
  275. } else if (leadRow < minY) {
  276. leadRow = maxY;
  277. leadColumn--;
  278. if (leadColumn < minX) {
  279. leadColumn = maxX;
  280. }
  281. }
  282. }
  283. }
  284. public void actionPerformed(ActionEvent e) {
  285. String key = getName();
  286. JTable table = (JTable)e.getSource();
  287. ListSelectionModel rsm = table.getSelectionModel();
  288. leadRow = rsm.getLeadSelectionIndex();
  289. ListSelectionModel csm = table.getColumnModel().getSelectionModel();
  290. leadColumn = csm.getLeadSelectionIndex();
  291. if (!table.getComponentOrientation().isLeftToRight()) {
  292. if (key == SCROLL_LEFT_CHANGE_SELECTION ||
  293. key == SCROLL_LEFT_EXTEND_SELECTION) {
  294. forwards = true;
  295. } else if (key == SCROLL_RIGHT_CHANGE_SELECTION ||
  296. key == SCROLL_RIGHT_EXTEND_SELECTION) {
  297. forwards = false;
  298. }
  299. }
  300. if (key == SCROLL_LEFT_CHANGE_SELECTION || // Paging Actions
  301. key == SCROLL_LEFT_EXTEND_SELECTION ||
  302. key == SCROLL_RIGHT_CHANGE_SELECTION ||
  303. key == SCROLL_RIGHT_EXTEND_SELECTION ||
  304. key == SCROLL_UP_CHANGE_SELECTION ||
  305. key == SCROLL_UP_EXTEND_SELECTION ||
  306. key == SCROLL_DOWN_CHANGE_SELECTION ||
  307. key == SCROLL_DOWN_EXTEND_SELECTION ||
  308. key == FIRST_COLUMN ||
  309. key == FIRST_COLUMN_EXTEND_SELECTION ||
  310. key == FIRST_ROW ||
  311. key == FIRST_ROW_EXTEND_SELECTION ||
  312. key == LAST_COLUMN ||
  313. key == LAST_COLUMN_EXTEND_SELECTION ||
  314. key == LAST_ROW ||
  315. key == LAST_ROW_EXTEND_SELECTION) {
  316. if (toLimit) {
  317. if (vertically) {
  318. int rowCount = table.getRowCount();
  319. this.dx = 0;
  320. this.dy = forwards ? rowCount : -rowCount;
  321. }
  322. else {
  323. int colCount = table.getColumnCount();
  324. this.dx = forwards ? colCount : -colCount;
  325. this.dy = 0;
  326. }
  327. }
  328. else {
  329. if (!(table.getParent().getParent() instanceof
  330. JScrollPane)) {
  331. return;
  332. }
  333. Dimension delta = table.getParent().getSize();
  334. int start = (vertically) ? rsm.getLeadSelectionIndex() :
  335. csm.getLeadSelectionIndex();
  336. if (vertically) {
  337. Rectangle r = table.getCellRect(start, 0, true);
  338. r.y += forwards ? delta.height : -delta.height;
  339. this.dx = 0;
  340. int newRow = table.rowAtPoint(r.getLocation());
  341. if (newRow == -1 && forwards) {
  342. newRow = table.getRowCount();
  343. }
  344. this.dy = newRow - start;
  345. }
  346. else {
  347. Rectangle r = table.getCellRect(0, start, true);
  348. r.x += forwards ? delta.width : -delta.width;
  349. int newColumn = table.columnAtPoint(r.getLocation());
  350. if (newColumn == -1 && forwards) {
  351. newColumn = table.getColumnCount();
  352. }
  353. this.dx = newColumn - start;
  354. this.dy = 0;
  355. }
  356. }
  357. }
  358. if (key == NEXT_ROW || // Navigate Actions
  359. key == NEXT_ROW_CELL ||
  360. key == NEXT_ROW_EXTEND_SELECTION ||
  361. key == NEXT_ROW_CHANGE_LEAD ||
  362. key == NEXT_COLUMN ||
  363. key == NEXT_COLUMN_CELL ||
  364. key == NEXT_COLUMN_EXTEND_SELECTION ||
  365. key == NEXT_COLUMN_CHANGE_LEAD ||
  366. key == PREVIOUS_ROW ||
  367. key == PREVIOUS_ROW_CELL ||
  368. key == PREVIOUS_ROW_EXTEND_SELECTION ||
  369. key == PREVIOUS_ROW_CHANGE_LEAD ||
  370. key == PREVIOUS_COLUMN ||
  371. key == PREVIOUS_COLUMN_CELL ||
  372. key == PREVIOUS_COLUMN_EXTEND_SELECTION ||
  373. key == PREVIOUS_COLUMN_CHANGE_LEAD ||
  374. // Paging Actions.
  375. key == SCROLL_LEFT_CHANGE_SELECTION ||
  376. key == SCROLL_LEFT_EXTEND_SELECTION ||
  377. key == SCROLL_RIGHT_CHANGE_SELECTION ||
  378. key == SCROLL_RIGHT_EXTEND_SELECTION ||
  379. key == SCROLL_UP_CHANGE_SELECTION ||
  380. key == SCROLL_UP_EXTEND_SELECTION ||
  381. key == SCROLL_DOWN_CHANGE_SELECTION ||
  382. key == SCROLL_DOWN_EXTEND_SELECTION ||
  383. key == FIRST_COLUMN ||
  384. key == FIRST_COLUMN_EXTEND_SELECTION ||
  385. key == FIRST_ROW ||
  386. key == FIRST_ROW_EXTEND_SELECTION ||
  387. key == LAST_COLUMN ||
  388. key == LAST_COLUMN_EXTEND_SELECTION ||
  389. key == LAST_ROW ||
  390. key == LAST_ROW_EXTEND_SELECTION) {
  391. if (table.isEditing() &&
  392. !table.getCellEditor().stopCellEditing()) {
  393. return;
  394. }
  395. // Unfortunately, this strategy introduces bugs because
  396. // of the asynchronous nature of requestFocus() call below.
  397. // Introducing a delay with invokeLater() makes this work
  398. // in the typical case though race conditions then allow
  399. // focus to disappear altogether. The right solution appears
  400. // to be to fix requestFocus() so that it queues a request
  401. // for the focus regardless of who owns the focus at the
  402. // time the call to requestFocus() is made. The optimisation
  403. // to ignore the call to requestFocus() when the component
  404. // already has focus may ligitimately be made as the
  405. // request focus event is dequeued, not before.
  406. // boolean wasEditingWithFocus = table.isEditing() &&
  407. // table.getEditorComponent().isFocusOwner();
  408. boolean changeLead = false;
  409. if (key == NEXT_ROW_CHANGE_LEAD || key == PREVIOUS_ROW_CHANGE_LEAD) {
  410. changeLead = (rsm.getSelectionMode()
  411. == ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
  412. } else if (key == NEXT_COLUMN_CHANGE_LEAD || key == PREVIOUS_COLUMN_CHANGE_LEAD) {
  413. changeLead = (csm.getSelectionMode()
  414. == ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
  415. }
  416. if (changeLead) {
  417. moveWithinTableRange(table, dx, dy);
  418. if (dy != 0) {
  419. // casting should be safe since the action is only enabled
  420. // for DefaultListSelectionModel
  421. ((DefaultListSelectionModel)rsm).moveLeadSelectionIndex(leadRow);
  422. } else {
  423. // casting should be safe since the action is only enabled
  424. // for DefaultListSelectionModel
  425. ((DefaultListSelectionModel)csm).moveLeadSelectionIndex(leadColumn);
  426. }
  427. Rectangle cellRect = table.getCellRect(leadRow, leadColumn, false);
  428. if (cellRect != null) {
  429. table.scrollRectToVisible(cellRect);
  430. }
  431. } else if (!inSelection) {
  432. moveWithinTableRange(table, dx, dy);
  433. table.changeSelection(leadRow, leadColumn, false, extend);
  434. }
  435. else {
  436. if (moveWithinSelectedRange(table, dx, dy, rsm, csm)) {
  437. // this is the only way we have to set both the lead
  438. // and the anchor without changing the selection
  439. if (rsm.isSelectedIndex(leadRow)) {
  440. rsm.addSelectionInterval(leadRow, leadRow);
  441. } else {
  442. rsm.removeSelectionInterval(leadRow, leadRow);
  443. }
  444. if (csm.isSelectedIndex(leadColumn)) {
  445. csm.addSelectionInterval(leadColumn, leadColumn);
  446. } else {
  447. csm.removeSelectionInterval(leadColumn, leadColumn);
  448. }
  449. Rectangle cellRect = table.getCellRect(leadRow, leadColumn, false);
  450. if (cellRect != null) {
  451. table.scrollRectToVisible(cellRect);
  452. }
  453. }
  454. else {
  455. table.changeSelection(leadRow, leadColumn,
  456. false, false);
  457. }
  458. }
  459. /*
  460. if (wasEditingWithFocus) {
  461. table.editCellAt(leadRow, leadColumn);
  462. final Component editorComp = table.getEditorComponent();
  463. if (editorComp != null) {
  464. SwingUtilities.invokeLater(new Runnable() {
  465. public void run() {
  466. editorComp.requestFocus();
  467. }
  468. });
  469. }
  470. }
  471. */
  472. } else if (key == CANCEL_EDITING) {
  473. table.removeEditor();
  474. } else if (key == SELECT_ALL) {
  475. table.selectAll();
  476. } else if (key == CLEAR_SELECTION) {
  477. table.clearSelection();
  478. } else if (key == START_EDITING) {
  479. if (!table.hasFocus()) {
  480. CellEditor cellEditor = table.getCellEditor();
  481. if (cellEditor != null && !cellEditor.stopCellEditing()) {
  482. return;
  483. }
  484. table.requestFocus();
  485. return;
  486. }
  487. table.editCellAt(leadRow, leadColumn);
  488. Component editorComp = table.getEditorComponent();
  489. if (editorComp != null) {
  490. editorComp.requestFocus();
  491. }
  492. } else if (key == ADD_TO_SELECTION) {
  493. if (!table.isCellSelected(leadRow, leadColumn)) {
  494. int oldAnchorRow = rsm.getAnchorSelectionIndex();
  495. int oldAnchorColumn = csm.getAnchorSelectionIndex();
  496. rsm.setValueIsAdjusting(true);
  497. csm.setValueIsAdjusting(true);
  498. table.changeSelection(leadRow, leadColumn, true, false);
  499. rsm.setAnchorSelectionIndex(oldAnchorRow);
  500. csm.setAnchorSelectionIndex(oldAnchorColumn);
  501. rsm.setValueIsAdjusting(false);
  502. csm.setValueIsAdjusting(false);
  503. }
  504. } else if (key == TOGGLE_AND_ANCHOR) {
  505. table.changeSelection(leadRow, leadColumn, true, false);
  506. } else if (key == EXTEND_TO) {
  507. table.changeSelection(leadRow, leadColumn, false, true);
  508. } else if (key == MOVE_SELECTION_TO) {
  509. table.changeSelection(leadRow, leadColumn, false, false);
  510. }
  511. }
  512. public boolean isEnabled(Object sender) {
  513. String key = getName();
  514. if (sender instanceof JTable &&
  515. Boolean.TRUE.equals(((JTable)sender).getClientProperty("Table.isFileList"))) {
  516. if (key == NEXT_COLUMN ||
  517. key == NEXT_COLUMN_CELL ||
  518. key == NEXT_COLUMN_EXTEND_SELECTION ||
  519. key == NEXT_COLUMN_CHANGE_LEAD ||
  520. key == PREVIOUS_COLUMN ||
  521. key == PREVIOUS_COLUMN_CELL ||
  522. key == PREVIOUS_COLUMN_EXTEND_SELECTION ||
  523. key == PREVIOUS_COLUMN_CHANGE_LEAD ||
  524. key == SCROLL_LEFT_CHANGE_SELECTION ||
  525. key == SCROLL_LEFT_EXTEND_SELECTION ||
  526. key == SCROLL_RIGHT_CHANGE_SELECTION ||
  527. key == SCROLL_RIGHT_EXTEND_SELECTION ||
  528. key == FIRST_COLUMN ||
  529. key == FIRST_COLUMN_EXTEND_SELECTION ||
  530. key == LAST_COLUMN ||
  531. key == LAST_COLUMN_EXTEND_SELECTION ||
  532. key == NEXT_ROW_CELL ||
  533. key == PREVIOUS_ROW_CELL) {
  534. return false;
  535. }
  536. }
  537. if (key == CANCEL_EDITING && sender instanceof JTable) {
  538. return ((JTable)sender).isEditing();
  539. } else if (key == NEXT_ROW_CHANGE_LEAD ||
  540. key == PREVIOUS_ROW_CHANGE_LEAD) {
  541. // discontinuous selection actions are only enabled for
  542. // DefaultListSelectionModel
  543. return sender != null &&
  544. ((JTable)sender).getSelectionModel()
  545. instanceof DefaultListSelectionModel;
  546. } else if (key == NEXT_COLUMN_CHANGE_LEAD ||
  547. key == PREVIOUS_COLUMN_CHANGE_LEAD) {
  548. // discontinuous selection actions are only enabled for
  549. // DefaultListSelectionModel
  550. return sender != null &&
  551. ((JTable)sender).getColumnModel().getSelectionModel()
  552. instanceof DefaultListSelectionModel;
  553. } else if (key == ADD_TO_SELECTION && sender instanceof JTable) {
  554. // This action is typically bound to SPACE.
  555. // If the table is already in an editing mode, SPACE should
  556. // simply enter a space character into the table, and not
  557. // select a cell. Likewise, if the lead cell is already selected
  558. // then hitting SPACE should just enter a space character
  559. // into the cell and begin editing. In both of these cases
  560. // this action will be disabled.
  561. JTable table = (JTable)sender;
  562. int leadRow = table.getSelectionModel().
  563. getLeadSelectionIndex();
  564. int leadCol = table.getColumnModel().getSelectionModel().
  565. getLeadSelectionIndex();
  566. return !(table.isEditing() || table.isCellSelected(leadRow, leadCol));
  567. }
  568. return true;
  569. }
  570. }
  571. //
  572. // The Table's Key listener
  573. //
  574. /**
  575. * This inner class is marked "public" due to a compiler bug.
  576. * This class should be treated as a "protected" inner class.
  577. * Instantiate it only within subclasses of BasicTableUI.
  578. * <p>As of Java 2 platform v1.3 this class is no longer used.
  579. * Instead <code>JTable</code>
  580. * overrides <code>processKeyBinding</code> to dispatch the event to
  581. * the current <code>TableCellEditor</code>.
  582. */
  583. public class KeyHandler implements KeyListener {
  584. // NOTE: This class exists only for backward compatability. All
  585. // its functionality has been moved into Handler. If you need to add
  586. // new functionality add it to the Handler, but make sure this
  587. // class calls into the Handler.
  588. public void keyPressed(KeyEvent e) {
  589. getHandler().keyPressed(e);
  590. }
  591. public void keyReleased(KeyEvent e) {
  592. getHandler().keyReleased(e);
  593. }
  594. public void keyTyped(KeyEvent e) {
  595. getHandler().keyTyped(e);
  596. }
  597. }
  598. //
  599. // The Table's focus listener
  600. //
  601. /**
  602. * This inner class is marked "public" due to a compiler bug.
  603. * This class should be treated as a "protected" inner class.
  604. * Instantiate it only within subclasses of BasicTableUI.
  605. */
  606. public class FocusHandler implements FocusListener {
  607. // NOTE: This class exists only for backward compatability. All
  608. // its functionality has been moved into Handler. If you need to add
  609. // new functionality add it to the Handler, but make sure this
  610. // class calls into the Handler.
  611. public void focusGained(FocusEvent e) {
  612. getHandler().focusGained(e);
  613. }
  614. public void focusLost(FocusEvent e) {
  615. getHandler().focusLost(e);
  616. }
  617. }
  618. //
  619. // The Table's mouse and mouse motion listeners
  620. //
  621. /**
  622. * This inner class is marked "public" due to a compiler bug.
  623. * This class should be treated as a "protected" inner class.
  624. * Instantiate it only within subclasses of BasicTableUI.
  625. */
  626. public class MouseInputHandler implements MouseInputListener {
  627. // NOTE: This class exists only for backward compatability. All
  628. // its functionality has been moved into Handler. If you need to add
  629. // new functionality add it to the Handler, but make sure this
  630. // class calls into the Handler.
  631. public void mouseClicked(MouseEvent e) {
  632. getHandler().mouseClicked(e);
  633. }
  634. public void mousePressed(MouseEvent e) {
  635. getHandler().mousePressed(e);
  636. }
  637. public void mouseReleased(MouseEvent e) {
  638. getHandler().mouseReleased(e);
  639. }
  640. public void mouseEntered(MouseEvent e) {
  641. getHandler().mouseEntered(e);
  642. }
  643. public void mouseExited(MouseEvent e) {
  644. getHandler().mouseExited(e);
  645. }
  646. public void mouseMoved(MouseEvent e) {
  647. getHandler().mouseMoved(e);
  648. }
  649. public void mouseDragged(MouseEvent e) {
  650. getHandler().mouseDragged(e);
  651. }
  652. }
  653. private class Handler implements FocusListener, MouseInputListener,
  654. PropertyChangeListener {
  655. // FocusListener
  656. private void repaintLeadCell( ) {
  657. int rc = table.getRowCount();
  658. int cc = table.getColumnCount();
  659. int lr = table.getSelectionModel().getLeadSelectionIndex();
  660. int lc = table.getColumnModel().getSelectionModel().
  661. getLeadSelectionIndex();
  662. if (lr < 0 || lr >= rc || lc < 0 || lc >= cc) {
  663. return;
  664. }
  665. Rectangle dirtyRect = table.getCellRect(lr, lc, false);
  666. table.repaint(dirtyRect);
  667. }
  668. public void focusGained(FocusEvent e) {
  669. repaintLeadCell();
  670. }
  671. public void focusLost(FocusEvent e) {
  672. repaintLeadCell();
  673. }
  674. // KeyListener
  675. public void keyPressed(KeyEvent e) { }
  676. public void keyReleased(KeyEvent e) { }
  677. public void keyTyped(KeyEvent e) {
  678. KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyChar(),
  679. e.getModifiers());
  680. // We register all actions using ANCESTOR_OF_FOCUSED_COMPONENT
  681. // which means that we might perform the appropriate action
  682. // in the table and then forward it to the editor if the editor
  683. // had focus. Make sure this doesn't happen by checking our
  684. // InputMaps.
  685. InputMap map = table.getInputMap(JComponent.WHEN_FOCUSED);
  686. if (map != null && map.get(keyStroke) != null) {
  687. return;
  688. }
  689. map = table.getInputMap(JComponent.
  690. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  691. if (map != null && map.get(keyStroke) != null) {
  692. return;
  693. }
  694. keyStroke = KeyStroke.getKeyStrokeForEvent(e);
  695. // The AWT seems to generate an unconsumed \r event when
  696. // ENTER (\n) is pressed.
  697. if (e.getKeyChar() == '\r') {
  698. return;
  699. }
  700. int leadRow = table.getSelectionModel().getLeadSelectionIndex();
  701. int leadColumn = table.getColumnModel().getSelectionModel().
  702. getLeadSelectionIndex();
  703. if (leadRow != -1 && leadColumn != -1 && !table.isEditing()) {
  704. if (!table.editCellAt(leadRow, leadColumn)) {
  705. return;
  706. }
  707. }
  708. // Forwarding events this way seems to put the component
  709. // in a state where it believes it has focus. In reality
  710. // the table retains focus - though it is difficult for
  711. // a user to tell, since the caret is visible and flashing.
  712. // Calling table.requestFocus() here, to get the focus back to
  713. // the table, seems to have no effect.
  714. Component editorComp = table.getEditorComponent();
  715. if (table.isEditing() && editorComp != null) {
  716. if (editorComp instanceof JComponent) {
  717. JComponent component = (JComponent)editorComp;
  718. map = component.getInputMap(JComponent.WHEN_FOCUSED);
  719. Object binding = (map != null) ? map.get(keyStroke) : null;
  720. if (binding == null) {
  721. map = component.getInputMap(JComponent.
  722. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  723. binding = (map != null) ? map.get(keyStroke) : null;
  724. }
  725. if (binding != null) {
  726. ActionMap am = component.getActionMap();
  727. Action action = (am != null) ? am.get(binding) : null;
  728. if (action != null && SwingUtilities.
  729. notifyAction(action, keyStroke, e, component,
  730. e.getModifiers())) {
  731. e.consume();
  732. }
  733. }
  734. }
  735. }
  736. }
  737. // MouseInputListener
  738. // Component receiving mouse events during editing.
  739. // May not be editorComponent.
  740. private Component dispatchComponent;
  741. private boolean selectedOnPress;
  742. public void mouseClicked(MouseEvent e) {}
  743. private void setDispatchComponent(MouseEvent e) {
  744. Component editorComponent = table.getEditorComponent();
  745. Point p = e.getPoint();
  746. Point p2 = SwingUtilities.convertPoint(table, p, editorComponent);
  747. dispatchComponent =
  748. SwingUtilities.getDeepestComponentAt(editorComponent,
  749. p2.x, p2.y);
  750. }
  751. private boolean repostEvent(MouseEvent e) {
  752. // Check for isEditing() in case another event has
  753. // caused the editor to be removed. See bug #4306499.
  754. if (dispatchComponent == null || !table.isEditing()) {
  755. return false;
  756. }
  757. MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e,
  758. dispatchComponent);
  759. dispatchComponent.dispatchEvent(e2);
  760. return true;
  761. }
  762. private void setValueIsAdjusting(boolean flag) {
  763. table.getSelectionModel().setValueIsAdjusting(flag);
  764. table.getColumnModel().getSelectionModel().
  765. setValueIsAdjusting(flag);
  766. }
  767. private boolean shouldIgnore(MouseEvent e) {
  768. return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) &&
  769. table.isEnabled()));
  770. }
  771. public void mousePressed(MouseEvent e) {
  772. if (e.isConsumed()) {
  773. selectedOnPress = false;
  774. return;
  775. }
  776. selectedOnPress = true;
  777. adjustFocusAndSelection(e);
  778. }
  779. void adjustFocusAndSelection(MouseEvent e) {
  780. if (shouldIgnore(e)) {
  781. return;
  782. }
  783. Point p = e.getPoint();
  784. int row = table.rowAtPoint(p);
  785. int column = table.columnAtPoint(p);
  786. // Fix for 4835633
  787. if (pointOutsidePrefSize(table, row, column, p)) {
  788. // If shift is down in multi-select, we should just return.
  789. // For single select or non-shift-click, clear the selection
  790. if (e.getID() == MouseEvent.MOUSE_PRESSED &&
  791. (!e.isShiftDown() ||
  792. table.getSelectionModel().getSelectionMode() ==
  793. ListSelectionModel.SINGLE_SELECTION)) {
  794. table.clearSelection();
  795. TableCellEditor tce = table.getCellEditor();
  796. if (tce != null) {
  797. tce.stopCellEditing();
  798. }
  799. }
  800. return;
  801. }
  802. // The autoscroller can generate drag events outside the
  803. // table's range.
  804. if ((column == -1) || (row == -1)) {
  805. return;
  806. }
  807. if (table.editCellAt(row, column, e)) {
  808. setDispatchComponent(e);
  809. repostEvent(e);
  810. }
  811. else if (table.isRequestFocusEnabled()) {
  812. table.requestFocus();
  813. }
  814. CellEditor editor = table.getCellEditor();
  815. if (editor == null || editor.shouldSelectCell(e)) {
  816. boolean adjusting = (e.getID() == MouseEvent.MOUSE_PRESSED) ?
  817. true : false;
  818. setValueIsAdjusting(adjusting);
  819. boolean ctrl = e.isControlDown();
  820. table.changeSelection(row, column, ctrl, !ctrl && e.isShiftDown());
  821. }
  822. }
  823. public void mouseReleased(MouseEvent e) {
  824. if (selectedOnPress) {
  825. if (shouldIgnore(e)) {
  826. return;
  827. }
  828. repostEvent(e);
  829. dispatchComponent = null;
  830. setValueIsAdjusting(false);
  831. } else {
  832. adjustFocusAndSelection(e);
  833. }
  834. }
  835. public void mouseEntered(MouseEvent e) {}
  836. public void mouseExited(MouseEvent e) {}
  837. public void mouseMoved(MouseEvent e) {}
  838. public void mouseDragged(MouseEvent e) {
  839. if (shouldIgnore(e)) {
  840. return;
  841. }
  842. repostEvent(e);
  843. CellEditor editor = table.getCellEditor();
  844. if (editor == null || editor.shouldSelectCell(e)) {
  845. Point p = e.getPoint();
  846. int row = table.rowAtPoint(p);
  847. int column = table.columnAtPoint(p);
  848. // The autoscroller can generate drag events outside the
  849. // table's range.
  850. if ((column == -1) || (row == -1)) {
  851. return;
  852. }
  853. // Fix for 4835633
  854. // Until we support drag-selection, dragging should not change
  855. // the selection (act like single-select).
  856. Object bySize = table.getClientProperty("Table.isFileList");
  857. if (bySize instanceof Boolean &&
  858. ((Boolean)bySize).booleanValue()) {
  859. return;
  860. }
  861. table.changeSelection(row, column, false, true);
  862. }
  863. }
  864. // PropertyChangeListener
  865. public void propertyChange(PropertyChangeEvent event) {
  866. String changeName = event.getPropertyName();
  867. if ("componentOrientation" == changeName) {
  868. JTableHeader header = table.getTableHeader();
  869. if (header != null) {
  870. header.setComponentOrientation(
  871. (ComponentOrientation)event.getNewValue());
  872. }
  873. } else if ("transferHandler" == changeName) {
  874. DropTarget dropTarget = table.getDropTarget();
  875. if (dropTarget instanceof UIResource) {
  876. if (defaultDropTargetListener == null) {
  877. defaultDropTargetListener =
  878. new TableDropTargetListener();
  879. }
  880. try {
  881. dropTarget.addDropTargetListener(
  882. defaultDropTargetListener);
  883. } catch (TooManyListenersException tmle) {
  884. // should not happen... swing drop target is multicast
  885. }
  886. }
  887. }
  888. }
  889. }
  890. /*
  891. * Returns true if the given point is outside the preferredSize of the
  892. * item at the given row of the table. (Column must be 0).
  893. * Returns false if the "Table.isFileList" client property is not set.
  894. */
  895. private static boolean pointOutsidePrefSize(JTable table,
  896. int row, int column, Point p) {
  897. Object bySize = table.getClientProperty("Table.isFileList");
  898. if (bySize instanceof Boolean && ((Boolean)bySize).booleanValue()) {
  899. if (table.convertColumnIndexToModel(column) != 0 || row == -1) {
  900. return true;
  901. }
  902. TableCellRenderer tcr = table.getCellRenderer(row, column);
  903. Object value = table.getValueAt(row, column);
  904. Component cell = tcr.getTableCellRendererComponent(table, value, false,
  905. false, row, column);
  906. Dimension itemSize = cell.getPreferredSize();
  907. Rectangle cellBounds = table.getCellRect(row, column, false);
  908. cellBounds.width = itemSize.width;
  909. cellBounds.height = itemSize.height;
  910. // See if coords are inside
  911. // ASSUME: mouse x,y will never be < cell's x,y
  912. assert (p.x >= cellBounds.x && p.y >= cellBounds.y);
  913. if (p.x > cellBounds.x + cellBounds.width ||
  914. p.y > cellBounds.y + cellBounds.height) {
  915. return true;
  916. }
  917. }
  918. return false;
  919. }
  920. //
  921. // Factory methods for the Listeners
  922. //
  923. private Handler getHandler() {
  924. if (handler == null) {
  925. handler = new Handler();
  926. }
  927. return handler;
  928. }
  929. /**
  930. * Creates the key listener for handling keyboard navigation in the JTable.
  931. */
  932. protected KeyListener createKeyListener() {
  933. return null;
  934. }
  935. /**
  936. * Creates the focus listener for handling keyboard navigation in the JTable.
  937. */
  938. protected FocusListener createFocusListener() {
  939. return getHandler();
  940. }
  941. /**
  942. * Creates the mouse listener for the JTable.
  943. */
  944. protected MouseInputListener createMouseInputListener() {
  945. return getHandler();
  946. }
  947. //
  948. // The installation/uninstall procedures and support
  949. //
  950. public static ComponentUI createUI(JComponent c) {
  951. return new BasicTableUI();
  952. }
  953. // Installation
  954. public void installUI(JComponent c) {
  955. table = (JTable)c;
  956. rendererPane = new CellRendererPane();
  957. table.add(rendererPane);
  958. installDefaults();
  959. installDefaults2();
  960. installListeners();
  961. installKeyboardActions();
  962. }
  963. /**
  964. * Initialize JTable properties, e.g. font, foreground, and background.
  965. * The font, foreground, and background properties are only set if their
  966. * current value is either null or a UIResource, other properties are set
  967. * if the current value is null.
  968. *
  969. * @see #installUI
  970. */
  971. protected void installDefaults() {
  972. LookAndFeel.installColorsAndFont(table, "Table.background",
  973. "Table.foreground", "Table.font");
  974. // JTable's original row height is 16. To correctly display the
  975. // contents on Linux we should have set it to 18, Windows 19 and
  976. // Solaris 20. As these values vary so much it's too hard to
  977. // be backward compatable and try to update the row height, we're
  978. // therefor NOT going to adjust the row height based on font. If the
  979. // developer changes the font, it's there responsability to update
  980. // the row height.
  981. LookAndFeel.installProperty(table, "opaque", Boolean.TRUE);
  982. Color sbg = table.getSelectionBackground();
  983. if (sbg == null || sbg instanceof UIResource) {
  984. table.setSelectionBackground(UIManager.getColor("Table.selectionBackground"));
  985. }
  986. Color sfg = table.getSelectionForeground();
  987. if (sfg == null || sfg instanceof UIResource) {
  988. table.setSelectionForeground(UIManager.getColor("Table.selectionForeground"));
  989. }
  990. Color gridColor = table.getGridColor();
  991. if (gridColor == null || gridColor instanceof UIResource) {
  992. table.setGridColor(UIManager.getColor("Table.gridColor"));
  993. }
  994. // install the scrollpane border
  995. Container parent = table.getParent(); // should be viewport
  996. if (parent != null) {
  997. parent = parent.getParent(); // should be the scrollpane
  998. if (parent != null && parent instanceof JScrollPane) {
  999. LookAndFeel.installBorder((JScrollPane)parent, "Table.scrollPaneBorder");
  1000. }
  1001. }
  1002. }
  1003. private void installDefaults2() {
  1004. TransferHandler th = table.getTransferHandler();
  1005. if (th == null || th instanceof UIResource) {
  1006. table.setTransferHandler(defaultTransferHandler);
  1007. }
  1008. DropTarget dropTarget = table.getDropTarget();
  1009. if (dropTarget instanceof UIResource) {
  1010. if (defaultDropTargetListener == null) {
  1011. defaultDropTargetListener =
  1012. new TableDropTargetListener();
  1013. }
  1014. try {
  1015. dropTarget.addDropTargetListener(defaultDropTargetListener);
  1016. } catch (TooManyListenersException tmle) {
  1017. // should not happen... swing drop target is multicast
  1018. }
  1019. }
  1020. }
  1021. /**
  1022. * Attaches listeners to the JTable.
  1023. */
  1024. protected void installListeners() {
  1025. focusListener = createFocusListener();
  1026. keyListener = createKeyListener();
  1027. mouseInputListener = createMouseInputListener();
  1028. table.addFocusListener(focusListener);
  1029. table.addKeyListener(keyListener);
  1030. table.addMouseListener(defaultDragRecognizer);
  1031. table.addMouseMotionListener(defaultDragRecognizer);
  1032. table.addMouseListener(mouseInputListener);
  1033. table.addMouseMotionListener(mouseInputListener);
  1034. table.addPropertyChangeListener(getHandler());
  1035. }
  1036. /**
  1037. * Register all keyboard actions on the JTable.
  1038. */
  1039. protected void installKeyboardActions() {
  1040. LazyActionMap.installLazyActionMap(table, BasicTableUI.class,
  1041. "Table.actionMap");
  1042. InputMap inputMap = getInputMap(JComponent.
  1043. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  1044. SwingUtilities.replaceUIInputMap(table,
  1045. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  1046. inputMap);
  1047. }
  1048. InputMap getInputMap(int condition) {
  1049. if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
  1050. InputMap keyMap =
  1051. (InputMap)DefaultLookup.get(table, this,
  1052. "Table.ancestorInputMap");
  1053. return keyMap;
  1054. }
  1055. return null;
  1056. }
  1057. static void loadActionMap(LazyActionMap map) {
  1058. // IMPORTANT: There is a very close coupling between the parameters
  1059. // passed to the Actions constructor. Only certain parameter
  1060. // combinations are supported. For example, the following Action would
  1061. // not work as expected:
  1062. // new Actions(Actions.NEXT_ROW_CELL, 1, 4, false, true)
  1063. // Actions which move within the selection only (having a true
  1064. // inSelection parameter) require that one of dx or dy be
  1065. // zero and the other be -1 or 1. The point of this warning is
  1066. // that you should be very careful about making sure a particular
  1067. // combination of parameters is supported before changing or
  1068. // adding anything here.
  1069. map.put(new Actions(Actions.NEXT_COLUMN, 1, 0,
  1070. false, false));
  1071. map.put(new Actions(Actions.NEXT_COLUMN_CHANGE_LEAD, 1, 0,
  1072. false, false));
  1073. map.put(new Actions(Actions.PREVIOUS_COLUMN, -1, 0,
  1074. false, false));
  1075. map.put(new Actions(Actions.PREVIOUS_COLUMN_CHANGE_LEAD, -1, 0,
  1076. false, false));
  1077. map.put(new Actions(Actions.NEXT_ROW, 0, 1,
  1078. false, false));
  1079. map.put(new Actions(Actions.NEXT_ROW_CHANGE_LEAD, 0, 1,
  1080. false, false));
  1081. map.put(new Actions(Actions.PREVIOUS_ROW, 0, -1,
  1082. false, false));
  1083. map.put(new Actions(Actions.PREVIOUS_ROW_CHANGE_LEAD, 0, -1,
  1084. false, false));
  1085. map.put(new Actions(Actions.NEXT_COLUMN_EXTEND_SELECTION,
  1086. 1, 0, true, false));
  1087. map.put(new Actions(Actions.PREVIOUS_COLUMN_EXTEND_SELECTION,
  1088. -1, 0, true, false));
  1089. map.put(new Actions(Actions.NEXT_ROW_EXTEND_SELECTION,
  1090. 0, 1, true, false));
  1091. map.put(new Actions(Actions.PREVIOUS_ROW_EXTEND_SELECTION,
  1092. 0, -1, true, false));
  1093. map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION,
  1094. false, false, true, false));
  1095. map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION,
  1096. false, true, true, false));
  1097. map.put(new Actions(Actions.FIRST_COLUMN,
  1098. false, false, false, true));
  1099. map.put(new Actions(Actions.LAST_COLUMN,
  1100. false, true, false, true));
  1101. map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION,
  1102. true, false, true, false));
  1103. map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION,
  1104. true, true, true, false));
  1105. map.put(new Actions(Actions.FIRST_COLUMN_EXTEND_SELECTION,
  1106. true, false, false, true));
  1107. map.put(new Actions(Actions.LAST_COLUMN_EXTEND_SELECTION,
  1108. true, true, false, true));
  1109. map.put(new Actions(Actions.FIRST_ROW, false, false, true, true));
  1110. map.put(new Actions(Actions.LAST_ROW, false, true, true, true));
  1111. map.put(new Actions(Actions.FIRST_ROW_EXTEND_SELECTION,
  1112. true, false, true, true));
  1113. map.put(new Actions(Actions.LAST_ROW_EXTEND_SELECTION,
  1114. true, true, true, true));
  1115. map.put(new Actions(Actions.NEXT_COLUMN_CELL,
  1116. 1, 0, false, true));
  1117. map.put(new Actions(Actions.PREVIOUS_COLUMN_CELL,
  1118. -1, 0, false, true));
  1119. map.put(new Actions(Actions.NEXT_ROW_CELL, 0, 1, false, true));
  1120. map.put(new Actions(Actions.PREVIOUS_ROW_CELL,
  1121. 0, -1, false, true));
  1122. map.put(new Actions(Actions.SELECT_ALL));
  1123. map.put(new Actions(Actions.CLEAR_SELECTION));
  1124. map.put(new Actions(Actions.CANCEL_EDITING));
  1125. map.put(new Actions(Actions.START_EDITING));
  1126. map.put(TransferHandler.getCutAction().getValue(Action.NAME),
  1127. TransferHandler.getCutAction());
  1128. map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
  1129. TransferHandler.getCopyAction());
  1130. map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
  1131. TransferHandler.getPasteAction());
  1132. map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_SELECTION,
  1133. false, false, false, false));
  1134. map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_SELECTION,
  1135. false, true, false, false));
  1136. map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION,
  1137. true, false, false, false));
  1138. map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION,
  1139. true, true, false, false));
  1140. map.put(new Actions(Actions.ADD_TO_SELECTION));
  1141. map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
  1142. map.put(new Actions(Actions.EXTEND_TO));
  1143. map.put(new Actions(Actions.MOVE_SELECTION_TO));
  1144. }
  1145. // Uninstallation
  1146. public void uninstallUI(JComponent c) {
  1147. uninstallDefaults();
  1148. uninstallListeners();
  1149. uninstallKeyboardActions();
  1150. table.remove(rendererPane);
  1151. rendererPane = null;
  1152. table = null;
  1153. }
  1154. protected void uninstallDefaults() {
  1155. if (table.getTransferHandler() instanceof UIResource) {
  1156. table.setTransferHandler(null);
  1157. }
  1158. }
  1159. protected void uninstallListeners() {
  1160. table.removeFocusListener(focusListener);
  1161. table.removeKeyListener(keyListener);
  1162. table.removeMouseListener(defaultDragRecognizer);
  1163. table.removeMouseMotionListener(defaultDragRecognizer);
  1164. table.removeMouseListener(mouseInputListener);
  1165. table.removeMouseMotionListener(mouseInputListener);
  1166. table.removePropertyChangeListener(getHandler());
  1167. focusListener = null;
  1168. keyListener = null;
  1169. mouseInputListener = null;
  1170. handler = null;
  1171. }
  1172. protected void uninstallKeyboardActions() {
  1173. SwingUtilities.replaceUIInputMap(table, JComponent.
  1174. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
  1175. SwingUtilities.replaceUIActionMap(table, null);
  1176. }
  1177. //
  1178. // Size Methods
  1179. //
  1180. private Dimension createTableSize(long width) {
  1181. int height = 0;
  1182. int rowCount = table.getRowCount();
  1183. if (rowCount > 0 && table.getColumnCount() > 0) {
  1184. Rectangle r = table.getCellRect(rowCount-1, 0, true);
  1185. height = r.y + r.height;
  1186. }
  1187. // Width is always positive. The call to abs() is a workaround for
  1188. // a bug in the 1.1.6 JIT on Windows.
  1189. long tmp = Math.abs(width);
  1190. if (tmp > Integer.MAX_VALUE) {
  1191. tmp = Integer.MAX_VALUE;
  1192. }
  1193. return new Dimension((int)tmp, height);
  1194. }
  1195. /**
  1196. * Return the minimum size of the table. The minimum height is the
  1197. * row height times the number of rows.
  1198. * The minimum width is the sum of the minimum widths of each column.
  1199. */
  1200. public Dimension getMinimumSize(JComponent c) {
  1201. long width = 0;
  1202. Enumeration enumeration = table.getColumnModel().getColumns();
  1203. while (enumeration.hasMoreElements()) {
  1204. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  1205. width = width + aColumn.getMinWidth();
  1206. }
  1207. return createTableSize(width);
  1208. }
  1209. /**
  1210. * Return the preferred size of the table. The preferred height is the
  1211. * row height times the number of rows.
  1212. * The preferred width is the sum of the preferred widths of each column.
  1213. */
  1214. public Dimension getPreferredSize(JComponent c) {
  1215. long width = 0;
  1216. Enumeration enumeration = table.getColumnModel().getColumns();
  1217. while (enumeration.hasMoreElements()) {
  1218. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  1219. width = width + aColumn.getPreferredWidth();
  1220. }
  1221. return createTableSize(width);
  1222. }
  1223. /**
  1224. * Return the maximum size of the table. The maximum height is the
  1225. * row heighttimes the number of rows.
  1226. * The maximum width is the sum of the maximum widths of each column.
  1227. */
  1228. public Dimension getMaximumSize(JComponent c) {
  1229. long width = 0;
  1230. Enumeration enumeration = table.getColumnModel().getColumns();
  1231. while (enumeration.hasMoreElements()) {
  1232. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  1233. width = width + aColumn.getMaxWidth();
  1234. }
  1235. return createTableSize(width);
  1236. }
  1237. //
  1238. // Paint methods and support
  1239. //
  1240. /** Paint a representation of the <code>table</code> instance
  1241. * that was set in installUI().
  1242. */
  1243. public void paint(Graphics g, JComponent c) {
  1244. Rectangle clip = g.getClipBounds();
  1245. Rectangle bounds = table.getBounds();
  1246. // account for the fact that the graphics has already been translated
  1247. // into the table's bounds
  1248. bounds.x = bounds.y = 0;
  1249. if (table.getRowCount() <= 0 || table.getColumnCount() <= 0 ||
  1250. // this check prevents us from painting the entire table
  1251. // when the clip doesn't intersect our bounds at all
  1252. !bounds.intersects(clip)) {
  1253. return;
  1254. }
  1255. Point upperLeft = clip.getLocation();
  1256. Point lowerRight = new Point(clip.x + clip.width - 1, clip.y + clip.height - 1);
  1257. int rMin = table.rowAtPoint(upperLeft);
  1258. int rMax = table.rowAtPoint(lowerRight);
  1259. // This should never happen (as long as our bounds intersect the clip,
  1260. // which is why we bail above if that is the case).
  1261. if (rMin == -1) {
  1262. rMin = 0;
  1263. }
  1264. // If the table does not have enough rows to fill the view we'll get -1.
  1265. // (We could also get -1 if our bounds don't intersect the clip,
  1266. // which is why we bail above if that is the case).
  1267. // Replace this with the index of the last row.
  1268. if (rMax == -1) {
  1269. rMax = table.getRowCount()-1;
  1270. }
  1271. boolean ltr = table.getComponentOrientation().isLeftToRight();
  1272. int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight);
  1273. int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);
  1274. // This should never happen.
  1275. if (cMin == -1) {
  1276. cMin = 0;
  1277. }
  1278. // If the table does not have enough columns to fill the view we'll get -1.
  1279. // Replace this with the index of the last column.
  1280. if (cMax == -1) {
  1281. cMax = table.getColumnCount()-1;
  1282. }
  1283. // Paint the grid.
  1284. paintGrid(g, rMin, rMax, cMin, cMax);
  1285. // Paint the cells.
  1286. paintCells(g, rMin, rMax, cMin, cMax);
  1287. }
  1288. /*
  1289. * Paints the grid lines within <I>aRect</I>, using the grid
  1290. * color set with <I>setGridColor</I>. Paints vertical lines
  1291. * if <code>getShowVerticalLines()</code> returns true and paints
  1292. * horizontal lines if <code>getShowHorizontalLines()</code>
  1293. * returns true.
  1294. */
  1295. private void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) {
  1296. g.setColor(table.getGridColor());
  1297. Rectangle minCell = table.getCellRect(rMin, cMin, true);
  1298. Rectangle maxCell = table.getCellRect(rMax, cMax, true);
  1299. Rectangle damagedArea = minCell.union( maxCell );
  1300. if (table.getShowHorizontalLines()) {
  1301. int tableWidth = damagedArea.x + damagedArea.width;
  1302. int y = damagedArea.y;
  1303. for (int row = rMin; row <= rMax; row++) {
  1304. y += table.getRowHeight(row);
  1305. g.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1);
  1306. }
  1307. }
  1308. if (table.getShowVerticalLines()) {
  1309. TableColumnModel cm = table.getColumnModel();
  1310. int tableHeight = damagedArea.y + damagedArea.height;
  1311. int x;
  1312. if (table.getComponentOrientation().isLeftToRight()) {
  1313. x = damagedArea.x;
  1314. for (int column = cMin; column <= cMax; column++) {
  1315. int w = cm.getColumn(column).getWidth();
  1316. x += w;
  1317. g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
  1318. }
  1319. } else {
  1320. x = damagedArea.x + damagedArea.width;
  1321. for (int column = cMin; column < cMax; column++) {
  1322. int w = cm.getColumn(column).getWidth();
  1323. x -= w;
  1324. g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
  1325. }
  1326. x -= cm.getColumn(cMax).getWidth();
  1327. g.drawLine(x, 0, x, tableHeight - 1);
  1328. }
  1329. }
  1330. }
  1331. private int viewIndexForColumn(TableColumn aColumn) {
  1332. TableColumnModel cm = table.getColumnModel();
  1333. for (int column = 0; column < cm.getColumnCount(); column++) {
  1334. if (cm.getColumn(column) == aColumn) {
  1335. return column;
  1336. }
  1337. }
  1338. return -1;
  1339. }
  1340. private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
  1341. JTableHeader header = table.getTableHeader();
  1342. TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
  1343. TableColumnModel cm = table.getColumnModel();
  1344. int columnMargin = cm.getColumnMargin();
  1345. Rectangle cellRect;
  1346. TableColumn aColumn;
  1347. int columnWidth;
  1348. if (table.getComponentOrientation().isLeftToRight()) {
  1349. for(int row = rMin; row <= rMax; row++) {
  1350. cellRect = table.getCellRect(row, cMin, false);
  1351. for(int column = cMin; column <= cMax; column++) {
  1352. aColumn = cm.getColumn(column);
  1353. columnWidth = aColumn.getWidth();
  1354. cellRect.width = columnWidth - columnMargin;
  1355. if (aColumn != draggedColumn) {
  1356. paintCell(g, cellRect, row, column);
  1357. }
  1358. cellRect.x += columnWidth;
  1359. }
  1360. }
  1361. } else {
  1362. for(int row = rMin; row <= rMax; row++) {
  1363. cellRect = table.getCellRect(row, cMin, false);
  1364. aColumn = cm.getColumn(cMin);
  1365. if (aColumn != draggedColumn) {
  1366. columnWidth = aColumn.getWidth();
  1367. cellRect.width = columnWidth - columnMargin;
  1368. paintCell(g, cellRect, row, cMin);
  1369. }
  1370. for(int column = cMin+1; column <= cMax; column++) {
  1371. aColumn = cm.getColumn(column);
  1372. columnWidth = aColumn.getWidth();
  1373. cellRect.width = columnWidth - columnMargin;
  1374. cellRect.x -= columnWidth;
  1375. if (aColumn != draggedColumn) {
  1376. paintCell(g, cellRect, row, column);
  1377. }
  1378. }
  1379. }
  1380. }
  1381. // Paint the dragged column if we are dragging.
  1382. if (draggedColumn != null) {
  1383. paintDraggedArea(g, rMin, rMax, draggedColumn, header.getDraggedDistance());
  1384. }
  1385. // Remove any renderers that may be left in the rendererPane.
  1386. rendererPane.removeAll();
  1387. }
  1388. private void paintDraggedArea(Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) {
  1389. int draggedColumnIndex = viewIndexForColumn(draggedColumn);
  1390. Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
  1391. Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
  1392. Rectangle vacatedColumnRect = minCell.union(maxCell);
  1393. // Paint a gray well in place of the moving column.
  1394. g.setColor(table.getParent().getBackground());
  1395. g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
  1396. vacatedColumnRect.width, vacatedColumnRect.height);
  1397. // Move to the where the cell has been dragged.
  1398. vacatedColumnRect.x += distance;
  1399. // Fill the background.
  1400. g.setColor(table.getBackground());
  1401. g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
  1402. vacatedColumnRect.width, vacatedColumnRect.height);
  1403. // Paint the vertical grid lines if necessary.
  1404. if (table.getShowVerticalLines()) {
  1405. g.setColor(table.getGridColor());
  1406. int x1 = vacatedColumnRect.x;
  1407. int y1 = vacatedColumnRect.y;
  1408. int x2 = x1 + vacatedColumnRect.width - 1;
  1409. int y2 = y1 + vacatedColumnRect.height - 1;
  1410. // Left
  1411. g.drawLine(x1-1, y1, x1-1, y2);
  1412. // Right
  1413. g.drawLine(x2, y1, x2, y2);
  1414. }
  1415. for(int row = rMin; row <= rMax; row++) {
  1416. // Render the cell value
  1417. Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
  1418. r.x += distance;
  1419. paintCell(g, r, row, draggedColumnIndex);
  1420. // Paint the (lower) horizontal grid line if necessary.
  1421. if (table.getShowHorizontalLines()) {
  1422. g.setColor(table.getGridColor());
  1423. Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
  1424. rcr.x += distance;
  1425. int x1 = rcr.x;
  1426. int y1 = rcr.y;
  1427. int x2 = x1 + rcr.width - 1;
  1428. int y2 = y1 + rcr.height - 1;
  1429. g.drawLine(x1, y2, x2, y2);
  1430. }
  1431. }
  1432. }
  1433. private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
  1434. if (table.isEditing() && table.getEditingRow()==row &&
  1435. table.getEditingColumn()==column) {
  1436. Component component = table.getEditorComponent();
  1437. component.setBounds(cellRect);
  1438. component.validate();
  1439. }
  1440. else {
  1441. TableCellRenderer renderer = table.getCellRenderer(row, column);
  1442. Component component = table.prepareRenderer(renderer, row, column);
  1443. rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
  1444. cellRect.width, cellRect.height, true);
  1445. }
  1446. }
  1447. private static final TableDragGestureRecognizer defaultDragRecognizer = new TableDragGestureRecognizer();
  1448. /**
  1449. * Drag gesture recognizer for JTable components
  1450. */
  1451. static class TableDragGestureRecognizer extends BasicDragGestureRecognizer {
  1452. /**
  1453. * Determines if the following are true:
  1454. * <ul>
  1455. * <li>the press event is located over a selection
  1456. * <li>the dragEnabled property is true
  1457. * <li>A TranferHandler is installed
  1458. * </ul>
  1459. * <p>
  1460. * This is implemented to perform the superclass behavior
  1461. * followed by a check if the dragEnabled
  1462. * property is set and if the location picked is selected.
  1463. */
  1464. protected boolean isDragPossible(MouseEvent e) {
  1465. if (super.isDragPossible(e)) {
  1466. JTable table = (JTable) this.getComponent(e);
  1467. if (table.getDragEnabled()) {
  1468. Point p = e.getPoint();
  1469. int row = table.rowAtPoint(p);
  1470. int column = table.columnAtPoint(p);
  1471. // For 4835633. Otherwise, you can drag a file by clicking below
  1472. // it.
  1473. if (pointOutsidePrefSize(table, row, column, p)) {
  1474. return false;
  1475. }
  1476. if ((column != -1) && (row != -1) && table.isCellSelected(row, column)) {
  1477. return true;
  1478. }
  1479. }
  1480. }
  1481. return false;
  1482. }
  1483. }
  1484. private static DropTargetListener defaultDropTargetListener = null;
  1485. /**
  1486. * A DropTargetListener to extend the default Swing handling of drop operations
  1487. * by moving the tree selection to the nearest location to the mouse pointer.
  1488. * Also adds autoscroll capability.
  1489. */
  1490. static class TableDropTargetListener extends BasicDropTargetListener {
  1491. /**
  1492. * called to save the state of a component in case it needs to
  1493. * be restored because a drop is not performed.
  1494. */
  1495. protected void saveComponentState(JComponent comp) {
  1496. JTable table = (JTable) comp;
  1497. rows = table.getSelectedRows();
  1498. cols = table.getSelectedColumns();
  1499. }
  1500. /**
  1501. * called to restore the state of a component
  1502. * because a drop was not performed.
  1503. */
  1504. protected void restoreComponentState(JComponent comp) {
  1505. JTable table = (JTable) comp;
  1506. table.clearSelection();
  1507. for (int i = 0; i < rows.length; i++) {
  1508. table.addRowSelectionInterval(rows[i], rows[i]);
  1509. }
  1510. for (int i = 0; i < cols.length; i++) {
  1511. table.addColumnSelectionInterval(cols[i], cols[i]);
  1512. }
  1513. }
  1514. /**
  1515. * called to set the insertion location to match the current
  1516. * mouse pointer coordinates.
  1517. */
  1518. protected void updateInsertionLocation(JComponent comp, Point p) {
  1519. JTable table = (JTable) comp;
  1520. int row = table.rowAtPoint(p);
  1521. int col = table.columnAtPoint(p);
  1522. if (row != -1) {
  1523. table.setRowSelectionInterval(row, row);
  1524. }
  1525. if (col != -1) {
  1526. table.setColumnSelectionInterval(col, col);
  1527. }
  1528. }
  1529. private int[] rows;
  1530. private int[] cols;
  1531. }
  1532. private static final TransferHandler defaultTransferHandler = new TableTransferHandler();
  1533. static class TableTransferHandler extends TransferHandler implements UIResource {
  1534. /**
  1535. * Create a Transferable to use as the source for a data transfer.
  1536. *
  1537. * @param c The component holding the data to be transfered. This
  1538. * argument is provided to enable sharing of TransferHandlers by
  1539. * multiple components.
  1540. * @return The representation of the data to be transfered.
  1541. *
  1542. */
  1543. protected Transferable createTransferable(JComponent c) {
  1544. if (c instanceof JTable) {
  1545. JTable table = (JTable) c;
  1546. int[] rows;
  1547. int[] cols;
  1548. if (!table.getRowSelectionAllowed() && !table.getColumnSelectionAllowed()) {
  1549. return null;
  1550. }
  1551. if (!table.getRowSelectionAllowed()) {
  1552. int rowCount = table.getRowCount();
  1553. rows = new int[rowCount];
  1554. for (int counter = 0; counter < rowCount; counter++) {
  1555. rows[counter] = counter;
  1556. }
  1557. } else {
  1558. rows = table.getSelectedRows();
  1559. }
  1560. if (!table.getColumnSelectionAllowed()) {
  1561. int colCount = table.getColumnCount();
  1562. cols = new int[colCount];
  1563. for (int counter = 0; counter < colCount; counter++) {
  1564. cols[counter] = counter;
  1565. }
  1566. } else {
  1567. cols = table.getSelectedColumns();
  1568. }
  1569. if (rows == null || cols == null || rows.length == 0 || cols.length == 0) {
  1570. return null;
  1571. }
  1572. StringBuffer plainBuf = new StringBuffer();
  1573. StringBuffer htmlBuf = new StringBuffer();
  1574. htmlBuf.append("<html>\n<body>\n<table>\n");
  1575. for (int row = 0; row < rows.length; row++) {
  1576. htmlBuf.append("<tr>\n");
  1577. for (int col = 0; col < cols.length; col++) {
  1578. Object obj = table.getValueAt(rows[row], cols[col]);
  1579. String val = ((obj == null) ? "" : obj.toString());
  1580. plainBuf.append(val + "\t");
  1581. htmlBuf.append(" <td>" + val + "</td>\n");
  1582. }
  1583. // we want a newline at the end of each line and not a tab
  1584. plainBuf.deleteCharAt(plainBuf.length() - 1).append("\n");
  1585. htmlBuf.append("</tr>\n");
  1586. }
  1587. // remove the last newline
  1588. plainBuf.deleteCharAt(plainBuf.length() - 1);
  1589. htmlBuf.append("</table>\n</body>\n</html>");
  1590. return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
  1591. }
  1592. return null;
  1593. }
  1594. public int getSourceActions(JComponent c) {
  1595. return COPY;
  1596. }
  1597. }
  1598. } // End of Class BasicTableUI