/plugins/CommonControls/tags/v_0_2/common/gui/HelpfulJTable.java

# · Java · 561 lines · 315 code · 106 blank · 140 comment · 80 complexity · 32d4bd57a793f72c562708e5e9e0907b MD5 · raw file

  1. /*
  2. * HelpfulJTable.java - a JTable with additional features.
  3. * Copyright (c) 2000,2001 Dirk Moebius
  4. *
  5. * :tabSize=4:indentSize=4:noTabs=false:maxLineLen=0:
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. */
  21. package common.gui;
  22. import java.awt.*;
  23. import java.awt.event.*;
  24. import java.beans.*;
  25. import java.net.URL;
  26. import javax.swing.*;
  27. import javax.swing.border.EtchedBorder;
  28. import javax.swing.event.TableModelEvent;
  29. import javax.swing.table.*;
  30. import org.gjt.sp.util.Log;
  31. /**
  32. * An extension of the default Swing JTable, that passes action key events,
  33. * displays tooltips and autoresizes columns.<p>
  34. *
  35. * In detail, the following features are provided:<p>
  36. *
  37. * <ul>
  38. * <li>
  39. * Fires an <code>ActionEvent</code>, if <b>Enter</b>, <b>Tab</b> or
  40. * <b>Shift-Tab</b> is pressed.<br>
  41. * Therefore, <code>addActionListener(ActionListener)</code> and
  42. * <code>removeActionListener(ActionListener)</code> methods are provided.
  43. * </li>
  44. *
  45. * <li>
  46. * Displays tooltips for partially visible text entries.<br>
  47. * To use this feature, you must use a <code>TableCellRenderer</code> that
  48. * implements the methods <code>getToolTipText()</code> and/or
  49. * <code>getToolTipText(MouseEvent)</code>, otherwise you won't see any
  50. * tooltips.
  51. * </li>
  52. *
  53. * <li>
  54. * Autoresizes all columns to the length of its longest content string.<br>
  55. * As a drawback, this <code>HelpfulJTable</code> can only be used to
  56. * with a String cell renderer, nothing complicated as a JList. (Complex
  57. * components may be used a CellEditor, however).
  58. * </li>
  59. * </ul><p>
  60. *
  61. * Only the default constructor of <code>JTable</code> is provided.
  62. * Please use <code>setModel(TableModel)</code> to set another model.
  63. *
  64. * @author Dirk Moebius
  65. */
  66. public class HelpfulJTable extends JTable
  67. {
  68. public final static int SORT_OFF = -1;
  69. public final static int SORT_ASCENDING = 1;
  70. public final static int SORT_DESCENDING = 2;
  71. public HelpfulJTable() {
  72. super();
  73. super.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
  74. KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
  75. KeyStroke tab = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
  76. KeyStroke shifttab = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK);
  77. this.unregisterKeyboardAction(enter);
  78. this.unregisterKeyboardAction(tab);
  79. KeyHandler kh = new KeyHandler();
  80. this.registerKeyboardAction(kh, "enter-pressed", enter, JComponent.WHEN_FOCUSED);
  81. this.registerKeyboardAction(kh, "tab-pressed", tab, JComponent.WHEN_FOCUSED);
  82. this.registerKeyboardAction(kh, "shift-tab-pressed", shifttab, JComponent.WHEN_FOCUSED);
  83. this.addMouseListener(new TooltipMouseHandler());
  84. if (this.getTableHeader() != null) {
  85. this.getTableHeader().setResizingAllowed(false);
  86. }
  87. }
  88. /**
  89. * If true, columns are autoresized according to the largest display
  90. * width of their contents.
  91. *
  92. * @param state whether autoresize is enabled or disabled.
  93. */
  94. public void setAutoResizeColumns(boolean state) {
  95. autoResizeColumns = state;
  96. if (tableHeader != null && autoResizeColumns)
  97. tableHeader.setResizingAllowed(false);
  98. }
  99. /**
  100. * Return the value of the autoResizeColumns property.
  101. * The default is <tt>true</tt>.
  102. */
  103. public boolean getAutoResizeColumns() {
  104. return autoResizeColumns;
  105. }
  106. /**
  107. * Set the sort column.
  108. * This is a bound bean property.
  109. *
  110. * @param sortColumn the new sortColumn value.
  111. */
  112. public void setSortColumn(int sortColumn) {
  113. int oldSortColumn = this.sortColumn;
  114. this.sortColumn = sortColumn;
  115. if (oldSortColumn != this.sortColumn)
  116. firePropertyChange("sortColumn", new Integer(oldSortColumn), new Integer(this.sortColumn));
  117. }
  118. public int getSortColumn() {
  119. return sortColumn;
  120. }
  121. /**
  122. * Set whether the current <code>sortColumn</code> should be sorted
  123. * ascending or descending, or not at all.<p>
  124. *
  125. * This is a bound bean property named "sortOrder". If it is changed, a
  126. * <code>PropertyChangeEvent</code> gets fired.
  127. *
  128. * @param order the new sort order, one of SORT_ASCENDING,
  129. * SORT_DESCENDING, SORT_OFF.
  130. */
  131. public void setSortOrder(int order) {
  132. if (order != SORT_ASCENDING && order != SORT_DESCENDING && order != SORT_OFF)
  133. throw new IllegalArgumentException("sortOrder must be one of: SORT_ASCENDING, SORT_DESCENDING, SORT_OFF");
  134. int oldSortOrder = this.sortOrder;
  135. this.sortOrder = order;
  136. if (oldSortOrder != this.sortOrder)
  137. firePropertyChange("sortOrder", new Integer(oldSortOrder), new Integer(this.sortOrder));
  138. }
  139. public int getSortOrder() {
  140. return sortOrder;
  141. }
  142. /**
  143. * Overridden, so that any attempts to set a mode other than
  144. * AUTO_RESIZE_OFF are ignored, if autoResizeColumns is on.
  145. */
  146. public void setAutoResizeMode(int mode) {
  147. if (autoResizeColumns)
  148. return;
  149. super.setAutoResizeMode(mode);
  150. }
  151. /**
  152. * Overridden, so that any attempts to set a TableHeader with
  153. * <tt>resizingAllowed = true</tt> is set back to <tt>false</tt>.
  154. */
  155. public void setTableHeader(JTableHeader th) {
  156. super.setTableHeader(th);
  157. if (th != null) {
  158. // remove resizing allowed, if neccessary:
  159. if (autoResizeColumns)
  160. th.setResizingAllowed(false);
  161. // set mouse listener
  162. th.addMouseListener(new TableHeaderMouseHandler());
  163. }
  164. super.configureEnclosingScrollPane();
  165. }
  166. /**
  167. * Set a new column model.
  168. * This implementation also sets new header renderers that display the
  169. * current sort column with a small icon.
  170. */
  171. public void setColumnModel(TableColumnModel tcm) {
  172. super.setColumnModel(tcm);
  173. // set header renderer for all columns:
  174. for (int i = 0, cc = columnModel.getColumnCount(); i < cc; i++)
  175. columnModel.getColumn(i).setHeaderRenderer(new SortTableHeaderRenderer(i));
  176. }
  177. /** Add an action listener to this table instance. */
  178. public void addActionListener(ActionListener l) {
  179. listenerList.add(ActionListener.class, l);
  180. }
  181. /** Remove an action listener from this table instance. */
  182. public void removeActionListener(ActionListener l) {
  183. listenerList.remove(ActionListener.class, l);
  184. }
  185. /**
  186. * Overridden to return null, if the cell is fully visible, so that
  187. * ToolTips are only displayed if the cell is partially hidden.
  188. */
  189. public final String getToolTipText(MouseEvent evt) {
  190. if (getToolTipLocation(evt) == null)
  191. return null;
  192. else
  193. return super.getToolTipText(evt);
  194. }
  195. /**
  196. * Overridden to return null, if the cell is fully visible, so that
  197. * ToolTips are only displayed if the cell is partially hidden.
  198. */
  199. public final Point getToolTipLocation(MouseEvent evt) {
  200. int col = columnAtPoint(evt.getPoint());
  201. int row = rowAtPoint(evt.getPoint());
  202. if (col < 0 || row < 0) return null;
  203. Rectangle rect = getCellRect(row, col, true);
  204. if (cellTextIsFullyVisible(rect, row, col)) {
  205. return null;
  206. } else {
  207. Component comp = getCellRendererComponent(row, col);
  208. int iconwidth = 0;
  209. if (comp instanceof JLabel) {
  210. JLabel label = (JLabel) comp;
  211. if (label.getIcon() != null) {
  212. iconwidth = label.getIcon().getIconWidth() + label.getIconTextGap();
  213. }
  214. }
  215. return new Point(rect.x + iconwidth, rect.y);
  216. }
  217. }
  218. /** Autosizes the specified column to the width of its longest cell. */
  219. public void autosizeColumn(int col) {
  220. int width = getLongestCellTextWidth(col);
  221. if (width >= 0) {
  222. TableColumn tc = columnModel.getColumn(col);
  223. tc.setPreferredWidth(width);
  224. resizeAndRepaint();
  225. if (tableHeader != null)
  226. tableHeader.resizeAndRepaint();
  227. }
  228. }
  229. /**
  230. * Invoked when the table data has changed, this method autoresizes
  231. * all columns to its longest content length, if autoResizeColumns is on.
  232. */
  233. public void tableChanged(TableModelEvent e) {
  234. super.tableChanged(e);
  235. if (!autoResizeColumns)
  236. return;
  237. int cc = columnModel.getColumnCount() - 1;
  238. for (int col = cc; col >= 0; col--) {
  239. int width = getLongestCellTextWidth(col);
  240. if (width > 0) {
  241. TableColumn tc = columnModel.getColumn(col);
  242. tc.setPreferredWidth(width);
  243. //tc.setMinWidth(width);
  244. //tc.setMaxWidth(width);
  245. }
  246. }
  247. resizeAndRepaint();
  248. if (tableHeader != null)
  249. tableHeader.resizeAndRepaint();
  250. }
  251. /** Overwritten to autoscroll only vertically, not horizontally. */
  252. public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
  253. if (getAutoscrolls()) {
  254. Rectangle cellRect = getCellRect(rowIndex, columnIndex, false);
  255. if (cellRect != null) {
  256. cellRect.width = 0;
  257. scrollRectToVisible(cellRect);
  258. }
  259. }
  260. // Update column selection model:
  261. // (private void changeSelectionModel(csm, columnIndex, toggle, extend);)
  262. ListSelectionModel csm = getColumnModel().getSelectionModel();
  263. if (extend)
  264. if (toggle)
  265. csm.setAnchorSelectionIndex(columnIndex);
  266. else
  267. csm.setLeadSelectionIndex(columnIndex);
  268. else
  269. if (toggle)
  270. if (csm.isSelectedIndex(columnIndex))
  271. csm.removeSelectionInterval(columnIndex, columnIndex);
  272. else
  273. csm.addSelectionInterval(columnIndex, columnIndex);
  274. else
  275. csm.setSelectionInterval(columnIndex, columnIndex);
  276. // Update row selection model
  277. // (private void changeSelectionModel(rsm, rowIndex, toggle, extend);)
  278. ListSelectionModel rsm = getSelectionModel();
  279. if (extend)
  280. if (toggle)
  281. rsm.setAnchorSelectionIndex(rowIndex);
  282. else
  283. rsm.setLeadSelectionIndex(rowIndex);
  284. else
  285. if (toggle)
  286. if (rsm.isSelectedIndex(rowIndex))
  287. rsm.removeSelectionInterval(rowIndex, rowIndex);
  288. else
  289. rsm.addSelectionInterval(rowIndex, rowIndex);
  290. else
  291. rsm.setSelectionInterval(rowIndex, rowIndex);
  292. }
  293. /**
  294. * This method is invoked if the user pressed <b>Enter</b>, <b>Tab</b>
  295. * or <b>Shift-Tab</b> on the table.
  296. */
  297. protected void fireActionEvent(ActionEvent evt) {
  298. Object[] listeners = listenerList.getListenerList();
  299. // Process the listeners last to first, notifying those that are interested in this event:
  300. for (int i = listeners.length-2; i >= 0; i -= 2)
  301. if (listeners[i]==ActionListener.class)
  302. ((ActionListener)listeners[i+1]).actionPerformed(evt);
  303. }
  304. /**
  305. * Return the cell renderer component for the cell at (row,col).
  306. */
  307. protected Component getCellRendererComponent(int row, int col) {
  308. String value = getValueAt(row, col).toString();
  309. TableCellRenderer rend = getCellRenderer(row, col);
  310. return rend == null ? null
  311. : rend.getTableCellRendererComponent(this, value, isCellSelected(row,col), hasFocus(), row, col);
  312. }
  313. /**
  314. * <p>Returns true, if the text of cell (row,col) is fully visible,
  315. * i.e. it is not hidden partially by a ScrollPane and it does not
  316. * display "...", because the column is too small.</p>
  317. */
  318. private final boolean cellTextIsFullyVisible(Rectangle rect, int row, int col) {
  319. int textWidth = getCellTextWidth(row, col);
  320. if (textWidth >= rect.width)
  321. return false;
  322. Rectangle vr = getVisibleRect();
  323. return vr.contains(rect.x, rect.y) && vr.contains(rect.x + textWidth, rect.y);
  324. }
  325. /**
  326. * Computes the length of the text of cell (row,col), in pixels.
  327. */
  328. private int getCellTextWidth(int row, int col) {
  329. String value = getValueAt(row, col).toString();
  330. Component comp = getCellRendererComponent(row, col);
  331. FontMetrics fm = comp.getFontMetrics(comp.getFont());
  332. int insetwidth = 0;
  333. if (comp instanceof JComponent) {
  334. Insets insets = ((JComponent)comp).getInsets();
  335. insetwidth += insets.left + insets.right;
  336. }
  337. if (comp instanceof JLabel) {
  338. JLabel label = (JLabel) comp;
  339. if (label.getIcon() != null)
  340. insetwidth += label.getIcon().getIconWidth() + label.getIconTextGap();
  341. }
  342. return SwingUtilities.computeStringWidth(fm, value) + insetwidth;
  343. }
  344. /**
  345. * Gets the longest text width of a column, in pixels.
  346. */
  347. private int getLongestCellTextWidth(int col) {
  348. int max = -1;
  349. int numRows = dataModel.getRowCount() - 1;
  350. for (int row = numRows; row >= 0; row--) {
  351. int width = getCellTextWidth(row, col) + getIntercellSpacing().width + 2;
  352. if (width > max)
  353. max = width;
  354. }
  355. return max;
  356. }
  357. private boolean autoResizeColumns = true;
  358. private int sortColumn = -1;
  359. private int sortOrder = SORT_OFF;
  360. private static Icon SORT_UP;
  361. private static Icon SORT_DOWN;
  362. static {
  363. URL urlSortUp = HelpfulJTable.class.getResource("sort_up.gif");
  364. if (urlSortUp != null)
  365. SORT_UP = new ImageIcon(urlSortUp);
  366. else
  367. Log.log(Log.ERROR, HelpfulJTable.class, "Error fetching image sort_up.gif");
  368. URL urlSortDown = HelpfulJTable.class.getResource("sort_down.gif");
  369. if (urlSortDown != null)
  370. SORT_DOWN = new ImageIcon(urlSortDown);
  371. else
  372. Log.log(Log.ERROR, HelpfulJTable.class, "Error fetching image sort_down.gif");
  373. }
  374. private class KeyHandler implements ActionListener
  375. {
  376. public void actionPerformed(ActionEvent evt) {
  377. fireActionEvent(evt);
  378. }
  379. }
  380. class TooltipMouseHandler extends MouseAdapter
  381. {
  382. public void mouseEntered(MouseEvent evt) {
  383. ToolTipManager ttm = ToolTipManager.sharedInstance();
  384. toolTipInitialDelay = ttm.getInitialDelay();
  385. toolTipReshowDelay = ttm.getReshowDelay();
  386. ttm.setInitialDelay(500);
  387. ttm.setReshowDelay(0);
  388. }
  389. public void mouseExited(MouseEvent evt) {
  390. ToolTipManager ttm = ToolTipManager.sharedInstance();
  391. ttm.setInitialDelay(toolTipInitialDelay);
  392. ttm.setReshowDelay(toolTipReshowDelay);
  393. }
  394. private int toolTipInitialDelay = -1;
  395. private int toolTipReshowDelay = -1;
  396. }
  397. class TableHeaderMouseHandler extends MouseAdapter
  398. {
  399. public void mouseClicked(MouseEvent evt) {
  400. int col = getColumnModel().getColumnIndexAtX(evt.getX());
  401. if (col < 0)
  402. return;
  403. if (evt.getClickCount() == 1) {
  404. // single-click on header: change sort column/order
  405. evt.consume();
  406. if (col == sortColumn) {
  407. switch (sortOrder) {
  408. case SORT_ASCENDING: setSortOrder(SORT_DESCENDING); break;
  409. case SORT_DESCENDING: setSortOrder(SORT_OFF); break;
  410. default: setSortOrder(SORT_ASCENDING); break;
  411. }
  412. } else {
  413. setSortColumn(col);
  414. setSortOrder(SORT_ASCENDING);
  415. }
  416. }
  417. else if (evt.getClickCount() == 2 && getTableHeader().getResizingAllowed()) {
  418. // double click on header: autoresize column
  419. evt.consume();
  420. Point p = evt.getPoint();
  421. Rectangle r = getTableHeader().getHeaderRect(col);
  422. r.grow(-3, 0);
  423. if (r.contains(p))
  424. return; // not on the edge
  425. int midPoint = r.x + r.width/2;
  426. int resizeCol = (p.x < midPoint) ? col - 1 : col;
  427. if (resizeCol >= 0)
  428. autosizeColumn(resizeCol);
  429. }
  430. }
  431. }
  432. class SortTableHeaderRenderer extends DefaultTableCellRenderer
  433. {
  434. public SortTableHeaderRenderer(int viewColumn) {
  435. super();
  436. this.viewColumn = viewColumn;
  437. setHorizontalAlignment(SwingConstants.LEADING);
  438. setHorizontalTextPosition(SwingConstants.LEADING);
  439. setBorder(UIManager.getBorder("TableHeader.cellBorder"));
  440. }
  441. public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
  442. setText(value == null ? "" : value.toString());
  443. if (viewColumn == sortColumn)
  444. switch (sortOrder) {
  445. case SORT_ASCENDING: setIcon(SORT_UP); break;
  446. case SORT_DESCENDING: setIcon(SORT_DOWN); break;
  447. default: setIcon(null); break;
  448. }
  449. else
  450. setIcon(null);
  451. return this;
  452. }
  453. private int viewColumn;
  454. }
  455. }