1

Here's a message I am plagued by, which occurs when I attempt to programmatically "select" cells (whether empty or not) in a grid via keypress combo shift-rightarrow or shift-leftarrow:

Exception in thread "AWT-EventQueue-0" javax.swing.text.StateInvariantError: 
Bad caret position

(Note that there is NO problem if I "select" via shift-uparrow or shift-downarrow.)

It happens when I attempt to change the font of the "selected" cells:

  static Font fontSelected = new Font("Serif", Font.BOLD , POINTSIZE);
  static Font fontNormal = new Font("Serif", Font.PLAIN, POINTSIZE);

(If I make the Font.type the SAME (both BOLD, both PLAIN, both ITALIC), no problem.)

The error occurs near code where I push a "selected" JTextField onto a stack (named stack), which is defined like so:

class GenericStack<E>:
  public LinkedList <E> stack = new LinkedList<>();

Here's the class declaration where the stack and fonts are used:

public class Grid  extends GenericStack<JTextField> implements ActionListener, KeyListener, KeyCodes, Serializable

Here's what's pushed onto stack:

 public static JTextField[][] cells = new JTextField[N][N];

Here's how cells are created:

    guiFrame.add(textPanel);
    for (int i = 0; i < N; i++) 
      for (int j = 0; j < N; j++) 
        cells[i][j] = addCell(textPanel, i, j);

  private JTextField addCell (Container parent, int row, int col) {
    JTextField cell;
    cell = new JTextField();
    cell.setFont(fontNormal);                  // 'default' font set
    cell.setText("x");                         // for debugging
    String r, c;                               // 11x11 grid
    if(row < N-1) r = "" + row; else r = "A";  // rows  r: 0,1,2,...A
    if(col < N-1) c = "" + col; else c = "A";  // cols  c: 0,1,2,...A
    cell.setActionCommand(r + c);              // cell rc: 00..0A;10..1A;...A0..AA;
    cell.addKeyListener(this);
    cell.setHorizontalAlignment(JTextField.CENTER);
    parent.add(cell);
    return cell;
  }  

Here's main:

  public static void main(String[] args)
  {    
     javax.swing.SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        new Grid();
      }
    });
  }

Here's where the font is changed (for any "selected" cell):

if(currentCell.selected){
  Grid.cells[currentCell.row][currentCell.col].setBackground(Color.RED);
  Grid.cells[currentCell.row][currentCell.col].setFont(fontSelected);
  stack.push(Grid.cells[currentCell.row][currentCell.col]);
}

The error occurs in this block of code--if I comment out the setFont line, no problem; if I instead change the font declarations to involve the same font, no problem.

Especially puzzling me is that the stack trace doesn't specify which line of code caused the error.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
DSlomer64
  • 4,234
  • 4
  • 53
  • 88
  • 1
    If you don't get help soon, consider creating and posting an [sscce](http://sscce.org) where you condense your code into the smallest bit that still compiles and runs, has no outside dependencies (such as need to link to a database or local images), has no extra code that's not relevant to your problem, but still demonstrates your problem. – Hovercraft Full Of Eels Oct 13 '13 at 00:04
  • Google shows this: "Bug 81777 - javax.swing.text.StateInvariantError: Bad caret position... resolved fixed ... 2006 ...2008 ... Hardware: ... Mac OS X" as well as this: Subject: [Bug 236459] New: Bad caret position permalink From: ekan...@netbeans.org (ekan...@netbeans.org) Date: Sep 27, 2013 3:34:13 am List: org.netbeans.classfile.issues So maybe I'm just screwed?? But I'll do what "Eels" just suggested, I guess. Or maybe just use a different "selection" method. – DSlomer64 Oct 13 '13 at 00:11
  • Again, for the best chances of us understanding your problem and *experiencing* your problem, post an [sscce](http://sscce.org). You've nothing to lose in the effort. – Hovercraft Full Of Eels Oct 13 '13 at 00:18
  • 1
    Will do. I guess if it's a bug and not just me, the Java community will profit, so I'm on it. – DSlomer64 Oct 13 '13 at 00:32
  • I'll up-vote you for the effort and for my opportunity to learn. – Hovercraft Full Of Eels Oct 13 '13 at 00:33

2 Answers2

2

I'm not sure why your exception is occurring, but it can be solved by queuing the font change on the Swing event thread:

@Override
public void keyPressed(KeyEvent evt) {
  final JComponent comp = (JComponent) evt.getSource();
  int keyCode = evt.getKeyCode();
  boolean shiftIsDown = evt.isShiftDown();
  currentCell.selected = ((shiftIsDown & (keyCode == RIGHT | keyCode == UP
        | keyCode == LEFT | keyCode == DOWN)));
  if (currentCell.selected) {
     SwingUtilities.invokeLater(new Runnable() {
        public void run() {
           comp.setFont(fontSelected);
        }
     });
  }
}

Myself, I try to avoid KeyListeners with Swing applications but instead prefer key bindings. For example:

import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

import javax.swing.*;

@SuppressWarnings("serial")
public class SSCCE2 extends JPanel {
   private static final int ROW_COUNT = 11;
   private static final int colCount = 3;
   private static final Font NORMAL_FONT = new Font("Serif", Font.PLAIN, 18);
   private static final Font SELECTED_FONT = NORMAL_FONT.deriveFont(Font.BOLD);

   private JTextField[][] fields = new JTextField[ROW_COUNT][ROW_COUNT];

   public SSCCE2() {
      FontAction fontAction = new FontAction();
      int condition = WHEN_FOCUSED;

      setLayout(new GridLayout(ROW_COUNT, ROW_COUNT));
      for (int i = 0; i < fields.length; i++) {
         for (int j = 0; j < fields[i].length; j++) {
            JTextField cell = new JTextField(colCount);
            InputMap inputMap = cell.getInputMap(condition);
            ActionMap actionMap = cell.getActionMap();
            int[] arrowKeyCodes = {KeyEvent.VK_UP, KeyEvent.VK_DOWN, 
                  KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT};
            for (int keyCode : arrowKeyCodes) {
               KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, 
                     KeyEvent.SHIFT_DOWN_MASK);
               inputMap.put(keyStroke, keyStroke.toString());
               actionMap.put(keyStroke.toString(), fontAction);
            }
            cell.setFont(NORMAL_FONT);
            cell.setHorizontalAlignment(JTextField.CENTER);
            add(cell);
            fields[i][j] = cell;
         }
      }


   }

   private class FontAction extends AbstractAction {
      @Override
      public void actionPerformed(ActionEvent evt) {
         for (JTextField[] row : fields) {
            for (JTextField textField : row) {
               if (textField.hasFocus()) {
                  textField.setFont(SELECTED_FONT);
               } else {
                  textField.setFont(NORMAL_FONT);
               }
            }
         }
      }
   }

   private static void createAndShowGui() {
      SSCCE2 mainPanel = new SSCCE2();

      JFrame frame = new JFrame("SSCCE2");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
0

I removed everything, including cursor movement code, that didn't affect getting the message, so when this is run, a grid appears, and to get the error, hold the shift key down and press right arrow key. (Is this what I should've done??) (P.S.--I even took out all the generic linked list (stack) stuff, which, it turns out, had nothing to do with the problem.)

package sscce;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

class Cell 
{
  int row, col;
  boolean selected;

  Cell(int row, int col){
    this.row = row;
    this.col = col;  
  } 
}

public class SSCCE implements ActionListener, KeyListener
{
  public static final int LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40;
  public static final int N = 11;
  JFrame guiFrame;
  JPanel textPanel;
  public static JTextField[][] cells = new JTextField[N][N];
  public static Cell currentCell = new Cell(0,0);

  static Font fontSelected = new Font("Serif", Font.BOLD , 12);
  static Font fontNormal = new Font("Serif", Font.PLAIN, 12);

  public SSCCE(){ 
    textPanel = new JPanel();
    textPanel.setLayout(new GridLayout(N, N));
    guiFrame = new JFrame();
    guiFrame.setMinimumSize(new Dimension(400, 400));
    guiFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    guiFrame.setLocationRelativeTo(null);    
    guiFrame.add(textPanel);
    for (int i = 0; i < N; i++) 
      for (int j = 0; j < N; j++) 
        cells[i][j] = addCell(textPanel, i, j);
    guiFrame.setVisible(true);
  }  

  private JTextField addCell (Container parent, int row, int col) {
    JTextField cell;
    cell = new JTextField();
    cell.setFont(fontNormal);
    cell.addKeyListener((KeyListener) this);
    cell.setHorizontalAlignment(JTextField.CENTER);
    parent.add(cell);
    return cell;
  }  

  public static void main(String[] args)
  {    
     javax.swing.SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        new SSCCE();
      }
    });
  }

  @Override
  public void keyPressed(KeyEvent evt) { 
    int keyCode = evt.getKeyCode();
    boolean shiftIsDown = evt.isShiftDown();
    currentCell.selected = ((shiftIsDown & (keyCode == RIGHT | keyCode == UP | keyCode == LEFT | keyCode == DOWN)));
    if(currentCell.selected ){
      SSCCE.cells[currentCell.row][currentCell.col].setFont(fontSelected);
    }
   }
  @Override
  public void keyTyped(KeyEvent e){     }
  @Override
  public void keyReleased(KeyEvent e){  }
  @Override
  public void actionPerformed(ActionEvent e){}
}
DSlomer64
  • 4,234
  • 4
  • 53
  • 88
  • Ah. "Key binding". New concept. Guess I DON'T know it all. (Ha.) Does my problem have anything to do with the fact that, as Java has told me over and over, "Warning: Swing is not thread safe. For more information see Swing's Threading Policy," which I've never done. And does your code (below) answer that question? SwingUtilities.invokeLater(new Runnable() – DSlomer64 Oct 13 '13 at 18:56
  • THANKS, EELS, for not only taking interest in MY problem (i.e., not a Java bug) AND for not only FIXING it but designing alternative code that, while I assure you I can't quite follow it, I will study it later. YOU ROCK!! (Ah! Unless I'm mistaken, I just "got" your name! "I have had it with these goshdarned eels on this goshdarned hovercraft!")(A bonus: laughter!!) – DSlomer64 Oct 13 '13 at 19:09