Index: javax/swing/JTable.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/JTable.java,v retrieving revision 1.42 diff -u -r1.42 JTable.java --- javax/swing/JTable.java 11 Aug 2005 20:19:19 -0000 1.42 +++ javax/swing/JTable.java 12 Aug 2005 20:43:30 -0000 @@ -2222,6 +2222,9 @@ public void setValueAt(Object value, int row, int column) { + if (!isCellEditable(row, column)) + return; + if (value instanceof Component) add((Component)value); dataModel.setValueAt(value, row, convertColumnIndexToModel(column)); @@ -2327,14 +2330,6 @@ oldCellValue = getValueAt(row, column); setCellEditor(getCellEditor(row, column)); editorComp = prepareEditor(cellEditor, row, column); - editorComp.addKeyListener(new KeyAdapter() { - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { - if (JTable.this.cellEditor != null) - JTable.this.cellEditor.cancelCellEditing(); - } - } - }); cellEditor.addCellEditorListener(this); rowBeingEdited = row; columnBeingEdited = column; Index: javax/swing/plaf/basic/BasicLookAndFeel.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicLookAndFeel.java,v retrieving revision 1.43 diff -u -r1.43 BasicLookAndFeel.java --- javax/swing/plaf/basic/BasicLookAndFeel.java 4 Aug 2005 14:29:46 -0000 1.43 +++ javax/swing/plaf/basic/BasicLookAndFeel.java 12 Aug 2005 20:43:30 -0000 @@ -886,13 +886,24 @@ "shift KP_DOWN", "selectNextRowExtendSelection", "shift KP_LEFT", "selectPreviousColumnExtendSelection", "ESCAPE", "cancel", - "ctrl shift PAGE_UP", "scrollRightExtendSelection", + "ctrl shift PAGE_UP", "scrollLeftExtendSelection", "shift KP_RIGHT", " selectNextColumnExtendSelection", "ctrl PAGE_UP", "scrollLeftChangeSelection", "shift PAGE_UP", "scrollUpExtendSelection", - "ctrl shift PAGE_DOWN", "scrollLeftExtendSelection", + "ctrl shift PAGE_DOWN", "scrollRightExtendSelection", "ctrl PAGE_DOWN", "scrollRightChangeSelection", - "PAGE_UP", "scrollUpChangeSelection" + "PAGE_UP", "scrollUpChangeSelection", + "ctrl shift LEFT", "selectPreviousColumnExtendSelection", + "shift KP_UP", "selectPreviousRowExtendSelection", + "ctrl shift UP", "selectPreviousRowExtendSelection", + "ctrl shift RIGHT", "selectNextColumnExtendSelection", + "ctrl shift KP_RIGHT", "selectNextColumnExtendSelection", + "ctrl shift DOWN", "selectNextRowExtendSelection", + "ctrl BACK_SLASH", "clearSelection", + "ctrl shift KP_UP", "selectPreviousRowExtendSelection", + "ctrl shift KP_LEFT", "selectPreviousColumnExtendSelection", + "ctrl SLASH", "selectAll", + "ctrl shift KP_DOWN", "selectNextRowExtendSelection", }), "Table.background", new ColorUIResource(light), "Table.focusCellBackground", new ColorUIResource(light), Index: javax/swing/plaf/basic/BasicTableUI.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicTableUI.java,v retrieving revision 1.20 diff -u -r1.20 BasicTableUI.java --- javax/swing/plaf/basic/BasicTableUI.java 9 Aug 2005 15:56:40 -0000 1.20 +++ javax/swing/plaf/basic/BasicTableUI.java 12 Aug 2005 20:43:30 -0000 @@ -45,6 +45,7 @@ import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; +import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.InputEvent; @@ -52,11 +53,14 @@ import java.awt.event.KeyListener; import java.awt.event.MouseEvent; +import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.CellRendererPane; +import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JTable; import javax.swing.JTextField; +import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.UIDefaults; import javax.swing.UIManager; @@ -89,6 +93,9 @@ /** The cell border for selected/highlighted cells. */ Border highlightCellBorder; + /** The action bound to KeyStrokes. */ + TableAction action; + class FocusHandler implements FocusListener { public void focusGained(FocusEvent e) @@ -99,402 +106,347 @@ } } - class KeyHandler implements KeyListener + class MouseInputHandler implements MouseInputListener { + Point begin, curr; - /** - * A helper method for the keyPressed event. Used because the actions - * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar. - * - * Selects the next (previous if SHIFT pressed) column for TAB, or row for - * ENTER from within the currently selected cells. - * - * @param firstModel the ListSelectionModel for columns (TAB) or - * rows (ENTER) - * @param firstMin the first selected index in firstModel - * @param firstMax the last selected index in firstModel - * @param secondModel the ListSelectionModel for rows (TAB) or - * columns (ENTER) - * @param secondMin the first selected index in secondModel - * @param secondMax the last selected index in secondModel - * @param reverse true if shift was held for the event - * @param eventIsTab true if TAB was pressed, false if ENTER pressed - */ - void advanceMultipleSelection (ListSelectionModel firstModel, int firstMin, - int firstMax, ListSelectionModel secondModel, - int secondMin, int secondMax, boolean reverse, - boolean eventIsTab) + private void updateSelection(boolean controlPressed) { - // If eventIsTab, all the "firsts" correspond to columns, otherwise, to rows - // "seconds" correspond to the opposite - int firstLead = firstModel.getLeadSelectionIndex(); - int secondLead = secondModel.getLeadSelectionIndex(); - int numFirsts = eventIsTab ? - table.getModel().getColumnCount() : table.getModel().getRowCount(); - int numSeconds = eventIsTab ? - table.getModel().getRowCount() : table.getModel().getColumnCount(); - - // check if we have to wrap the "firsts" around, going to the other side - if ((firstLead == firstMax && !reverse) || - (reverse && firstLead == firstMin)) + // Update the rows + int lo_row = table.rowAtPoint(begin); + int hi_row = table.rowAtPoint(curr); + ListSelectionModel rowModel = table.getSelectionModel(); + if (lo_row != -1 && hi_row != -1) { - firstModel.addSelectionInterval(reverse ? firstMax : firstMin, - reverse ? firstMax : firstMin); - - // check if we have to wrap the "seconds" - if ((secondLead == secondMax && !reverse) || - (reverse && secondLead == secondMin)) - secondModel.addSelectionInterval(reverse ? secondMax : secondMin, - reverse ? secondMax : secondMin); - - // if we're not wrapping the seconds, we have to find out where we - // are within the secondModel and advance to the next cell (or - // go back to the previous cell if reverse == true) + if (controlPressed && rowModel.getSelectionMode() + != ListSelectionModel.SINGLE_SELECTION) + rowModel.addSelectionInterval(lo_row, hi_row); else - { - int[] secondsSelected; - if (eventIsTab && table.getRowSelectionAllowed() || - !eventIsTab && table.getColumnSelectionAllowed()) - secondsSelected = eventIsTab ? - table.getSelectedRows() : table.getSelectedColumns(); - else - { - // if row selection is not allowed, then the entire column gets - // selected when you click on it, so consider ALL rows selected - secondsSelected = new int[numSeconds]; - for (int i = 0; i < numSeconds; i++) - secondsSelected[i] = i; - } - - // and now find the "next" index within the model - int secondIndex = reverse ? secondsSelected.length - 1 : 0; - if (!reverse) - while (secondsSelected[secondIndex] <= secondLead) - secondIndex++; - else - while (secondsSelected[secondIndex] >= secondLead) - secondIndex--; - - // and select it - updating the lead selection index - secondModel.addSelectionInterval(secondsSelected[secondIndex], - secondsSelected[secondIndex]); - } + rowModel.setSelectionInterval(lo_row, hi_row); } - // We didn't have to wrap the firsts, so just find the "next" first - // and select it, we don't have to change "seconds" - else + + // Update the columns + int lo_col = table.columnAtPoint(begin); + int hi_col = table.columnAtPoint(curr); + ListSelectionModel colModel = table.getColumnModel(). + getSelectionModel(); + if (lo_col != -1 && hi_col != -1) { - int[] firstsSelected; - if (eventIsTab && table.getColumnSelectionAllowed() || - !eventIsTab && table.getRowSelectionAllowed()) - firstsSelected = eventIsTab ? - table.getSelectedColumns() : table.getSelectedRows(); + if (controlPressed && colModel.getSelectionMode() != + ListSelectionModel.SINGLE_SELECTION) + colModel.addSelectionInterval(lo_col, hi_col); else - { - // if selection not allowed, consider ALL firsts to be selected - firstsSelected = new int[numFirsts]; - for (int i = 0; i < numFirsts; i++) - firstsSelected[i] = i; - } - int firstIndex = reverse ? firstsSelected.length - 1 : 0; - if (!reverse) - while (firstsSelected[firstIndex] <= firstLead) - firstIndex++; - else - while (firstsSelected[firstIndex] >= firstLead) - firstIndex--; - firstModel.addSelectionInterval(firstsSelected[firstIndex], - firstsSelected[firstIndex]); - secondModel.addSelectionInterval(secondLead, secondLead); + colModel.setSelectionInterval(lo_col, hi_col); } } - - /** - * A helper method for the keyPressed event. Used because the actions - * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar. - * - * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER) - * in the table, changing the current selection. All cells in the table - * are eligible, not just the ones that are currently selected. - * @param firstModel the ListSelectionModel for columns (TAB) or rows - * (ENTER) - * @param firstMax the last index in firstModel - * @param secondModel the ListSelectionModel for rows (TAB) or columns - * (ENTER) - * @param secondMax the last index in secondModel - * @param reverse true if SHIFT was pressed for the event - */ - void advanceSingleSelection (ListSelectionModel firstModel, int firstMax, - ListSelectionModel secondModel, int secondMax, - boolean reverse) + public void mouseClicked(MouseEvent e) { - // for TABs, "first" corresponds to columns and "seconds" to rows. - // the opposite is true for ENTERs - int firstLead = firstModel.getLeadSelectionIndex(); - int secondLead = secondModel.getLeadSelectionIndex(); - - // if we are going backwards subtract 2 because we later add 1 - // for a net change of -1 - if (reverse && (firstLead == 0)) - { - // check if we have to wrap around - if (secondLead == 0) - secondLead += secondMax + 1; - secondLead -= 2; - } - - // do we have to wrap the "seconds"? - if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax)) - secondModel.setSelectionInterval((secondLead + 1)%(secondMax + 1), - (secondLead + 1)%(secondMax + 1)); - // if not, just reselect the current lead - else - secondModel.setSelectionInterval(secondLead, secondLead); - - // if we are going backwards, subtract 2 because we add 1 later - // for net change of -1 - if (reverse) - { - // check for wraparound - if (firstLead == 0) - firstLead += firstMax + 1; - firstLead -= 2; - } - // select the next "first" - firstModel.setSelectionInterval ((firstLead + 1)%(firstMax + 1), - (firstLead + 1)%(firstMax + 1)); } - - public void keyPressed(KeyEvent evt) + public void mouseDragged(MouseEvent e) + { + curr = new Point(e.getX(), e.getY()); + updateSelection(e.isControlDown()); + } + public void mouseEntered(MouseEvent e) + { + } + public void mouseExited(MouseEvent e) + { + } + public void mouseMoved(MouseEvent e) + { + } + public void mousePressed(MouseEvent e) { ListSelectionModel rowModel = table.getSelectionModel(); ListSelectionModel colModel = table.getColumnModel().getSelectionModel(); - int rowLead = rowModel.getLeadSelectionIndex(); - int rowMax = table.getModel().getRowCount() - 1; - int colLead = colModel.getLeadSelectionIndex(); - int colMax = table.getModel().getColumnCount() - 1; - - if ((evt.getKeyCode() == KeyEvent.VK_DOWN) - || (evt.getKeyCode() == KeyEvent.VK_KP_DOWN)) - { - if (evt.getModifiers() == 0) - { - - table.clearSelection(); - rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax), - Math.min(rowLead + 1, rowMax)); - colModel.setSelectionInterval(colLead,colLead); - } - else if (evt.getModifiers() == InputEvent.SHIFT_MASK) - { - rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax)); - colModel.setLeadSelectionIndex(colLead); - } - } - else if ((evt.getKeyCode() == KeyEvent.VK_UP) - || (evt.getKeyCode() == KeyEvent.VK_KP_UP)) - { - if (evt.getModifiers() == 0) - { - table.clearSelection(); - rowModel.setSelectionInterval(Math.max(rowLead - 1, 0), - Math.max(rowLead - 1, 0)); - colModel.setSelectionInterval(colLead,colLead); - } - else if (evt.getModifiers() == InputEvent.SHIFT_MASK) - { - rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0)); - colModel.setLeadSelectionIndex(colLead); - } - } - else if ((evt.getKeyCode() == KeyEvent.VK_LEFT) - || (evt.getKeyCode() == KeyEvent.VK_KP_LEFT)) - { - if (evt.getModifiers() == InputEvent.SHIFT_MASK) - { - colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0)); - rowModel.setLeadSelectionIndex(rowLead); - } - else if (evt.getModifiers() == 0) - { - table.clearSelection(); - rowModel.setSelectionInterval(rowLead,rowLead); - colModel.setSelectionInterval(Math.max(colLead - 1, 0), - Math.max(colLead - 1, 0)); - } - } - else if ((evt.getKeyCode() == KeyEvent.VK_RIGHT) - || (evt.getKeyCode() == KeyEvent.VK_KP_RIGHT)) - { - if (evt.getModifiers() == InputEvent.SHIFT_MASK) - { - colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax)); - rowModel.setLeadSelectionIndex(rowLead); - } - else if (evt.getModifiers() == 0) - { - table.clearSelection(); - rowModel.setSelectionInterval(rowLead,rowLead); - colModel.setSelectionInterval(Math.min(colLead + 1, colMax), - Math.min(colLead + 1, colMax)); - } - } - else if (evt.getKeyCode() == KeyEvent.VK_END) - { - if (evt.getModifiers() == (InputEvent.SHIFT_MASK | InputEvent.CTRL_MASK)) - { - rowModel.setLeadSelectionIndex(rowMax); - colModel.setLeadSelectionIndex(colLead); - } - else if (evt.getModifiers() == InputEvent.CTRL_MASK) - { - table.clearSelection(); - rowModel.setSelectionInterval(rowMax,rowMax); - colModel.setSelectionInterval(colLead, colLead); - } - else if (evt.getModifiers() == InputEvent.SHIFT_MASK) - { - colModel.setLeadSelectionIndex(colMax); - rowModel.setLeadSelectionIndex(rowLead); - } - else if (evt.getModifiers() == 0) - { - table.clearSelection(); - rowModel.setSelectionInterval(rowLead, rowLead); - colModel.setSelectionInterval(colMax, colMax); - } - } - else if (evt.getKeyCode() == KeyEvent.VK_HOME) - { - if (evt.getModifiers() == - (InputEvent.SHIFT_MASK | InputEvent.CTRL_MASK)) - { - rowModel.setLeadSelectionIndex(0); - colModel.setLeadSelectionIndex(colLead); - } - else if (evt.getModifiers() == InputEvent.CTRL_MASK) - { - table.clearSelection(); - rowModel.setSelectionInterval(0,0); - colModel.setSelectionInterval(colLead, colLead); - } - else if (evt.getModifiers() == InputEvent.SHIFT_MASK) - { - colModel.setLeadSelectionIndex(0); - rowModel.setLeadSelectionIndex(rowLead); - } - else if (evt.getModifiers() == 0) - { - table.clearSelection(); - rowModel.setSelectionInterval(rowLead, rowLead); - colModel.setSelectionInterval(0, 0); - } - } - else if (evt.getKeyCode() == KeyEvent.VK_F2) - { - table.editCellAt(rowLead,colLead); + + begin = new Point(e.getX(), e.getY()); + curr = new Point(e.getX(), e.getY()); + //if control is pressed and the cell is already selected, deselect it + if (e.isControlDown() && table. + isCellSelected(table.rowAtPoint(begin),table.columnAtPoint(begin))) + { + table.getSelectionModel(). + removeSelectionInterval(table.rowAtPoint(begin), + table.rowAtPoint(begin)); + table.getColumnModel().getSelectionModel(). + removeSelectionInterval(table.columnAtPoint(begin), + table.columnAtPoint(begin)); } - else if (evt.getKeyCode() == KeyEvent.VK_PAGE_UP) + else + updateSelection(e.isControlDown()); + + // If we were editing, but the moved to another cell, stop editing + if (rowLead != rowModel.getLeadSelectionIndex() || + colLead != colModel.getLeadSelectionIndex()) + if (table.isEditing()) + table.editingStopped(new ChangeEvent(e)); + } + public void mouseReleased(MouseEvent e) + { + begin = null; + curr = null; + } + } + + protected FocusListener createFocusListener() + { + return new FocusHandler(); + } + + protected MouseInputListener createMouseInputListener() + { + return new MouseInputHandler(); + } + + public Dimension getMaximumSize(JComponent comp) + { + return getPreferredSize(comp); + } + + public Dimension getMinimumSize(JComponent comp) + { + return getPreferredSize(comp); + } + + public Dimension getPreferredSize(JComponent comp) + { + int width = table.getColumnModel().getTotalColumnWidth(); + int height = table.getRowCount() * table.getRowHeight(); + return new Dimension(width, height); + } + + protected void installDefaults() + { + UIDefaults defaults = UIManager.getLookAndFeelDefaults(); + table.setFont(defaults.getFont("Table.font")); + table.setGridColor(defaults.getColor("Table.gridColor")); + table.setForeground(defaults.getColor("Table.foreground")); + table.setBackground(defaults.getColor("Table.background")); + table.setSelectionForeground(defaults.getColor("Table.selectionForeground")); + table.setSelectionBackground(defaults.getColor("Table.selectionBackground")); + table.setOpaque(true); + + highlightCellBorder = defaults.getBorder("Table.focusCellHighlightBorder"); + cellBorder = BorderFactory.createEmptyBorder(1, 1, 1, 1); + } + + private int convertModifiers(int mod) + { + if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0) + { + mod |= KeyEvent.SHIFT_MASK; + mod &= ~KeyEvent.SHIFT_DOWN_MASK; + } + if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0) + { + mod |= KeyEvent.CTRL_MASK; + mod &= ~KeyEvent.CTRL_DOWN_MASK; + } + if ((mod & KeyEvent.META_DOWN_MASK) != 0) + { + mod |= KeyEvent.META_MASK; + mod &= ~KeyEvent.META_DOWN_MASK; + } + if ((mod & KeyEvent.ALT_DOWN_MASK) != 0) + { + mod |= KeyEvent.ALT_MASK; + mod &= ~KeyEvent.ALT_DOWN_MASK; + } + if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0) + { + mod |= KeyEvent.ALT_GRAPH_MASK; + mod &= ~KeyEvent.ALT_GRAPH_DOWN_MASK; + } + return mod; + } + + protected void installKeyboardActions() + { + UIDefaults defaults = UIManager.getLookAndFeelDefaults(); + InputMap ancestorMap = (InputMap)defaults.get("Table.ancestorInputMap"); + action = new TableAction(); + Object keys[] = ancestorMap.allKeys(); + // Register the key bindings with the JTable. + // Note that we register key bindings with both the old and new modifier + // masks: InputEvent.SHIFT_MASK and InputEvent.SHIFT_DOWN_MASK and so on. + for (int i = 0; i < keys.length; i++) + { + table.registerKeyboardAction(action,(String)ancestorMap.get((KeyStroke)keys[i]), + KeyStroke.getKeyStroke + (((KeyStroke)keys[i]).getKeyCode(), convertModifiers(((KeyStroke)keys[i]).getModifiers())), + JComponent.WHEN_FOCUSED); + + table.registerKeyboardAction(action,(String)ancestorMap.get((KeyStroke)keys[i]), + KeyStroke.getKeyStroke + (((KeyStroke)keys[i]).getKeyCode(), ((KeyStroke)keys[i]).getModifiers()), + JComponent.WHEN_FOCUSED); + } + } + + /** + * This class implements the actions that we want to happen + * when specific keys are pressed for the JTable. The actionPerformed + * method is called when a key that has been registered for the JTable + * is received. + */ + class TableAction extends AbstractAction + { + /** + * What to do when this action is called. + * + * @param e the ActionEvent that caused this action. + */ + public void actionPerformed (ActionEvent e) + { + ListSelectionModel rowModel = table.getSelectionModel(); + ListSelectionModel colModel = table.getColumnModel().getSelectionModel(); + + int rowLead = rowModel.getLeadSelectionIndex(); + int rowMax = table.getModel().getRowCount() - 1; + + int colLead = colModel.getLeadSelectionIndex(); + int colMax = table.getModel().getColumnCount() - 1; + + if (e.getActionCommand().equals("selectPreviousRowExtendSelection")) + { + rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0)); + colModel.setLeadSelectionIndex(colLead); + } + else if (e.getActionCommand().equals("selectLastColumn")) + { + table.clearSelection(); + rowModel.setSelectionInterval(rowLead, rowLead); + colModel.setSelectionInterval(colMax, colMax); + } + else if (e.getActionCommand().equals("startEditing")) + { + if (table.isCellEditable(rowLead, colLead)) + table.editCellAt(rowLead,colLead); + } + else if (e.getActionCommand().equals("selectFirstRowExtendSelection")) + { + rowModel.setLeadSelectionIndex(0); + colModel.setLeadSelectionIndex(colLead); + } + else if (e.getActionCommand().equals("selectFirstColumn")) + { + rowModel.setSelectionInterval(rowLead, rowLead); + colModel.setSelectionInterval(0, 0); + } + else if (e.getActionCommand().equals("selectFirstColumnExtendSelection")) + { + colModel.setLeadSelectionIndex(0); + rowModel.setLeadSelectionIndex(rowLead); + } + else if (e.getActionCommand().equals("selectLastRow")) + { + rowModel.setSelectionInterval(rowMax,rowMax); + colModel.setSelectionInterval(colLead, colLead); + } + else if (e.getActionCommand().equals("selectNextRowExtendSelection")) + { + rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax)); + colModel.setLeadSelectionIndex(colLead); + } + else if (e.getActionCommand().equals("selectFirstRow")) + { + rowModel.setSelectionInterval(0,0); + colModel.setSelectionInterval(colLead, colLead); + } + else if (e.getActionCommand().equals("selectNextColumnExtendSelection")) + { + colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax)); + rowModel.setLeadSelectionIndex(rowLead); + } + else if (e.getActionCommand().equals("selectLastColumnExtendSelection")) + { + colModel.setLeadSelectionIndex(colMax); + rowModel.setLeadSelectionIndex(rowLead); + } + else if (e.getActionCommand().equals("selectPreviousColumnExtendSelection")) + { + colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0)); + rowModel.setLeadSelectionIndex(rowLead); + } + else if (e.getActionCommand().equals("selectNextRow")) + { + rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax), + Math.min(rowLead + 1, rowMax)); + colModel.setSelectionInterval(colLead,colLead); + } + else if (e.getActionCommand().equals("scrollUpExtendSelection")) { int target; - if (!evt.isControlDown()) - { - if (rowLead == getFirstVisibleRowIndex()) - target = Math.max - (0, rowLead - (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); - else - target = getFirstVisibleRowIndex(); - - if (evt.getModifiers() == 0) - { - rowModel.setSelectionInterval(target, target); - colModel.setSelectionInterval(colLead, colLead); - } - else if (evt.getModifiers() == InputEvent.SHIFT_MASK) - { - rowModel.setLeadSelectionIndex(target); - colModel.setLeadSelectionIndex(colLead); - } - } + if (rowLead == getFirstVisibleRowIndex()) + target = Math.max + (0, rowLead - (getLastVisibleRowIndex() - + getFirstVisibleRowIndex() + 1)); else - { - if (colLead == getFirstVisibleColumnIndex()) - target = Math.max - (0, colLead - (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); - else - target = getFirstVisibleColumnIndex(); - - if (evt.getModifiers() == InputEvent.CTRL_MASK) - { - colModel.setSelectionInterval(target, target); - rowModel.setSelectionInterval(rowLead, rowLead); - } - else if (evt.getModifiers() == - (InputEvent.SHIFT_MASK | InputEvent.CTRL_MASK)) - { - colModel.setLeadSelectionIndex(target); - rowModel.setLeadSelectionIndex(rowLead); - } - } + target = getFirstVisibleRowIndex(); + + rowModel.setLeadSelectionIndex(target); + colModel.setLeadSelectionIndex(colLead); } - else if (evt.getKeyCode() == KeyEvent.VK_PAGE_DOWN) + else if (e.getActionCommand().equals("selectPreviousRow")) + { + rowModel.setSelectionInterval(Math.max(rowLead - 1, 0), + Math.max(rowLead - 1, 0)); + colModel.setSelectionInterval(colLead,colLead); + } + else if (e.getActionCommand().equals("scrollRightChangeSelection")) { int target; - if (!evt.isControlDown()) - { - if (rowLead == getLastVisibleRowIndex()) - target = Math.min - (rowMax, rowLead + (getLastVisibleRowIndex() - - getFirstVisibleRowIndex() + 1)); - else - target = getLastVisibleRowIndex(); - - if (evt.getModifiers() == 0) - { - rowModel.setSelectionInterval(target, target); - colModel.setSelectionInterval(colLead, colLead); - } - else if (evt.getModifiers() == InputEvent.SHIFT_MASK) - { - rowModel.setLeadSelectionIndex(target); - colModel.setLeadSelectionIndex(colLead); - } - } + if (colLead == getLastVisibleColumnIndex()) + target = Math.min + (colMax, colLead + (getLastVisibleColumnIndex() - + getFirstVisibleColumnIndex() + 1)); else - { - if (colLead == getLastVisibleColumnIndex()) - target = Math.min - (colMax, colLead + (getLastVisibleColumnIndex() - - getFirstVisibleColumnIndex() + 1)); - else - target = getLastVisibleColumnIndex(); - - if (evt.getModifiers() == InputEvent.CTRL_MASK) - { - colModel.setSelectionInterval(target, target); - rowModel.setSelectionInterval(rowLead, rowLead); - } - else if (evt.getModifiers() == - (InputEvent.SHIFT_MASK | InputEvent.CTRL_MASK)) - { - colModel.setLeadSelectionIndex(target); - rowModel.setLeadSelectionIndex(rowLead); - } - } + target = getLastVisibleColumnIndex(); + + colModel.setSelectionInterval(target, target); + rowModel.setSelectionInterval(rowLead, rowLead); } - else if (evt.getKeyCode() == KeyEvent.VK_TAB - || evt.getKeyCode() == KeyEvent.VK_ENTER) + else if (e.getActionCommand().equals("selectPreviousColumn")) { - // If modifers other than SHIFT are pressed, do nothing - if (evt.getModifiers() != 0 && evt.getModifiers() != - InputEvent.SHIFT_MASK) - return; + rowModel.setSelectionInterval(rowLead,rowLead); + colModel.setSelectionInterval(Math.max(colLead - 1, 0), + Math.max(colLead - 1, 0)); + } + else if (e.getActionCommand().equals("scrollLeftChangeSelection")) + { + int target; + if (colLead == getFirstVisibleColumnIndex()) + target = Math.max + (0, colLead - (getLastVisibleColumnIndex() - + getFirstVisibleColumnIndex() + 1)); + else + target = getFirstVisibleColumnIndex(); + colModel.setSelectionInterval(target, target); + rowModel.setSelectionInterval(rowLead, rowLead); + } + else if (e.getActionCommand().equals("clearSelection")) + { + table.clearSelection(); + } + else if (e.getActionCommand().equals("cancel")) + { + // FIXME: implement other parts of "cancel" like undo-ing last + // selection. Right now it just calls editingCancelled if + // we're currently editing. + if (table.isEditing()) + table.editingCanceled(new ChangeEvent("cancel")); + } + else if (e.getActionCommand().equals("selectNextRowCell") + || e.getActionCommand().equals("selectPreviousRowCell") + || e.getActionCommand().equals("selectNextColumnCell") + || e.getActionCommand().equals("selectPreviousColumnCell")) + { // If nothing is selected, select the first cell in the table if (table.getSelectedRowCount() == 0 && table.getSelectedColumnCount() == 0) @@ -519,25 +471,24 @@ // multRowsSelected and multColsSelected tell us if multiple rows or // columns are selected, respectively boolean multRowsSelected, multColsSelected; - multRowsSelected = (table.getSelectedRowCount() > 1) || - (!table.getRowSelectionAllowed() && - table.getSelectedColumnCount() > 0); - multColsSelected = (table.getSelectedColumnCount() > 1) || - (!table.getColumnSelectionAllowed() && - table.getSelectedRowCount() > 0); + multRowsSelected = table.getSelectedRowCount() > 1 && + table.getRowSelectionAllowed(); + + multColsSelected = table.getSelectedColumnCount() > 1 && + table.getColumnSelectionAllowed(); // If there is just one selection, select the next cell, and wrap // when you get to the edges of the table. - if (!multColsSelected || !multRowsSelected) + if (!multColsSelected && !multRowsSelected) { - if (evt.getKeyCode() == KeyEvent.VK_TAB) + if (e.getActionCommand().indexOf("Column") != -1) advanceSingleSelection(colModel, colMax, rowModel, rowMax, - (evt.getModifiers() == - InputEvent.SHIFT_MASK)); + (e.getActionCommand().equals + ("selectPreviousColumnCell"))); else advanceSingleSelection(rowModel, rowMax, colModel, colMax, - (evt.getModifiers() == - InputEvent.SHIFT_MASK)); + (e.getActionCommand().equals + ("selectPreviousRowCell"))); return; } @@ -557,56 +508,112 @@ // If there are multiple rows and columns selected, select the next // cell and wrap at the edges of the selection. - if (evt.getKeyCode() == KeyEvent.VK_TAB) + if (e.getActionCommand().indexOf("Column") != -1) advanceMultipleSelection(colModel, colMinSelected, colMaxSelected, rowModel, rowMinSelected, rowMaxSelected, - (evt.getModifiers() == - InputEvent.SHIFT_MASK), true); + (e.getActionCommand().equals + ("selectPreviousColumnCell")), true); + else advanceMultipleSelection(rowModel, rowMinSelected, rowMaxSelected, colModel, colMinSelected, colMaxSelected, - (evt.getModifiers() == - InputEvent.SHIFT_MASK), false); + (e.getActionCommand().equals + ("selectPreviousRowCell")), false); table.repaint(); } - else if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) + else if (e.getActionCommand().equals("selectNextColumn")) + { + rowModel.setSelectionInterval(rowLead,rowLead); + colModel.setSelectionInterval(Math.min(colLead + 1, colMax), + Math.min(colLead + 1, colMax)); + } + else if (e.getActionCommand().equals("scrollLeftExtendSelection")) + { + int target; + if (colLead == getFirstVisibleColumnIndex()) + target = Math.max + (0, colLead - (getLastVisibleColumnIndex() - + getFirstVisibleColumnIndex() + 1)); + else + target = getFirstVisibleColumnIndex(); + + colModel.setLeadSelectionIndex(target); + rowModel.setLeadSelectionIndex(rowLead); + } + else if (e.getActionCommand().equals("scrollDownChangeSelection")) + { + int target; + if (rowLead == getLastVisibleRowIndex()) + target = Math.min + (rowMax, rowLead + (getLastVisibleRowIndex() - + getFirstVisibleRowIndex() + 1)); + else + target = getLastVisibleRowIndex(); + + rowModel.setSelectionInterval(target, target); + colModel.setSelectionInterval(colLead, colLead); + } + else if (e.getActionCommand().equals("scrollRightExtendSelection")) { - // FIXME: implement "cancel" + int target; + if (colLead == getLastVisibleColumnIndex()) + target = Math.min + (colMax, colLead + (getLastVisibleColumnIndex() - + getFirstVisibleColumnIndex() + 1)); + else + target = getLastVisibleColumnIndex(); + + colModel.setLeadSelectionIndex(target); + rowModel.setLeadSelectionIndex(rowLead); } - else if ((evt.getKeyCode() == KeyEvent.VK_A || evt.getKeyCode() - == KeyEvent.VK_SLASH) && (evt.getModifiers() == - InputEvent.CTRL_MASK)) + else if (e.getActionCommand().equals("selectAll")) { table.selectAll(); } - else if (evt.getKeyCode() == KeyEvent.VK_BACK_SLASH - && (evt.getModifiers() == InputEvent.CTRL_MASK)) + else if (e.getActionCommand().equals("selectLastRowExtendSelection")) { - table.clearSelection(); + rowModel.setLeadSelectionIndex(rowMax); + colModel.setLeadSelectionIndex(colLead); + } + else if (e.getActionCommand().equals("scrollDownExtendSelection")) + { + int target; + if (rowLead == getLastVisibleRowIndex()) + target = Math.min + (rowMax, rowLead + (getLastVisibleRowIndex() - + getFirstVisibleRowIndex() + 1)); + else + target = getLastVisibleRowIndex(); + + rowModel.setLeadSelectionIndex(target); + colModel.setLeadSelectionIndex(colLead); + } + else if (e.getActionCommand().equals("scrollUpChangeSelection")) + { + int target; + if (rowLead == getFirstVisibleRowIndex()) + target = Math.max + (0, rowLead - (getLastVisibleRowIndex() - + getFirstVisibleRowIndex() + 1)); + else + target = getFirstVisibleRowIndex(); + + rowModel.setSelectionInterval(target, target); + colModel.setSelectionInterval(colLead, colLead); } - else if (evt.getKeyCode() == KeyEvent.VK_SPACE - && (evt.getModifiers() == InputEvent.CTRL_MASK)) + else { - table.changeSelection(rowLead, colLead, true, false); + // If we're here that means we bound this TableAction class + // to a keyboard input but we either want to ignore that input + // or we just haven't implemented its action yet. } + table.scrollRectToVisible (table.getCellRect(rowModel.getLeadSelectionIndex(), colModel.getLeadSelectionIndex(), false)); } - public void keyReleased(KeyEvent e) - { - } - - public void keyTyped(KeyEvent e) - { - } - - /** - * Returns the column index of the first visible column. - * - */ int getFirstVisibleColumnIndex() { ComponentOrientation or = table.getComponentOrientation(); @@ -665,143 +672,172 @@ } return table.rowAtPoint(r.getLocation()); } - } - - class MouseInputHandler implements MouseInputListener - { - Point begin, curr; - private void updateSelection(boolean controlPressed) + /** + * A helper method for the key bindings. Used because the actions + * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar. + * + * Selects the next (previous if SHIFT pressed) column for TAB, or row for + * ENTER from within the currently selected cells. + * + * @param firstModel the ListSelectionModel for columns (TAB) or + * rows (ENTER) + * @param firstMin the first selected index in firstModel + * @param firstMax the last selected index in firstModel + * @param secondModel the ListSelectionModel for rows (TAB) or + * columns (ENTER) + * @param secondMin the first selected index in secondModel + * @param secondMax the last selected index in secondModel + * @param reverse true if shift was held for the event + * @param eventIsTab true if TAB was pressed, false if ENTER pressed + */ + void advanceMultipleSelection (ListSelectionModel firstModel, int firstMin, + int firstMax, ListSelectionModel secondModel, + int secondMin, int secondMax, boolean reverse, + boolean eventIsTab) { - // Update the rows - int lo_row = table.rowAtPoint(begin); - int hi_row = table.rowAtPoint(curr); - ListSelectionModel rowModel = table.getSelectionModel(); - if (lo_row != -1 && hi_row != -1) + // If eventIsTab, all the "firsts" correspond to columns, otherwise, to rows + // "seconds" correspond to the opposite + int firstLead = firstModel.getLeadSelectionIndex(); + int secondLead = secondModel.getLeadSelectionIndex(); + int numFirsts = eventIsTab ? + table.getModel().getColumnCount() : table.getModel().getRowCount(); + int numSeconds = eventIsTab ? + table.getModel().getRowCount() : table.getModel().getColumnCount(); + + // check if we have to wrap the "firsts" around, going to the other side + if ((firstLead == firstMax && !reverse) || + (reverse && firstLead == firstMin)) { - if (controlPressed && rowModel.getSelectionMode() - != ListSelectionModel.SINGLE_SELECTION) - rowModel.addSelectionInterval(lo_row, hi_row); + firstModel.addSelectionInterval(reverse ? firstMax : firstMin, + reverse ? firstMax : firstMin); + + // check if we have to wrap the "seconds" + if ((secondLead == secondMax && !reverse) || + (reverse && secondLead == secondMin)) + secondModel.addSelectionInterval(reverse ? secondMax : secondMin, + reverse ? secondMax : secondMin); + + // if we're not wrapping the seconds, we have to find out where we + // are within the secondModel and advance to the next cell (or + // go back to the previous cell if reverse == true) else - rowModel.setSelectionInterval(lo_row, hi_row); + { + int[] secondsSelected; + if (eventIsTab && table.getRowSelectionAllowed() || + !eventIsTab && table.getColumnSelectionAllowed()) + secondsSelected = eventIsTab ? + table.getSelectedRows() : table.getSelectedColumns(); + else + { + // if row selection is not allowed, then the entire column gets + // selected when you click on it, so consider ALL rows selected + secondsSelected = new int[numSeconds]; + for (int i = 0; i < numSeconds; i++) + secondsSelected[i] = i; + } + + // and now find the "next" index within the model + int secondIndex = reverse ? secondsSelected.length - 1 : 0; + if (!reverse) + while (secondsSelected[secondIndex] <= secondLead) + secondIndex++; + else + while (secondsSelected[secondIndex] >= secondLead) + secondIndex--; + + // and select it - updating the lead selection index + secondModel.addSelectionInterval(secondsSelected[secondIndex], + secondsSelected[secondIndex]); + } } - - // Update the columns - int lo_col = table.columnAtPoint(begin); - int hi_col = table.columnAtPoint(curr); - ListSelectionModel colModel = table.getColumnModel(). - getSelectionModel(); - if (lo_col != -1 && hi_col != -1) + // We didn't have to wrap the firsts, so just find the "next" first + // and select it, we don't have to change "seconds" + else { - if (controlPressed && colModel.getSelectionMode() != - ListSelectionModel.SINGLE_SELECTION) - colModel.addSelectionInterval(lo_col, hi_col); + int[] firstsSelected; + if (eventIsTab && table.getColumnSelectionAllowed() || + !eventIsTab && table.getRowSelectionAllowed()) + firstsSelected = eventIsTab ? + table.getSelectedColumns() : table.getSelectedRows(); else - colModel.setSelectionInterval(lo_col, hi_col); + { + // if selection not allowed, consider ALL firsts to be selected + firstsSelected = new int[numFirsts]; + for (int i = 0; i < numFirsts; i++) + firstsSelected[i] = i; + } + int firstIndex = reverse ? firstsSelected.length - 1 : 0; + if (!reverse) + while (firstsSelected[firstIndex] <= firstLead) + firstIndex++; + else + while (firstsSelected[firstIndex] >= firstLead) + firstIndex--; + firstModel.addSelectionInterval(firstsSelected[firstIndex], + firstsSelected[firstIndex]); + secondModel.addSelectionInterval(secondLead, secondLead); } } + + /** + * A helper method for the key bindings. Used because the actions + * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar. + * + * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER) + * in the table, changing the current selection. All cells in the table + * are eligible, not just the ones that are currently selected. + * @param firstModel the ListSelectionModel for columns (TAB) or rows + * (ENTER) + * @param firstMax the last index in firstModel + * @param secondModel the ListSelectionModel for rows (TAB) or columns + * (ENTER) + * @param secondMax the last index in secondModel + * @param reverse true if SHIFT was pressed for the event + */ - public void mouseClicked(MouseEvent e) - { - } - public void mouseDragged(MouseEvent e) - { - curr = new Point(e.getX(), e.getY()); - updateSelection(e.isControlDown()); - } - public void mouseEntered(MouseEvent e) - { - } - public void mouseExited(MouseEvent e) - { - } - public void mouseMoved(MouseEvent e) - { - } - public void mousePressed(MouseEvent e) + void advanceSingleSelection (ListSelectionModel firstModel, int firstMax, + ListSelectionModel secondModel, int secondMax, + boolean reverse) { - ListSelectionModel rowModel = table.getSelectionModel(); - ListSelectionModel colModel = table.getColumnModel().getSelectionModel(); - int rowLead = rowModel.getLeadSelectionIndex(); - int colLead = colModel.getLeadSelectionIndex(); - - begin = new Point(e.getX(), e.getY()); - curr = new Point(e.getX(), e.getY()); - //if control is pressed and the cell is already selected, deselect it - if (e.isControlDown() && table. - isCellSelected(table.rowAtPoint(begin),table.columnAtPoint(begin))) - { - table.getSelectionModel(). - removeSelectionInterval(table.rowAtPoint(begin), - table.rowAtPoint(begin)); - table.getColumnModel().getSelectionModel(). - removeSelectionInterval(table.columnAtPoint(begin), - table.columnAtPoint(begin)); + // for TABs, "first" corresponds to columns and "seconds" to rows. + // the opposite is true for ENTERs + int firstLead = firstModel.getLeadSelectionIndex(); + int secondLead = secondModel.getLeadSelectionIndex(); + + // if we are going backwards subtract 2 because we later add 1 + // for a net change of -1 + if (reverse && (firstLead == 0)) + { + // check if we have to wrap around + if (secondLead == 0) + secondLead += secondMax + 1; + secondLead -= 2; } + + // do we have to wrap the "seconds"? + if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax)) + secondModel.setSelectionInterval((secondLead + 1)%(secondMax + 1), + (secondLead + 1)%(secondMax + 1)); + // if not, just reselect the current lead else - updateSelection(e.isControlDown()); - - // If we were editing, but the moved to another cell, stop editing - if (rowLead != rowModel.getLeadSelectionIndex() || - colLead != colModel.getLeadSelectionIndex()) - if (table.isEditing()) - table.editingStopped(new ChangeEvent(e)); - } - public void mouseReleased(MouseEvent e) - { - begin = null; - curr = null; + secondModel.setSelectionInterval(secondLead, secondLead); + + // if we are going backwards, subtract 2 because we add 1 later + // for net change of -1 + if (reverse) + { + // check for wraparound + if (firstLead == 0) + firstLead += firstMax + 1; + firstLead -= 2; + } + // select the next "first" + firstModel.setSelectionInterval ((firstLead + 1)%(firstMax + 1), + (firstLead + 1)%(firstMax + 1)); } } - protected FocusListener createFocusListener() - { - return new FocusHandler(); - } - protected KeyListener createKeyListener() - { - return new KeyHandler(); - } - protected MouseInputListener createMouseInputListener() - { - return new MouseInputHandler(); - } - - public Dimension getMaximumSize(JComponent comp) - { - return getPreferredSize(comp); - } - - public Dimension getMinimumSize(JComponent comp) - { - return getPreferredSize(comp); - } - - public Dimension getPreferredSize(JComponent comp) - { - int width = table.getColumnModel().getTotalColumnWidth(); - int height = table.getRowCount() * table.getRowHeight(); - return new Dimension(width, height); - } - - protected void installDefaults() - { - UIDefaults defaults = UIManager.getLookAndFeelDefaults(); - table.setFont(defaults.getFont("Table.font")); - table.setGridColor(defaults.getColor("Table.gridColor")); - table.setForeground(defaults.getColor("Table.foreground")); - table.setBackground(defaults.getColor("Table.background")); - table.setSelectionForeground(defaults.getColor("Table.selectionForeground")); - table.setSelectionBackground(defaults.getColor("Table.selectionBackground")); - table.setOpaque(true); - - highlightCellBorder = defaults.getBorder("Table.focusCellHighlightBorder"); - cellBorder = BorderFactory.createEmptyBorder(1, 1, 1, 1); - } - protected void installKeyboardActions() - { - } - protected void installListeners() { table.addFocusListener(focusListener); @@ -846,7 +882,6 @@ { table = (JTable)comp; focusListener = createFocusListener(); - keyListener = createKeyListener(); mouseInputListener = createMouseInputListener(); installDefaults(); installKeyboardActions();