package org.obo.app.swing; import java.awt.Component; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.util.EventObject; import java.util.HashSet; import java.util.Set; import java.util.Vector; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.InputVerifier; import javax.swing.JComponent; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.table.TableCellEditor; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import org.apache.log4j.Logger; /** * This table provides some workarounds to behaviors in Sun's JTable. * @author Jim Balhoff */ public class BugWorkaroundTable extends JTable { private final Set<Component> verifierAdded = new HashSet<Component>(); /** * This input verifier cancels editing on the table cell editor when the user * clicks elsewhere in the window. This addresses a bug in which the edited value * was being applied to a newly selected item in another (master) table, instead * of the edited one. */ private final InputVerifier verifier = new InputVerifier() { @Override public boolean verify(JComponent input) { return true; } @Override public boolean shouldYieldFocus(JComponent input) { final TableCellEditor editor = BugWorkaroundTable.this.getCellEditor(); if (editor != null) editor.cancelCellEditing(); return true; } }; public BugWorkaroundTable() { super(); this.init(); } public BugWorkaroundTable(TableModel dm) { super(dm); this.init(); } public BugWorkaroundTable(TableModel dm, TableColumnModel cm) { super(dm, cm); this.init(); } public BugWorkaroundTable(int numRows, int numColumns) { super(numRows, numColumns); this.init(); } public BugWorkaroundTable(Vector<?> rowData, Vector<?> columnNames) { super(rowData, columnNames); this.init(); } public BugWorkaroundTable(Object[][] rowData, Object[] columnNames) { super(rowData, columnNames); this.init(); } public BugWorkaroundTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) { super(dm, cm, sm); this.init(); } private void init() { // make sure any cell editors take focus, so that: // 1. user can start typing immediately (especially for JComboBox) // 2. editor has focus and its verifier will be called when user clicks elsewhere this.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { if (BugWorkaroundTable.this.isEditing()) { BugWorkaroundTable.this.getEditorComponent().requestFocusInWindow(); } } @Override public void focusLost(FocusEvent e) {} }); } /** * JTable incorrectly begins editing of table cells when various modifier keys are pressed. This * results in bizarre behavior when trying to select or copy rows. * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4820794 * @see javax.swing.JTable#processKeyBinding(javax.swing.KeyStroke, java.awt.event.KeyEvent, int, boolean) */ @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { if (e.getKeyCode() == KeyEvent.VK_CAPS_LOCK) { return false; } // from http://lists.apple.com/archives/Java-dev/2004/Dec/msg00283.html boolean retValue = false; if (e.getKeyCode()!=KeyEvent.VK_META || e.getKeyCode()!=KeyEvent.VK_CONTROL || e.getKeyCode()!=KeyEvent.VK_ALT) { if (e.isControlDown() || e.isMetaDown() || e.isAltDown()) { InputMap map = this.getInputMap(condition); ActionMap am = getActionMap(); if (map != null && am != null && isEnabled()) { Object binding = map.get(ks); Action action = (binding == null) ? null : am.get(binding); if (action != null) { SwingUtilities.notifyAction(action, ks, e, this, e.getModifiers()); retValue = false; } else { try { JComponent ancestor = (JComponent) SwingUtilities.getAncestorOfClass(Class.forName("javax.swing.JComponent"), this); ancestor.dispatchEvent(e); } catch (ClassNotFoundException fr) { log().error(fr.toString()); } } } else { retValue = super.processKeyBinding(ks, e, condition, pressed); } } else { retValue = super.processKeyBinding(ks, e, condition, pressed); } } return retValue; } /** * Most convenient spot to make sure any table cell editor has the input verifier. */ @Override public Component getEditorComponent() { final Component component = super.getEditorComponent(); if ((component != null) && (!this.verifierAdded.contains(component))) { this.verifierAdded.add(component); ((JComponent) component).setInputVerifier(this.verifier); } return component; } /** * Take this opportunity to request focus, so that input verifiers elsewhere will * be triggered. */ @Override public boolean editCellAt(int row, int column, EventObject e) { this.requestFocusInWindow(); return super.editCellAt(row, column, e); } private Logger log() { return Logger.getLogger(this.getClass()); } }