This is one of those “I wonder if…” moments.
Personally, I’d try a tackle the problem more directly, at the source. This means “trapping” the Tab event some how and “replacing” it’s functionality.
So, I started by modifying http://www.java2s.com/Tutorial/Java/0260__Swing-Event/ListingtheKeyBindingsinaComponent.htm, which could list the key bindings for component, for this, I found that the JTextArea was using insert-tab
as the action map key.
I then created my own Action
designed to insert spaces at the current caret position, for example…
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
// insert-tab
JTextArea ta = new JTextArea(10, 20);
add(new JScrollPane(ta));
ActionMap am = ta.getActionMap();
am.put("insert-tab", new SpacesTabAction());
}
public class SpacesTabAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JTextArea)) {
return;
}
JTextArea textArea = (JTextArea) e.getSource();
int caretPosition = textArea.getCaretPosition();
Document document = textArea.getDocument();
try {
document.insertString(caretPosition, " ", null);
} catch (BadLocationException ex) {
ex.printStackTrace();
}
}
}
}
}
Admittable limitations
This will not cover pasting the text into the component, which would otherwise be covered by a DocumentFilter
, but, I like to think about scenarios where a DocumentFilter
might not be usable (such as having one already installed)
More investigations
The method setTabSize does not work, as it puts a tab ('\t') in the contents of the text area.
Is this one of those “spaces vs tabs” flame wars :P
I did some fiddling and discovered that, most of the inconsistencies with setTabSize
came about from not using a fixed width font, for example…
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
Font font = Font.decode("Courier New").deriveFont(12);
JTextArea ta = new JTextArea(10, 40);
ta.setFont(font);
ta.setText("---|----|----|----|----|----|----|----|\n\tHello");
ta.setTabSize(0);
add(new JScrollPane(ta));
DefaultComboBoxModel<Integer> model = new DefaultComboBoxModel<>(new Integer[] {0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24});
JComboBox<Integer> tabSizes = new JComboBox<>(model);
add(tabSizes, BorderLayout.NORTH);
tabSizes.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Integer tabSize = (Integer)tabSizes.getSelectedItem();
ta.setTabSize(tabSize);
}
});
}
}
}
When I set the font to “Courier New”, I was able to get a consistent indentation which was in align with the set tab size





I'm obviously missing something...
The tab is four spaces. In this "111\t" string, the tab is expands to 1 space; "22\t" — to 2 spaces; "3\t" — to 3 spaces; finally, "\t" expands to four spaces.
Yes, isn't this the expected behaviour?!
JTextArea
, tabSize
of 4
, mono spaced font

Sublime text editor, tabSize
of 4
, mono spaced font

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Font;
import java.util.StringJoiner;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
StringJoiner textJoiner = new StringJoiner("\n");
for (int row = 0; row < 10; row++) {
StringJoiner rowJoiner = new StringJoiner("\t");
for (int col = 0; col < 10; col++) {
rowJoiner.add(Integer.toString(col).repeat(row));
}
textJoiner.add(rowJoiner.toString());
}
setLayout(new BorderLayout());
JTextArea ta = new JTextArea(10, 40);
ta.setFont(new Font("Monospaced", Font.PLAIN, 13));
ta.setText(textJoiner.toString());
add(new JScrollPane(ta));
}
protected String replicate(char value, int times) {
return new String(new char[times]).replace('\0', value);
}
}
}
I think you're thinking of "tab stops", rather than "tab size", for example, in which case, even the DocumentFilter
won't do what you're expecting.
I assume the use of monospace font is implied in the question
Don't "assume" we know anything. In my experimentation, the font was NOT monospaced. This is where providing "expected" and "actual" results and a minimal reproducible example, as it removes the ambiguity and doesn't waste everybody's time (especially yours)
It is how it works in any text editor.
I'm obviously using different text editors