1. /*
  2. * @(#)BasicTableUI.java 1.123 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import javax.swing.table.*;
  9. import javax.swing.*;
  10. import javax.swing.event.*;
  11. import java.util.Enumeration;
  12. import java.util.Hashtable;
  13. import java.util.TooManyListenersException;
  14. import java.awt.event.*;
  15. import java.awt.*;
  16. import java.awt.datatransfer.*;
  17. import java.awt.dnd.*;
  18. import javax.swing.plaf.*;
  19. import java.util.EventObject;
  20. import javax.swing.text.*;
  21. import java.beans.PropertyChangeEvent;
  22. import java.beans.PropertyChangeListener;
  23. /**
  24. * BasicTableUI implementation
  25. *
  26. * @version 1.117 08/27/01
  27. * @author Philip Milne
  28. */
  29. public class BasicTableUI extends TableUI
  30. {
  31. //
  32. // Instance Variables
  33. //
  34. // The JTable that is delegating the painting to this UI.
  35. protected JTable table;
  36. protected CellRendererPane rendererPane;
  37. // Listeners that are attached to the JTable
  38. protected KeyListener keyListener;
  39. protected FocusListener focusListener;
  40. protected MouseInputListener mouseInputListener;
  41. private PropertyChangeListener propertyChangeListener;
  42. //
  43. // Helper class for keyboard actions
  44. //
  45. private static class NavigationalAction extends AbstractAction {
  46. protected int dx;
  47. protected int dy;
  48. protected boolean toggle;
  49. protected boolean extend;
  50. protected boolean inSelection;
  51. protected int anchorRow;
  52. protected int anchorColumn;
  53. protected int leadRow;
  54. protected int leadColumn;
  55. protected NavigationalAction(int dx, int dy, boolean toggle, boolean extend,
  56. boolean inSelection) {
  57. this.dx = dx;
  58. this.dy = dy;
  59. this.toggle = toggle;
  60. this.extend = extend;
  61. this.inSelection = inSelection;
  62. }
  63. private int clipToRange(int i, int a, int b) {
  64. return Math.min(Math.max(i, a), b-1);
  65. }
  66. private void moveWithinTableRange(JTable table, int dx, int dy, boolean changeLead) {
  67. if (changeLead) {
  68. leadRow = clipToRange(leadRow+dy, 0, table.getRowCount());
  69. leadColumn = clipToRange(leadColumn+dx, 0, table.getColumnCount());
  70. }
  71. else {
  72. anchorRow = clipToRange(anchorRow+dy, 0, table.getRowCount());
  73. anchorColumn = clipToRange(anchorColumn+dx, 0, table.getColumnCount());
  74. }
  75. }
  76. private int selectionSpan(ListSelectionModel sm) {
  77. return sm.getMaxSelectionIndex() - sm.getMinSelectionIndex() + 1;
  78. }
  79. private int compare(int i, ListSelectionModel sm) {
  80. return compare(i, sm.getMinSelectionIndex(), sm.getMaxSelectionIndex()+1);
  81. }
  82. private int compare(int i, int a, int b) {
  83. return (i < a) ? -1 : (i >= b) ? 1 : 0 ;
  84. }
  85. private boolean moveWithinSelectedRange(JTable table, int dx, int dy, boolean ignoreCarry) {
  86. ListSelectionModel rsm = table.getSelectionModel();
  87. ListSelectionModel csm = table.getColumnModel().getSelectionModel();
  88. int newAnchorRow = anchorRow + dy;
  89. int newAnchorColumn = anchorColumn + dx;
  90. int rowSgn;
  91. int colSgn;
  92. int rowCount = selectionSpan(rsm);
  93. int columnCount = selectionSpan(csm);
  94. boolean canStayInSelection = (rowCount * columnCount > 1);
  95. if (canStayInSelection) {
  96. rowSgn = compare(newAnchorRow, rsm);
  97. colSgn = compare(newAnchorColumn, csm);
  98. }
  99. else {
  100. // If there is only one selected cell, there is no point
  101. // in trying to stay within the selected area. Move outside
  102. // the selection, wrapping at the table boundaries.
  103. rowCount = table.getRowCount();
  104. columnCount = table.getColumnCount();
  105. rowSgn = compare(newAnchorRow, 0, rowCount);
  106. colSgn = compare(newAnchorColumn, 0, columnCount);
  107. }
  108. anchorRow = newAnchorRow - rowCount * rowSgn;
  109. anchorColumn = newAnchorColumn - columnCount * colSgn;
  110. if (!ignoreCarry) {
  111. return moveWithinSelectedRange(table, rowSgn, colSgn, true);
  112. }
  113. return canStayInSelection;
  114. }
  115. public void actionPerformed(ActionEvent e) {
  116. JTable table = (JTable)e.getSource();
  117. ListSelectionModel rsm = table.getSelectionModel();
  118. anchorRow = rsm.getAnchorSelectionIndex();
  119. leadRow = rsm.getLeadSelectionIndex();
  120. ListSelectionModel csm = table.getColumnModel().getSelectionModel();
  121. anchorColumn = csm.getAnchorSelectionIndex();
  122. leadColumn = csm.getLeadSelectionIndex();
  123. int oldAnchorRow = anchorRow;
  124. int oldAnchorColumn = anchorColumn;
  125. if (table.isEditing() && !table.getCellEditor().stopCellEditing()) {
  126. return;
  127. }
  128. // Unfortunately, this strategy introduces bugs because
  129. // of the asynchronous nature of requestFocus() call below.
  130. // Introducing a delay with invokeLater() makes this work
  131. // in the typical case though race conditions then allow
  132. // focus to disappear altogether. The right solution appears
  133. // to be to fix requestFocus() so that it queues a request
  134. // for the focus regardless of who owns the focus at the
  135. // time the call to requestFocus() is made. The optimisation
  136. // to ignore the call to requestFocus() when the component
  137. // already has focus may ligitimately be made as the
  138. // request focus event is dequeued, not before.
  139. // boolean wasEditingWithFocus = table.isEditing() && table.getEditorComponent().isFocusOwner();
  140. if (!inSelection) {
  141. moveWithinTableRange(table, dx, dy, extend);
  142. if (!extend) {
  143. table.changeSelection(anchorRow, anchorColumn, false, extend);
  144. }
  145. else {
  146. table.changeSelection(leadRow, leadColumn, false, extend);
  147. }
  148. }
  149. else {
  150. if (moveWithinSelectedRange(table, dx, dy, false)) {
  151. table.changeSelection(anchorRow, anchorColumn, true, true);
  152. }
  153. else {
  154. table.changeSelection(anchorRow, anchorColumn, false, false);
  155. }
  156. }
  157. /*
  158. if (wasEditingWithFocus) {
  159. table.editCellAt(anchorRow, anchorColumn);
  160. final Component editorComp = table.getEditorComponent();
  161. if (editorComp != null) {
  162. SwingUtilities.invokeLater(new Runnable() {
  163. public void run() {
  164. editorComp.requestFocus();
  165. }
  166. });
  167. }
  168. }
  169. */
  170. }
  171. }
  172. private static class PagingAction extends NavigationalAction {
  173. private boolean forwards;
  174. private boolean vertically;
  175. private boolean toLimit;
  176. private PagingAction(boolean extend, boolean forwards,
  177. boolean vertically, boolean toLimit) {
  178. super(0, 0, false, extend, false);
  179. this.forwards = forwards;
  180. this.vertically = vertically;
  181. this.toLimit = toLimit;
  182. }
  183. public void actionPerformed(ActionEvent e) {
  184. JTable table = (JTable)e.getSource();
  185. if (toLimit) {
  186. if (vertically) {
  187. int rowCount = table.getRowCount();
  188. this.dx = 0;
  189. this.dy = forwards ? rowCount : -rowCount;
  190. }
  191. else {
  192. int colCount = table.getColumnCount();
  193. this.dx = forwards ? colCount : -colCount;
  194. this.dy = 0;
  195. }
  196. }
  197. else {
  198. if (!(table.getParent().getParent() instanceof JScrollPane)) {
  199. return;
  200. }
  201. Dimension delta = table.getParent().getSize();
  202. ListSelectionModel sm = (vertically)
  203. ? table.getSelectionModel()
  204. : table.getColumnModel().getSelectionModel();
  205. int start = (extend) ? sm.getLeadSelectionIndex()
  206. : sm.getAnchorSelectionIndex();
  207. if (vertically) {
  208. Rectangle r = table.getCellRect(start, 0, true);
  209. r.y += forwards ? delta.height : -delta.height;
  210. this.dx = 0;
  211. int newRow = table.rowAtPoint(r.getLocation());
  212. if (newRow == -1 && forwards) {
  213. newRow = table.getRowCount();
  214. }
  215. this.dy = newRow - start;
  216. }
  217. else {
  218. Rectangle r = table.getCellRect(0, start, true);
  219. r.x += forwards ? delta.width : -delta.width;
  220. int newColumn = table.columnAtPoint(r.getLocation());
  221. if (newColumn == -1 && forwards) {
  222. newColumn = table.getColumnCount();
  223. }
  224. this.dx = newColumn - start;
  225. this.dy = 0;
  226. }
  227. }
  228. super.actionPerformed(e);
  229. }
  230. }
  231. /**
  232. * Action to invoke <code>selectAll</code> on the table.
  233. */
  234. private static class SelectAllAction extends AbstractAction {
  235. public void actionPerformed(ActionEvent e) {
  236. JTable table = (JTable)e.getSource();
  237. table.selectAll();
  238. }
  239. }
  240. /**
  241. * Action to invoke <code>removeEditor</code> on the table.
  242. */
  243. private static class CancelEditingAction extends AbstractAction {
  244. public void actionPerformed(ActionEvent e) {
  245. JTable table = (JTable)e.getSource();
  246. table.removeEditor();
  247. }
  248. }
  249. /**
  250. * Action to start editing, and pass focus to the editor.
  251. */
  252. private static class StartEditingAction extends AbstractAction {
  253. public void actionPerformed(ActionEvent e) {
  254. JTable table = (JTable)e.getSource();
  255. if (!table.hasFocus()) {
  256. CellEditor cellEditor = table.getCellEditor();
  257. if (cellEditor != null && !cellEditor.stopCellEditing()) {
  258. return;
  259. }
  260. table.requestFocus();
  261. return;
  262. }
  263. ListSelectionModel rsm = table.getSelectionModel();
  264. int anchorRow = rsm.getAnchorSelectionIndex();
  265. ListSelectionModel csm = table.getColumnModel().getSelectionModel();
  266. int anchorColumn = csm.getAnchorSelectionIndex();
  267. table.editCellAt(anchorRow, anchorColumn);
  268. Component editorComp = table.getEditorComponent();
  269. if (editorComp != null) {
  270. editorComp.requestFocus();
  271. }
  272. }
  273. }
  274. //
  275. // The Table's Key listener
  276. //
  277. /**
  278. * This inner class is marked "public" due to a compiler bug.
  279. * This class should be treated as a "protected" inner class.
  280. * Instantiate it only within subclasses of BasicTableUI.
  281. * <p>As of Java 2 platform v1.3 this class is no longer used.
  282. * Instead <code>JTable</code>
  283. * overrides <code>processKeyBinding</code> to dispatch the event to
  284. * the current <code>TableCellEditor</code>.
  285. */
  286. public class KeyHandler implements KeyListener {
  287. public void keyPressed(KeyEvent e) { }
  288. public void keyReleased(KeyEvent e) { }
  289. public void keyTyped(KeyEvent e) {
  290. KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyChar(), e.getModifiers());
  291. // We register all actions using ANCESTOR_OF_FOCUSED_COMPONENT
  292. // which means that we might perform the appropriate action
  293. // in the table and then forward it to the editor if the editor
  294. // had focus. Make sure this doesn't happen by checking our
  295. // InputMaps.
  296. InputMap map = table.getInputMap(JComponent.WHEN_FOCUSED);
  297. if (map != null && map.get(keyStroke) != null) {
  298. return;
  299. }
  300. map = table.getInputMap(JComponent.
  301. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  302. if (map != null && map.get(keyStroke) != null) {
  303. return;
  304. }
  305. keyStroke = KeyStroke.getKeyStrokeForEvent(e);
  306. // The AWT seems to generate an unconsumed \r event when
  307. // ENTER (\n) is pressed.
  308. if (e.getKeyChar() == '\r') {
  309. return;
  310. }
  311. int anchorRow = table.getSelectionModel().getAnchorSelectionIndex();
  312. int anchorColumn =
  313. table.getColumnModel().getSelectionModel().getAnchorSelectionIndex();
  314. if (anchorRow != -1 && anchorColumn != -1 && !table.isEditing()) {
  315. if (!table.editCellAt(anchorRow, anchorColumn)) {
  316. return;
  317. }
  318. }
  319. // Forwarding events this way seems to put the component
  320. // in a state where it believes it has focus. In reality
  321. // the table retains focus - though it is difficult for
  322. // a user to tell, since the caret is visible and flashing.
  323. // Calling table.requestFocus() here, to get the focus back to
  324. // the table, seems to have no effect.
  325. Component editorComp = table.getEditorComponent();
  326. if (table.isEditing() && editorComp != null) {
  327. if (editorComp instanceof JComponent) {
  328. JComponent component = (JComponent)editorComp;
  329. map = component.getInputMap(JComponent.WHEN_FOCUSED);
  330. Object binding = (map != null) ? map.get(keyStroke) : null;
  331. if (binding == null) {
  332. map = component.getInputMap(JComponent.
  333. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  334. binding = (map != null) ? map.get(keyStroke) : null;
  335. }
  336. if (binding != null) {
  337. ActionMap am = component.getActionMap();
  338. Action action = (am != null) ? am.get(binding) : null;
  339. if (action != null && SwingUtilities.
  340. notifyAction(action, keyStroke, e, component,
  341. e.getModifiers())) {
  342. e.consume();
  343. }
  344. }
  345. }
  346. }
  347. }
  348. }
  349. //
  350. // The Table's focus listener
  351. //
  352. /**
  353. * This inner class is marked "public" due to a compiler bug.
  354. * This class should be treated as a "protected" inner class.
  355. * Instantiate it only within subclasses of BasicTableUI.
  356. */
  357. public class FocusHandler implements FocusListener {
  358. private void repaintAnchorCell( ) {
  359. int rc = table.getRowCount();
  360. int cc = table.getColumnCount();
  361. int ar = table.getSelectionModel().getAnchorSelectionIndex();
  362. int ac = table.getColumnModel().getSelectionModel().getAnchorSelectionIndex();
  363. if (ar < 0 || ar >= rc || ac < 0 || ac >= cc) {
  364. return;
  365. }
  366. Rectangle dirtyRect = table.getCellRect(ar, ac, false);
  367. table.repaint(dirtyRect);
  368. }
  369. public void focusGained(FocusEvent e) {
  370. repaintAnchorCell();
  371. }
  372. public void focusLost(FocusEvent e) {
  373. repaintAnchorCell();
  374. }
  375. }
  376. //
  377. // The Table's mouse and mouse motion listeners
  378. //
  379. /**
  380. * This inner class is marked "public" due to a compiler bug.
  381. * This class should be treated as a "protected" inner class.
  382. * Instantiate it only within subclasses of BasicTableUI.
  383. */
  384. public class MouseInputHandler implements MouseInputListener {
  385. // Component receiving mouse events during editing.
  386. // May not be editorComponent.
  387. private Component dispatchComponent;
  388. private boolean selectedOnPress;
  389. // The Table's mouse listener methods.
  390. public void mouseClicked(MouseEvent e) {}
  391. private void setDispatchComponent(MouseEvent e) {
  392. Component editorComponent = table.getEditorComponent();
  393. Point p = e.getPoint();
  394. Point p2 = SwingUtilities.convertPoint(table, p, editorComponent);
  395. dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent,
  396. p2.x, p2.y);
  397. }
  398. private boolean repostEvent(MouseEvent e) {
  399. // Check for isEditing() in case another event has
  400. // caused the editor to be removed. See bug #4306499.
  401. if (dispatchComponent == null || !table.isEditing()) {
  402. return false;
  403. }
  404. MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e, dispatchComponent);
  405. dispatchComponent.dispatchEvent(e2);
  406. return true;
  407. }
  408. private void setValueIsAdjusting(boolean flag) {
  409. table.getSelectionModel().setValueIsAdjusting(flag);
  410. table.getColumnModel().getSelectionModel().setValueIsAdjusting(flag);
  411. }
  412. private boolean shouldIgnore(MouseEvent e) {
  413. return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) && table.isEnabled()));
  414. }
  415. public void mousePressed(MouseEvent e) {
  416. if (e.isConsumed()) {
  417. selectedOnPress = false;
  418. return;
  419. }
  420. selectedOnPress = true;
  421. adjustFocusAndSelection(e);
  422. }
  423. void adjustFocusAndSelection(MouseEvent e) {
  424. if (shouldIgnore(e)) {
  425. return;
  426. }
  427. Point p = e.getPoint();
  428. int row = table.rowAtPoint(p);
  429. int column = table.columnAtPoint(p);
  430. // The autoscroller can generate drag events outside the Table's range.
  431. if ((column == -1) || (row == -1)) {
  432. return;
  433. }
  434. if (table.editCellAt(row, column, e)) {
  435. setDispatchComponent(e);
  436. repostEvent(e);
  437. }
  438. else if (table.isRequestFocusEnabled()) {
  439. table.requestFocus();
  440. }
  441. CellEditor editor = table.getCellEditor();
  442. if (editor == null || editor.shouldSelectCell(e)) {
  443. boolean adjusting = (e.getID() == MouseEvent.MOUSE_PRESSED) ? true : false;
  444. setValueIsAdjusting(adjusting);
  445. table.changeSelection(row, column, e.isControlDown(), e.isShiftDown());
  446. }
  447. }
  448. public void mouseReleased(MouseEvent e) {
  449. if (selectedOnPress) {
  450. if (shouldIgnore(e)) {
  451. return;
  452. }
  453. repostEvent(e);
  454. dispatchComponent = null;
  455. setValueIsAdjusting(false);
  456. } else {
  457. adjustFocusAndSelection(e);
  458. }
  459. }
  460. public void mouseEntered(MouseEvent e) {}
  461. public void mouseExited(MouseEvent e) {}
  462. // The Table's mouse motion listener methods.
  463. public void mouseMoved(MouseEvent e) {}
  464. public void mouseDragged(MouseEvent e) {
  465. if (shouldIgnore(e)) {
  466. return;
  467. }
  468. repostEvent(e);
  469. CellEditor editor = table.getCellEditor();
  470. if (editor == null || editor.shouldSelectCell(e)) {
  471. Point p = e.getPoint();
  472. int row = table.rowAtPoint(p);
  473. int column = table.columnAtPoint(p);
  474. // The autoscroller can generate drag events outside the Table's range.
  475. if ((column == -1) || (row == -1)) {
  476. return;
  477. }
  478. table.changeSelection(row, column, false, true);
  479. }
  480. }
  481. }
  482. //
  483. // Factory methods for the Listeners
  484. //
  485. /**
  486. * Creates the key listener for handling keyboard navigation in the JTable.
  487. */
  488. protected KeyListener createKeyListener() {
  489. return null;
  490. }
  491. /**
  492. * Creates the focus listener for handling keyboard navigation in the JTable.
  493. */
  494. protected FocusListener createFocusListener() {
  495. return new FocusHandler();
  496. }
  497. /**
  498. * Creates the mouse listener for the JTable.
  499. */
  500. protected MouseInputListener createMouseInputListener() {
  501. return new MouseInputHandler();
  502. }
  503. /**
  504. * Creates the property change listener for the JTable.
  505. */
  506. private PropertyChangeListener createPropertyChangeListener() {
  507. return new PropertyChangeHandler();
  508. }
  509. //
  510. // The installation/uninstall procedures and support
  511. //
  512. public static ComponentUI createUI(JComponent c) {
  513. return new BasicTableUI();
  514. }
  515. // Installation
  516. public void installUI(JComponent c) {
  517. table = (JTable)c;
  518. rendererPane = new CellRendererPane();
  519. table.add(rendererPane);
  520. installDefaults();
  521. installListeners();
  522. installKeyboardActions();
  523. }
  524. /**
  525. * Initialize JTable properties, e.g. font, foreground, and background.
  526. * The font, foreground, and background properties are only set if their
  527. * current value is either null or a UIResource, other properties are set
  528. * if the current value is null.
  529. *
  530. * @see #installUI
  531. */
  532. protected void installDefaults() {
  533. LookAndFeel.installColorsAndFont(table, "Table.background",
  534. "Table.foreground", "Table.font");
  535. Color sbg = table.getSelectionBackground();
  536. if (sbg == null || sbg instanceof UIResource) {
  537. table.setSelectionBackground(UIManager.getColor("Table.selectionBackground"));
  538. }
  539. Color sfg = table.getSelectionForeground();
  540. if (sfg == null || sfg instanceof UIResource) {
  541. table.setSelectionForeground(UIManager.getColor("Table.selectionForeground"));
  542. }
  543. Color gridColor = table.getGridColor();
  544. if (gridColor == null || gridColor instanceof UIResource) {
  545. table.setGridColor(UIManager.getColor("Table.gridColor"));
  546. }
  547. // install the scrollpane border
  548. Container parent = table.getParent(); // should be viewport
  549. if (parent != null) {
  550. parent = parent.getParent(); // should be the scrollpane
  551. if (parent != null && parent instanceof JScrollPane) {
  552. LookAndFeel.installBorder((JScrollPane)parent, "Table.scrollPaneBorder");
  553. }
  554. }
  555. TransferHandler th = table.getTransferHandler();
  556. if (th == null || th instanceof UIResource) {
  557. table.setTransferHandler(defaultTransferHandler);
  558. }
  559. DropTarget dropTarget = table.getDropTarget();
  560. if (dropTarget instanceof UIResource) {
  561. if (defaultDropTargetListener == null) {
  562. defaultDropTargetListener = new TableDropTargetListener();
  563. }
  564. try {
  565. dropTarget.addDropTargetListener(defaultDropTargetListener);
  566. } catch (TooManyListenersException tmle) {
  567. // should not happen... swing drop target is multicast
  568. }
  569. }
  570. }
  571. /**
  572. * Attaches listeners to the JTable.
  573. */
  574. protected void installListeners() {
  575. focusListener = createFocusListener();
  576. keyListener = createKeyListener();
  577. mouseInputListener = createMouseInputListener();
  578. propertyChangeListener = createPropertyChangeListener();
  579. table.addFocusListener(focusListener);
  580. table.addKeyListener(keyListener);
  581. table.addMouseListener(defaultDragRecognizer);
  582. table.addMouseMotionListener(defaultDragRecognizer);
  583. table.addMouseListener(mouseInputListener);
  584. table.addMouseMotionListener(mouseInputListener);
  585. table.addPropertyChangeListener(propertyChangeListener);
  586. }
  587. /**
  588. * Register all keyboard actions on the JTable.
  589. */
  590. protected void installKeyboardActions() {
  591. ActionMap map = getActionMap();
  592. SwingUtilities.replaceUIActionMap(table, map);
  593. InputMap inputMap = getInputMap(JComponent.
  594. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  595. SwingUtilities.replaceUIInputMap(table,
  596. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  597. inputMap);
  598. }
  599. InputMap getInputMap(int condition) {
  600. if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
  601. InputMap keyMap = (InputMap)UIManager.get("Table.ancestorInputMap");
  602. InputMap rtlKeyMap;
  603. if (table.getComponentOrientation().isLeftToRight() ||
  604. ((rtlKeyMap = (InputMap)UIManager.get("Table.ancestorInputMap.RightToLeft")) == null)) {
  605. return keyMap;
  606. } else {
  607. rtlKeyMap.setParent(keyMap);
  608. return rtlKeyMap;
  609. }
  610. }
  611. return null;
  612. }
  613. ActionMap getActionMap() {
  614. ActionMap map = (ActionMap)UIManager.get("Table.actionMap");
  615. if (map == null) {
  616. map = createActionMap();
  617. if (map != null) {
  618. UIManager.getLookAndFeelDefaults().put("Table.actionMap", map);
  619. }
  620. }
  621. return map;
  622. }
  623. ActionMap createActionMap() {
  624. ActionMap map = new ActionMapUIResource();
  625. map.put("selectNextColumn", new NavigationalAction
  626. (1, 0, false, false, false));
  627. map.put("selectPreviousColumn", new NavigationalAction
  628. (-1, 0, false, false, false));
  629. map.put("selectNextRow", new NavigationalAction
  630. (0, 1, false, false, false));
  631. map.put("selectPreviousRow", new NavigationalAction
  632. (0, -1, false, false, false));
  633. map.put("selectNextColumnExtendSelection", new NavigationalAction
  634. (1, 0, false, true, false));
  635. map.put("selectPreviousColumnExtendSelection", new NavigationalAction
  636. (-1, 0, false, true, false));
  637. map.put("selectNextRowExtendSelection", new NavigationalAction
  638. (0, 1, false, true, false));
  639. map.put("selectPreviousRowExtendSelection", new NavigationalAction
  640. (0, -1, false, true, false));
  641. map.put("scrollUpChangeSelection",
  642. new PagingAction(false, false, true, false));
  643. map.put("scrollDownChangeSelection",
  644. new PagingAction(false, true, true, false));
  645. map.put("selectFirstColumn",
  646. new PagingAction(false, false, false, true));
  647. map.put("selectLastColumn",
  648. new PagingAction(false, true, false, true));
  649. map.put("scrollUpExtendSelection",
  650. new PagingAction(true, false, true, false));
  651. map.put("scrollDownExtendSelection",
  652. new PagingAction(true, true, true, false));
  653. map.put("selectFirstColumnExtendSelection",
  654. new PagingAction(true, false, false, true));
  655. map.put("selectLastColumnExtendSelection",
  656. new PagingAction(true, true, false, true));
  657. map.put("selectFirstRow",
  658. new PagingAction(false, false, true, true));
  659. map.put("selectLastRow",
  660. new PagingAction(false, true, true, true));
  661. map.put("selectFirstRowExtendSelection",
  662. new PagingAction(true, false, true, true));
  663. map.put("selectLastRowExtendSelection",
  664. new PagingAction(true, true, true, true));
  665. map.put("selectNextColumnCell",
  666. new NavigationalAction(1, 0, true, false, true));
  667. map.put("selectPreviousColumnCell",
  668. new NavigationalAction(-1, 0, true, false, true));
  669. map.put("selectNextRowCell",
  670. new NavigationalAction(0, 1, true, false, true));
  671. map.put("selectPreviousRowCell",
  672. new NavigationalAction(0, -1, true, false, true));
  673. map.put("selectAll", new SelectAllAction());
  674. map.put("cancel", new CancelEditingAction());
  675. map.put("startEditing", new StartEditingAction());
  676. map.put(TransferHandler.getCutAction().getValue(Action.NAME),
  677. TransferHandler.getCutAction());
  678. map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
  679. TransferHandler.getCopyAction());
  680. map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
  681. TransferHandler.getPasteAction());
  682. if (table.getComponentOrientation().isLeftToRight()) {
  683. map.put("scrollLeftChangeSelection",
  684. new PagingAction(false, false, false, false));
  685. map.put("scrollRightChangeSelection",
  686. new PagingAction(false, true, false, false));
  687. map.put("scrollLeftExtendSelection",
  688. new PagingAction(true, false, false, false));
  689. map.put("scrollRightExtendSelection",
  690. new PagingAction(true, true, false, false));
  691. } else {
  692. map.put("scrollLeftChangeSelection",
  693. new PagingAction(false, true, false, false));
  694. map.put("scrollRightChangeSelection",
  695. new PagingAction(false, false, false, false));
  696. map.put("scrollLeftExtendSelection",
  697. new PagingAction(true, true, false, false));
  698. map.put("scrollRightExtendSelection",
  699. new PagingAction(true, false, false, false));
  700. }
  701. return map;
  702. }
  703. // Uninstallation
  704. public void uninstallUI(JComponent c) {
  705. uninstallDefaults();
  706. uninstallListeners();
  707. uninstallKeyboardActions();
  708. table.remove(rendererPane);
  709. rendererPane = null;
  710. table = null;
  711. }
  712. protected void uninstallDefaults() {
  713. if (table.getTransferHandler() instanceof UIResource) {
  714. table.setTransferHandler(null);
  715. }
  716. }
  717. protected void uninstallListeners() {
  718. table.removeFocusListener(focusListener);
  719. table.removeKeyListener(keyListener);
  720. table.removeMouseListener(defaultDragRecognizer);
  721. table.removeMouseMotionListener(defaultDragRecognizer);
  722. table.removeMouseListener(mouseInputListener);
  723. table.removeMouseMotionListener(mouseInputListener);
  724. table.removePropertyChangeListener(propertyChangeListener);
  725. focusListener = null;
  726. keyListener = null;
  727. mouseInputListener = null;
  728. propertyChangeListener = null;
  729. }
  730. protected void uninstallKeyboardActions() {
  731. SwingUtilities.replaceUIInputMap(table, JComponent.
  732. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
  733. SwingUtilities.replaceUIActionMap(table, null);
  734. }
  735. //
  736. // Size Methods
  737. //
  738. private Dimension createTableSize(long width) {
  739. int height = 0;
  740. int rowCount = table.getRowCount();
  741. if (rowCount > 0 && table.getColumnCount() > 0) {
  742. Rectangle r = table.getCellRect(rowCount-1, 0, true);
  743. height = r.y + r.height;
  744. }
  745. // Width is always positive. The call to abs() is a workaround for
  746. // a bug in the 1.1.6 JIT on Windows.
  747. long tmp = Math.abs(width);
  748. if (tmp > Integer.MAX_VALUE) {
  749. tmp = Integer.MAX_VALUE;
  750. }
  751. return new Dimension((int)tmp, height);
  752. }
  753. /**
  754. * Return the minimum size of the table. The minimum height is the
  755. * row height times the number of rows.
  756. * The minimum width is the sum of the minimum widths of each column.
  757. */
  758. public Dimension getMinimumSize(JComponent c) {
  759. long width = 0;
  760. Enumeration enumeration = table.getColumnModel().getColumns();
  761. while (enumeration.hasMoreElements()) {
  762. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  763. width = width + aColumn.getMinWidth();
  764. }
  765. return createTableSize(width);
  766. }
  767. /**
  768. * Return the preferred size of the table. The preferred height is the
  769. * row height times the number of rows.
  770. * The preferred width is the sum of the preferred widths of each column.
  771. */
  772. public Dimension getPreferredSize(JComponent c) {
  773. long width = 0;
  774. Enumeration enumeration = table.getColumnModel().getColumns();
  775. while (enumeration.hasMoreElements()) {
  776. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  777. width = width + aColumn.getPreferredWidth();
  778. }
  779. return createTableSize(width);
  780. }
  781. /**
  782. * Return the maximum size of the table. The maximum height is the
  783. * row heighttimes the number of rows.
  784. * The maximum width is the sum of the maximum widths of each column.
  785. */
  786. public Dimension getMaximumSize(JComponent c) {
  787. long width = 0;
  788. Enumeration enumeration = table.getColumnModel().getColumns();
  789. while (enumeration.hasMoreElements()) {
  790. TableColumn aColumn = (TableColumn)enumeration.nextElement();
  791. width = width + aColumn.getMaxWidth();
  792. }
  793. return createTableSize(width);
  794. }
  795. //
  796. // Paint methods and support
  797. //
  798. /** Paint a representation of the <code>table</code> instance
  799. * that was set in installUI().
  800. */
  801. public void paint(Graphics g, JComponent c) {
  802. if (table.getRowCount() <= 0 || table.getColumnCount() <= 0) {
  803. return;
  804. }
  805. Rectangle clip = g.getClipBounds();
  806. Point upperLeft = clip.getLocation();
  807. Point lowerRight = new Point(clip.x + clip.width - 1, clip.y + clip.height - 1);
  808. int rMin = table.rowAtPoint(upperLeft);
  809. int rMax = table.rowAtPoint(lowerRight);
  810. // This should never happen.
  811. if (rMin == -1) {
  812. rMin = 0;
  813. }
  814. // If the table does not have enough rows to fill the view we'll get -1.
  815. // Replace this with the index of the last row.
  816. if (rMax == -1) {
  817. rMax = table.getRowCount()-1;
  818. }
  819. boolean ltr = table.getComponentOrientation().isLeftToRight();
  820. int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight);
  821. int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);
  822. // This should never happen.
  823. if (cMin == -1) {
  824. cMin = 0;
  825. }
  826. // If the table does not have enough columns to fill the view we'll get -1.
  827. // Replace this with the index of the last column.
  828. if (cMax == -1) {
  829. cMax = table.getColumnCount()-1;
  830. }
  831. // Paint the grid.
  832. paintGrid(g, rMin, rMax, cMin, cMax);
  833. // Paint the cells.
  834. paintCells(g, rMin, rMax, cMin, cMax);
  835. }
  836. /*
  837. * Paints the grid lines within <I>aRect</I>, using the grid
  838. * color set with <I>setGridColor</I>. Paints vertical lines
  839. * if <code>getShowVerticalLines()</code> returns true and paints
  840. * horizontal lines if <code>getShowHorizontalLines()</code>
  841. * returns true.
  842. */
  843. private void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) {
  844. g.setColor(table.getGridColor());
  845. Rectangle minCell = table.getCellRect(rMin, cMin, true);
  846. Rectangle maxCell = table.getCellRect(rMax, cMax, true);
  847. Rectangle damagedArea = minCell.union( maxCell );
  848. if (table.getShowHorizontalLines()) {
  849. int tableWidth = damagedArea.x + damagedArea.width;
  850. int y = damagedArea.y;
  851. for (int row = rMin; row <= rMax; row++) {
  852. y += table.getRowHeight(row);
  853. g.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1);
  854. }
  855. }
  856. if (table.getShowVerticalLines()) {
  857. TableColumnModel cm = table.getColumnModel();
  858. int tableHeight = damagedArea.y + damagedArea.height;
  859. int x;
  860. if (table.getComponentOrientation().isLeftToRight()) {
  861. x = damagedArea.x;
  862. for (int column = cMin; column <= cMax; column++) {
  863. int w = cm.getColumn(column).getWidth();
  864. x += w;
  865. g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
  866. }
  867. } else {
  868. x = damagedArea.x + damagedArea.width;
  869. for (int column = cMin; column < cMax; column++) {
  870. int w = cm.getColumn(column).getWidth();
  871. x -= w;
  872. g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
  873. }
  874. x -= cm.getColumn(cMax).getWidth();
  875. g.drawLine(x, 0, x, tableHeight - 1);
  876. }
  877. }
  878. }
  879. private int viewIndexForColumn(TableColumn aColumn) {
  880. TableColumnModel cm = table.getColumnModel();
  881. for (int column = 0; column < cm.getColumnCount(); column++) {
  882. if (cm.getColumn(column) == aColumn) {
  883. return column;
  884. }
  885. }
  886. return -1;
  887. }
  888. private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
  889. JTableHeader header = table.getTableHeader();
  890. TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
  891. TableColumnModel cm = table.getColumnModel();
  892. int columnMargin = cm.getColumnMargin();
  893. Rectangle cellRect;
  894. TableColumn aColumn;
  895. int columnWidth;
  896. if (table.getComponentOrientation().isLeftToRight()) {
  897. for(int row = rMin; row <= rMax; row++) {
  898. cellRect = table.getCellRect(row, cMin, false);
  899. for(int column = cMin; column <= cMax; column++) {
  900. aColumn = cm.getColumn(column);
  901. columnWidth = aColumn.getWidth();
  902. cellRect.width = columnWidth - columnMargin;
  903. if (aColumn != draggedColumn) {
  904. paintCell(g, cellRect, row, column);
  905. }
  906. cellRect.x += columnWidth;
  907. }
  908. }
  909. } else {
  910. for(int row = rMin; row <= rMax; row++) {
  911. cellRect = table.getCellRect(row, cMin, false);
  912. aColumn = cm.getColumn(cMin);
  913. if (aColumn != draggedColumn) {
  914. columnWidth = aColumn.getWidth();
  915. cellRect.width = columnWidth - columnMargin;
  916. paintCell(g, cellRect, row, cMin);
  917. }
  918. for(int column = cMin+1; column <= cMax; column++) {
  919. aColumn = cm.getColumn(column);
  920. columnWidth = aColumn.getWidth();
  921. cellRect.width = columnWidth - columnMargin;
  922. cellRect.x -= columnWidth;
  923. if (aColumn != draggedColumn) {
  924. paintCell(g, cellRect, row, column);
  925. }
  926. }
  927. }
  928. }
  929. // Paint the dragged column if we are dragging.
  930. if (draggedColumn != null) {
  931. paintDraggedArea(g, rMin, rMax, draggedColumn, header.getDraggedDistance());
  932. }
  933. // Remove any renderers that may be left in the rendererPane.
  934. rendererPane.removeAll();
  935. }
  936. private void paintDraggedArea(Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) {
  937. int draggedColumnIndex = viewIndexForColumn(draggedColumn);
  938. Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
  939. Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
  940. Rectangle vacatedColumnRect = minCell.union(maxCell);
  941. // Paint a gray well in place of the moving column.
  942. g.setColor(table.getParent().getBackground());
  943. g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
  944. vacatedColumnRect.width, vacatedColumnRect.height);
  945. // Move to the where the cell has been dragged.
  946. vacatedColumnRect.x += distance;
  947. // Fill the background.
  948. g.setColor(table.getBackground());
  949. g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
  950. vacatedColumnRect.width, vacatedColumnRect.height);
  951. // Paint the vertical grid lines if necessary.
  952. if (table.getShowVerticalLines()) {
  953. g.setColor(table.getGridColor());
  954. int x1 = vacatedColumnRect.x;
  955. int y1 = vacatedColumnRect.y;
  956. int x2 = x1 + vacatedColumnRect.width - 1;
  957. int y2 = y1 + vacatedColumnRect.height - 1;
  958. // Left
  959. g.drawLine(x1-1, y1, x1-1, y2);
  960. // Right
  961. g.drawLine(x2, y1, x2, y2);
  962. }
  963. for(int row = rMin; row <= rMax; row++) {
  964. // Render the cell value
  965. Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
  966. r.x += distance;
  967. paintCell(g, r, row, draggedColumnIndex);
  968. // Paint the (lower) horizontal grid line if necessary.
  969. if (table.getShowHorizontalLines()) {
  970. g.setColor(table.getGridColor());
  971. Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
  972. rcr.x += distance;
  973. int x1 = rcr.x;
  974. int y1 = rcr.y;
  975. int x2 = x1 + rcr.width - 1;
  976. int y2 = y1 + rcr.height - 1;
  977. g.drawLine(x1, y2, x2, y2);
  978. }
  979. }
  980. }
  981. private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
  982. if (table.isEditing() && table.getEditingRow()==row &&
  983. table.getEditingColumn()==column) {
  984. Component component = table.getEditorComponent();
  985. component.setBounds(cellRect);
  986. component.validate();
  987. }
  988. else {
  989. TableCellRenderer renderer = table.getCellRenderer(row, column);
  990. Component component = table.prepareRenderer(renderer, row, column);
  991. rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
  992. cellRect.width, cellRect.height, true);
  993. }
  994. }
  995. private static final TableDragGestureRecognizer defaultDragRecognizer = new TableDragGestureRecognizer();
  996. /**
  997. * Drag gesture recognizer for JTable components
  998. */
  999. static class TableDragGestureRecognizer extends BasicDragGestureRecognizer {
  1000. /**
  1001. * Determines if the following are true:
  1002. * <ul>
  1003. * <li>the press event is located over a selection
  1004. * <li>the dragEnabled property is true
  1005. * <li>A TranferHandler is installed
  1006. * </ul>
  1007. * <p>
  1008. * This is implemented to perform the superclass behavior
  1009. * followed by a check if the dragEnabled
  1010. * property is set and if the location picked is selected.
  1011. */
  1012. protected boolean isDragPossible(MouseEvent e) {
  1013. if (super.isDragPossible(e)) {
  1014. JTable table = (JTable) this.getComponent(e);
  1015. if (table.getDragEnabled()) {
  1016. Point p = e.getPoint();
  1017. int row = table.rowAtPoint(p);
  1018. int column = table.columnAtPoint(p);
  1019. if ((column != -1) && (row != -1) && table.isCellSelected(row, column)) {
  1020. return true;
  1021. }
  1022. }
  1023. }
  1024. return false;
  1025. }
  1026. }
  1027. private static DropTargetListener defaultDropTargetListener = null;
  1028. /**
  1029. * A DropTargetListener to extend the default Swing handling of drop operations
  1030. * by moving the tree selection to the nearest location to the mouse pointer.
  1031. * Also adds autoscroll capability.
  1032. */
  1033. static class TableDropTargetListener extends BasicDropTargetListener {
  1034. /**
  1035. * called to save the state of a component in case it needs to
  1036. * be restored because a drop is not performed.
  1037. */
  1038. protected void saveComponentState(JComponent comp) {
  1039. JTable table = (JTable) comp;
  1040. rows = table.getSelectedRows();
  1041. cols = table.getSelectedColumns();
  1042. }
  1043. /**
  1044. * called to restore the state of a component
  1045. * because a drop was not performed.
  1046. */
  1047. protected void restoreComponentState(JComponent comp) {
  1048. JTable table = (JTable) comp;
  1049. table.clearSelection();
  1050. for (int i = 0; i < rows.length; i++) {
  1051. table.addRowSelectionInterval(rows[i], rows[i]);
  1052. }
  1053. for (int i = 0; i < cols.length; i++) {
  1054. table.addColumnSelectionInterval(cols[i], cols[i]);
  1055. }
  1056. }
  1057. /**
  1058. * called to set the insertion location to match the current
  1059. * mouse pointer coordinates.
  1060. */
  1061. protected void updateInsertionLocation(JComponent comp, Point p) {
  1062. JTable table = (JTable) comp;
  1063. int row = table.rowAtPoint(p);
  1064. int col = table.columnAtPoint(p);
  1065. if (row != -1) {
  1066. table.setRowSelectionInterval(row, row);
  1067. }
  1068. if (col != -1) {
  1069. table.setColumnSelectionInterval(col, col);
  1070. }
  1071. }
  1072. private int[] rows;
  1073. private int[] cols;
  1074. }
  1075. private static final TransferHandler defaultTransferHandler = new TableTransferHandler();
  1076. static class TableTransferHandler extends TransferHandler implements UIResource {
  1077. /**
  1078. * Create a Transferable to use as the source for a data transfer.
  1079. *
  1080. * @param c The component holding the data to be transfered. This
  1081. * argument is provided to enable sharing of TransferHandlers by
  1082. * multiple components.
  1083. * @return The representation of the data to be transfered.
  1084. *
  1085. */
  1086. protected Transferable createTransferable(JComponent c) {
  1087. if (c instanceof JTable) {
  1088. JTable table = (JTable) c;
  1089. int[] rows;
  1090. int[] cols;
  1091. if (!table.getRowSelectionAllowed() && !table.getColumnSelectionAllowed()) {
  1092. return null;
  1093. }
  1094. if (!table.getRowSelectionAllowed()) {
  1095. int rowCount = table.getRowCount();
  1096. rows = new int[rowCount];
  1097. for (int counter = 0; counter < rowCount; counter++) {
  1098. rows[counter] = counter;
  1099. }
  1100. } else {
  1101. rows = table.getSelectedRows();
  1102. }
  1103. if (!table.getColumnSelectionAllowed()) {
  1104. int colCount = table.getColumnCount();
  1105. cols = new int[colCount];
  1106. for (int counter = 0; counter < colCount; counter++) {
  1107. cols[counter] = counter;
  1108. }
  1109. } else {
  1110. cols = table.getSelectedColumns();
  1111. }
  1112. if (rows == null || cols == null || rows.length == 0 || cols.length == 0) {
  1113. return null;
  1114. }
  1115. StringBuffer plainBuf = new StringBuffer();
  1116. StringBuffer htmlBuf = new StringBuffer();
  1117. htmlBuf.append("<html>\n<body>\n<table>\n");
  1118. for (int row = 0; row < rows.length; row++) {
  1119. htmlBuf.append("<tr>\n");
  1120. for (int col = 0; col < cols.length; col++) {
  1121. Object obj = table.getValueAt(rows[row], cols[col]);
  1122. String val = ((obj == null) ? "" : obj.toString());
  1123. plainBuf.append(val + "\t");
  1124. htmlBuf.append(" <td>" + val + "</td>\n");
  1125. }
  1126. // we want a newline at the end of each line and not a tab
  1127. plainBuf.deleteCharAt(plainBuf.length() - 1).append("\n");
  1128. htmlBuf.append("</tr>\n");
  1129. }
  1130. // remove the last newline
  1131. plainBuf.deleteCharAt(plainBuf.length() - 1);
  1132. htmlBuf.append("</table>\n</body>\n</html>");
  1133. return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
  1134. }
  1135. return null;
  1136. }
  1137. public int getSourceActions(JComponent c) {
  1138. return COPY;
  1139. }
  1140. }
  1141. /**
  1142. * PropertyChangeListener for the table. Updates the appropriate
  1143. * varaible, or TreeState, based on what changes.
  1144. */
  1145. private class PropertyChangeHandler implements
  1146. PropertyChangeListener {
  1147. public void propertyChange(PropertyChangeEvent event) {
  1148. String changeName = event.getPropertyName();
  1149. if (changeName.equals("componentOrientation")) {
  1150. InputMap inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  1151. SwingUtilities.replaceUIInputMap(table,
  1152. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
  1153. inputMap);
  1154. UIManager.getLookAndFeelDefaults().put("Table.actionMap",
  1155. null);
  1156. ActionMap actionMap = getActionMap();
  1157. SwingUtilities.replaceUIActionMap(table, actionMap);
  1158. JTableHeader header = table.getTableHeader();
  1159. if (header != null) {
  1160. header.setComponentOrientation((ComponentOrientation)event.getNewValue());
  1161. }
  1162. } else if ("transferHandler".equals(changeName)) {
  1163. DropTarget dropTarget = table.getDropTarget();
  1164. if (dropTarget instanceof UIResource) {
  1165. if (defaultDropTargetListener == null) {
  1166. defaultDropTargetListener = new TableDropTargetListener();
  1167. }
  1168. try {
  1169. dropTarget.addDropTargetListener(defaultDropTargetListener);
  1170. } catch (TooManyListenersException tmle) {
  1171. // should not happen... swing drop target is multicast
  1172. }
  1173. }
  1174. }
  1175. }
  1176. } // End of BasicTableUI.PropertyChangeHandler
  1177. } // End of Class BasicTableUI