JDK-4907988 : JTable focus traversal gets confused when text selected by mouse
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.4.1,1.4.2
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2003-08-18
  • Updated: 2005-03-02
  • Resolved: 2005-03-02
Related Reports
Relates :  
Description

Name: jk109818			Date: 08/17/2003


FULL PRODUCT VERSION :
java version "1.4.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1-b21)
Java HotSpot(TM) Client VM (build 1.4.1-b21, mixed mode)

FULL OS VERSION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
I have a multi-column JTable where the second column (a JTextField) is editable.  Double click the cell to give it focus, and then use the mouse to select the existing text going from right to left.  If the mouse button is actually released while the mouse cursor is over the first column (which happens frequently when the editable cell is left justified), the cell in the second column appears to still have focus.  Typing does replace the text in this cell, but when the field is tabbed from, the editingStopped method thinks the current column is column 0.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a JTable with several columns.  Override the editingStopped method and have it print the column of the current cell where editing just completed. Make the second column editable.

Put some data in the second column and tab out.  Double click on the same cell to give it focus.  Now, using the mouse, select all of the text in the cell by starting to the right of the text and dragging to the left over the cell contents.  Do not release the mouse button until the cursor is over the cell in the first column.

Start typing.  The text in the second column is replaced, as expected, but when the cell is tabbed from, the editingStopped method reports that the current column is 0 (the first column).

EXPECTED VERSUS ACTUAL BEHAVIOR :
editingStopped should report 1 (the second column).
editingStopped is reporting 0 (the first column).

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import java.util.*;
import java.io.*;

public class EditingStoppedBug {

  private JTextField nfSpheroNo = new JTextField(3);

  protected JTable tblManualSpheroData; //table
  private TableColumnModel tblColMdl; //column model - default
  private JScrollPane spTblManualSpheroData; //scroll Pane to hold the table

  private JFrame mainFrame = new JFrame();
  private JPanel panel2 = new JPanel();

  private Vector columnNames;
  private Vector data = new Vector();

  private int numRows;

  public static void main(String[] args) {
    EditingStoppedBug app = new EditingStoppedBug();
    try {
      app.jbInit();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  public void jbInit() throws Exception {
    columnNames = new Vector();

    columnNames.addElement("Col 1");
    columnNames.addElement("Col 2");
    columnNames.addElement("Col 3");

    //create an empty table
    MyTableModel tblMdlManualSpheroData = new MyTableModel(0, 0);
    tblMdlManualSpheroData.insertRows(0, 1);

    tblMdlManualSpheroData.insertColumns(0, 1, "Col 1");
    tblMdlManualSpheroData.insertColumns(2, 1, "Col 2");
    tblMdlManualSpheroData.insertColumns(3, 1, "Col 3");

    tblManualSpheroData = new JTable(tblMdlManualSpheroData) {
      //override from AbstractTableModel
      public boolean isCellEditable(int rowIndex, int columnIndex) {
        //Action and Merck Number columns should not be editable
        if (columnIndex == 0 || columnIndex == 2 || columnIndex == 3) {
          return false;
        }
        return true;
      }

      public void editingStopped(ChangeEvent e) {
        super.editingStopped(e);
        int currentRow = tblManualSpheroData.getSelectedRow();
        int currentCol = tblManualSpheroData.getSelectedColumn();
        System.out.println("column = " + currentCol);
      } //editingStopped

    };
    tblManualSpheroData.setSurrendersFocusOnKeystroke(true);
    tblColMdl = tblManualSpheroData.getColumnModel();
    tblColMdl.getColumn(1).setCellEditor(new DefaultCellEditor(nfSpheroNo));
    spTblManualSpheroData = new JScrollPane(tblManualSpheroData);

    panel2.add(spTblManualSpheroData);

    mainFrame.getContentPane().add(panel2, BorderLayout.CENTER);
    mainFrame.pack();
    mainFrame.setVisible(true);
  }
}

class MyTableModel
    extends AbstractTableModel {
  public static final String DEFAULT_COLUMN_NAME = "untitled";
  private Vector columnNames;
  private Vector data;

  public String[] rowNames = {};
  private boolean relative = false;
  private Object o;

  public MyTableModel(int rows, int columns) {
    super();

    columnNames = new Vector(columns);
    for (int j = 0; j < columns; j++) {
      columnNames.addElement(new String(DEFAULT_COLUMN_NAME));
    }
    data = new Vector(rows);

    for (int i = 0; i < rows; i++) {
      Vector v = new Vector(columns);
      data.addElement(v);
      for (int j = 0; j < columns; j++) {
        v.addElement("");
      }
    }
  }

  public int getRowCount() {
    if (data.size() == 0) {
      return 0;
    }
    else {
      return data.size();
    }
  }

  public int getColumnCount() {
    if (data.size() == 0) {
      return 0;
    }
    else {
      return ( (Vector) data.elementAt(0)).size();
    }
  }

  public Object getValueAt(int rowIndex, int columnIndex) {
    return ( (Vector) data.elementAt(rowIndex)).elementAt(
        columnIndex);
  }

  public void setValueAt(Object value, int rowIndex, int columnIndex) {
    ( (Vector) data.elementAt(rowIndex)).setElementAt(value,
        columnIndex);
    fireTableCellUpdated(rowIndex, columnIndex);
  }

  public void insertRows(int firstRowIndex, int count) {
    int rows = data.size();
    int cols = getColumnCount();
    if (firstRowIndex > rows) {
      firstRowIndex = rows;
    }
    for (int i = 0; i < count; i++) {
      Vector v = new Vector(cols);
      data.insertElementAt(v, firstRowIndex + i);
      for (int j = 0; j < cols; j++) {
        v.addElement("");
      }
    }
    fireTableStructureChanged();
  }

  public void insertColumns(int firstColumnIndex, int count, String columnName) {
    int rows = 0;
    if (data.size() == 0) {
      rows = 0;
    }
    else {
      rows = data.size();
    }
    int cols = getColumnCount();
    if (firstColumnIndex > cols) {
      firstColumnIndex = cols;
    }
    for (int j = 0; j < count; j++) {
      columnNames.insertElementAt(columnName, firstColumnIndex + j);
    }
    for (int i = 0; i < rows; i++) {
      Vector v = (Vector) data.elementAt(i);
      for (int j = 0; j < count; j++) {
        v.insertElementAt("", firstColumnIndex + j);
      }
    }
    fireTableStructureChanged();
  }
}

---------- END SOURCE ----------
(Incident Review ID: 184823) 
======================================================================

Comments
EVALUATION After many tries, I was unable to duplicate the behavior exactly as the submitter mentions. However, I did manage to modify the procedure slightly and see a similar result. Rather than double-clicking on the cell before trying to select the text, I single clicked on it and dragged over to the first column (not really selecting any text). Leaving the rest of the steps the same, I got the mentioned result. I'm going to e-mail the submitter to see if this is what they were actually talking about. This isn't really a bug. The code is printing out the return value from getSelectedColumn(). When a user presses and drags like this, it extends the selection. According to the documentation, getSelectedColumn() simply returns the first selected column. Unfortunately, in 1.4.1 it isn't apparent that this drag operation is adding column 0 to the selection. This is because JTable was using the anchor index as the visible selection. 4303294 includes a fix this to make the lead index the visible selection. After the fix to 4303294, this behavior can no longer occur. Dragging from column 1 to column 0 will cause column 0 to show as being selected. Typing characters will attempt to edit that column, which is not editable, rather than column 1. Therefore, the behavior mentioned in this report could not happen. Moving to incomplete while I e-mail the submitter. Want to confirm that the modified steps I used are what they were actually doing. ###@###.### 2003-09-30 What can still happen, even after the fix to 4303294, is for the user to drag from column 0 to column 1, start editing, and then stop editing - which still causes this test to print out column 0 as the current column. What needs to be understood here is the definition of getSelectedColumn() - the definition of selection is very light here; this returns the index of the *first* selected column. Even though it isn't visible (since the table is in row selection mode), dragging across columns internally selects groups of columns. getSelectedColumn() returns the index of the first column in the selected range. To fix this test, the user can do one of two things: a) use table.getColumnSelectionModel().getLeadSelectionIndex() instead to determine which column is actually "selected" (ie. showing focus) b) configure the table with table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) Option b) enforces single selections only, and therefore there can only ever be a single column selected (which will also be the focused one). The rest of the user's code will then work as-is. Closing as not a bug. ###@###.### 2005-03-02 20:49:08 GMT
02-03-2005